Добавление автофильтра и сортировка приводит к сбою Excel

Я разрабатываю приложение, в котором вы можете экспортировать некоторые данные в файл Excel с помощью OpenXML. Все работает отлично, за исключением автофильтра. Идея состоит в том, чтобы добавить автофильтр в основную часть данных, чтобы пользователь автоматически имел элементы управления для фильтрации и сортировки данных. Поэтому в коде я делаю что-то вроде этого:

var filter = new AutoFilter() { Reference = string.Format("{0}:{1}", topLeftCellReference, bottomRightCellReference ) };
worksheet.AppendChild(filter);

В экспортированном XLSX он выглядит примерно так:

<x:autoFilter ref="A4:L33" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" />

И он добавлен на рабочий лист между sheetData и mergeCells.

Затем я могу открыть этот фильтр в Excel, и он отлично работает. Ожидайте, если вы попытаетесь отсортировать столбец, сортировка столбца, а затем произойдет сбой Excel. Сохранение и перезагрузка файла (что заставляет Excel очищать все) не устраняет проблему. Но если вы примените фильтр сначала (скажем, отфильтруйте столбец до > 10, а затем удалите этот фильтр, теперь вы можете сортировать без сбоев. Я сохранил файл после применения фильтра и удалил его, и теперь этот файл в порядке, но выглядящий в XML "восстановленного" файла я не вижу никакой очевидной разницы.

Кто-нибудь знает, что может вызвать проблему? Есть ли что-нибудь еще, что я должен делать, применяя автоматический фильтр, кроме добавления его на рабочий лист?

Примечание. Мы используем Excel 2010 (версия 14.0.7153.5000)

Вот пример файла (щелкните по загрузке, и он будет загружен как .zip). Переименуйте в .xlsx, чтобы открыть в Excel. Включите редактирование, выберите один столбцов и попробуйте сортировать).

Изменить: играйте с этим еще немного. Если вы сохраняете файл в Excel, он все еще сломан. Однако, если вы сначала примените фильтр (а затем очистите его), а затем сохраните в Excel, вы получите рабочий файл. Если посмотреть на два файла (все еще сломанный сохраненный файл и рабочий файл), я заметил, что этот дополнительный бит добавлен в рабочую книгу после того, как фильтр был применен (и очищен):

  <x:definedNames>
    <x:definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">'Sheet 1'!$A$1:$E$11</x:definedName>
  </x:definedNames>

Не уверен, что это может быть что-то или нет...

Ответ 1

Хорошо, поэтому кажется, что волшебная формула состоит в том, чтобы добавить часть DefinedNames, как я предложил в своем редактировании:

<x:definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">'Sheet 1'!$A$1:$E$11</x:definedName>

По-видимому, для работы автофильтра требуется _xlmn._FilterDatabase (по крайней мере, для сортировки). Я думаю, если это не так, когда вы фильтруете, он создается, но если он не появляется, когда вы сортируете, он взрывает Excel.

Поэтому вам нужно имя листа и ссылку на ячейку, чтобы заполнить его.

Просматривая стандарт Open XML, в разделе 18.2.5 из definedName, я вижу следующее:

Фильтр и расширенный фильтр

_xlnm.Criteria: это определенное имя относится к диапазону, содержащему значения критериев для использования при применении расширенного фильтра к диапазону данных.

_xlnm._FilterDatabase: может быть одним из следующих

а. это определенное имя относится к диапазону, к которому был добавлен усовершенствованный фильтр применяется. Это представляет собой диапазон исходных данных, нефильтрованный.

б. Это определенное имя относится к диапазону, в который был установлен AutoFilter приложенное.

Итак, вам кажется, что вам нужно добавить _xlnm._FilterDatabase для каждого листа с фильтром (похоже, на одном листе нет возможности иметь более одного фильтра). Имя такое же _xlmn_FilterDatabase независимо от того, сколько листов у вас есть с фильтрами, потому что я думаю, что только комбинация имени и localSheetId должна быть уникальной.

Итак, в конце концов, у меня есть что-то вроде этого:

var filter = new AutoFilter() { Reference = string.Format("{0}:{1}", topLeftCellReference, bottomRightCellReference ) };
worksheet.AppendChild(filter);

workbookPart.Wookbook.DefinedNames.AppendChild(new DefinedName(string.Format("'{0}'!$A${1}:${2}${3}",
    sheet.Name,
    leftColumnLetter,
    topRowIndex,
    rightColumnLetter,
    bottomRowIndex))
{
    Name = "_xlnm._FilterDatabase",
    LocalSheetId = sheet.SheetId - 1,
    Hidden = true
});

Кажется, что он работает с ошибкой в ​​Excel. Excel должен проверить, определено ли имя перед сортировкой и автоматически создать его (что, по-видимому, выполняется, если вы фильтруете, а не сортируете).