VSTO - Excel - Автофильтр - Попытка получить критерии фильтра даты (год, месяц или день) вызывает исключение

Я пытаюсь получить фильтры таблицы, чтобы повторно применить их после некоторых изменений. Все в порядке, но проблема начинается, когда фильтр находится в столбце «Дата». Вот как я это делаю:

for (int i = 1; i <= filters.Count; i++)
{
  FilterTemp f = new FilterTemp();
  f.On = filters[i].On;

  if (f.On)
  {
    f.Field = i;

    try
    {
      f.Criteria1 = filters[i].Criteria1;
    }
    catch { }

    f.Operator = (int)filters[i].Operator;

    try
    {
      f.Criteria2 = filters[i].Criteria2;
    }
    catch { }
  }

  fs.Add(f);
}

Когда фильтр находится в текстовом или числовом столбце, все работает прекрасно, но когда столбец даты фильтруется по году, месяцу или дню, я получаю исключение в строке 5 при попытке получить «Criteria1».

Я попытался изменить оператор на xlFilterDynamic, как упоминалось в ответе на это сообщение MSDN: https://social.msdn.microsoft.com/Forums/vstudio/en-US/15ec8d69-3e6f-450d-82c0-ca53e63c8f64/getting-data-of-list-object-filters-for-date-column?forum= всто

Что-то вроде этого:

for (int i = 1; i <= filters.Count; i++)
{
  FilterCache f = new FilterCache();
  f.On = filters[i].On;

  if (f.On)
  {
    f.Field = i;

    try
    {
      f.Criteria1 = filters[i].Criteria1;
    }
    catch
    {
      filters[i].Operator = XlAutoFilterOperator.xlFilterDynamic;

      f.Criteria1 = filters[i].Criteria1;
    }

    f.Operator = (int)filters[i].Operator;

    if (f.Operator == 0)
      f.Operator = (int)XlAutoFilterOperator.xlAnd;

    try
    {
      f.Criteria2 = filters[i].Criteria2;
    }
    catch { }
  }

  fs.Add(f);
}

Безуспешно. Фильтры [i] .Criteria1 теперь всегда возвращают 1, не имеет значения, какой фильтр я использую для этого столбца даты.

Чтобы смоделировать эту проблему, необходимо создать таблицу на листе Excel, а затем поместить несколько случайных дат в столбец. Затем отфильтруйте этот столбец, выбрав как минимум 3 разные даты. Запустите код.

В stackoverflow уже есть сообщение об этом: Excel VSTO - доступ к критериям массива автофильтра вызывает исключения

Также по адресу: https://social.msdn.microsoft.com/Forums/office/en-US/281fdbc5-6535-497f-b427-f69f4b092e24/excel-vsto-accessing-autofilters-array-criteria-throws-exceptions

... Но удовлетворительных ответов нет или, может быть, немного сложно понять вопрос.

FilterTemp класс:

public class FilterTemp
{
  public bool On;
  public object Field;
  public object Criteria1;
  public int Operator;
  public object Criteria2;
}

Поддерживаются ли старые двоичные файлы (xls) или версии 2007 года и выше? Ищу вариант получения данных из ExcelML

PetLahev 19.04.2018 12:04

@PetLahev, в моем случае это Excel 2010+ (.xlsx). Но я не экспортирую, я работаю напрямую с Excel в надстройке, запрограммированной с использованием VSTO.

Eric Milaneze 19.04.2018 16:43

Это нормально. Если моя идея сработает, единственное, что вам нужно будет сделать, это добавить OpenXML nuget в ваш проект - nuget.org/packages/DocumentFormat.OpenXml. Вы ориентируетесь на .NETFramework 4.5.x?

PetLahev 19.04.2018 18:45
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
1
3
356
1

Ответы 1

Итак, это просто идея. Конечная цель - вернуть дату обратно клиенту таким образом, чтобы ее можно было повторно применить через автофильтр, как здесь

string[] FilterList = new string[] { "10/31/2013", "5/4/2013" };
visibleCells.AutoFilter(1, FilterList.Length > 0 ? FilterList.ToArray() : Type.Missing, Excel.XlAutoFilterOperator.xlFilterValues, Type.Missing, true);

Надеюсь, это подскажет, как продолжить. Постараюсь когда-нибудь его доработать. Весь код с примером на GitHub

using System;
using System.Collections.Generic;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Excel = Microsoft.Office.Interop.Excel;
using System.IO;

namespace AutoFilterRetriever
{
    /// <summary>
    /// An example class how to get AutoFilter criteria from internal XML representation
    /// </summary>
    public class CriteriaFilterRetriever : IDisposable
    {
        private readonly Excel.Worksheet wks;
        private readonly string filePath;
        private Stream docStream;
        private SpreadsheetDocument openXmlDoc;
        private WorkbookPart wkb;        

        public CriteriaFilterRetriever(Excel.Worksheet sheet)
        {
            wks = sheet;
            filePath = sheet.Application.ActiveWorkbook.FullName;
            if (!filePath.Contains("\\"))
            {
                throw new FileLoadException("Save the file in order to get autofilter criteria");
            }
        }

        /// <summary>
        /// This can be changed to a complex object instead of just list of strings
        /// </summary>
        public List<string> FilterCriteria { get; private set; }

        public void GetFilterCriteria()
        {
            if (!OpenFile()) throw new FileLoadException($"Couldn't open the file - {filePath}");
            if (wks.AutoFilter == null) return;

            // here we get sheet in the workbook.xml (Equals don't work here)
            var sheetInWkb = wkb.Workbook.Descendants<Sheet>().Where(s => s.Name == wks.Name).FirstOrDefault();
            // get a reference to the worksheet part. Imagine part as the folder in the zip structure
            WorksheetPart wsPart = (WorksheetPart)(wkb.GetPartById(sheetInWkb.Id));
            // finally get the xml file e.g. sheet1.xml
            var sheet = wsPart.Worksheet;
            // there should be just one autofilter per sheet
            var filter = sheet.Descendants<AutoFilter>().First();
            if (filter == null) throw new InvalidOperationException($"Couldn't get autofilter data from the {wks.Name} sheet.");
            ManageFilterData(filter);
        }

        private void ManageFilterData(AutoFilter filter)
        {
            FilterCriteria = new List<string>();
            // this is always the first element in AutoFilter
            foreach (FilterColumn filterCol in filter)
            {
                // here we get the filters data
                var filters = filterCol.FirstChild;
                if (filters is Filters)
                {
                    foreach (var item in filters)
                    {
                        if (item is DateGroupItem)
                        {
                            FilterCriteria.Add(GetDateFilterCriteria(item as DateGroupItem));
                        }
                        else if (item is Filter)
                        {
                            FilterCriteria.Add(((Filter)item).Val);
                        }
                        else
                        {
                            throw new Exception("Not sure what to do here");
                        }
                    }
                }
                else if (filters is CustomFilters)
                {
                    // if custom filter is applied (more than one criteria it falls to this category
                    foreach (var item in filters)
                    {
                        if (item is CustomFilter)
                        {
                            var tmp = item as CustomFilter;                            
                            FilterCriteria.Add($"{tmp.Operator}, {tmp.Val}");
                        }                        
                        else
                        {
                            throw new Exception("Not sure what to do here");
                        }
                    }
                }
            }
        }

        private string GetDateFilterCriteria(DateGroupItem criteria)
        {
            if (criteria.DateTimeGrouping == DateTimeGroupingValues.Year)
            {
                return criteria.Year.ToString();
            }
            else if (criteria.DateTimeGrouping == DateTimeGroupingValues.Month)
            {
                return $"{criteria.Year.ToString()}-{criteria.Month.ToString()}";
            }
            else if (criteria.DateTimeGrouping == DateTimeGroupingValues.Day)
            {
                return $"{criteria.Year.ToString()}-{criteria.Month.ToString()}-{criteria.Day.ToString()}";
            }
            else if (criteria.DateTimeGrouping == DateTimeGroupingValues.Hour)
            {
                return $"{criteria.Year.ToString()}-{criteria.Month.ToString()}-{criteria.Day.ToString()} {criteria.Hour.ToString()}:00:00";
            }
            else if (criteria.DateTimeGrouping == DateTimeGroupingValues.Minute)
            {
                return $"{criteria.Year.ToString()}-{criteria.Month.ToString()}-{criteria.Day.ToString()} {criteria.Hour.ToString()}:{criteria.Minute.ToString()}:00";
            }
            else
            {
                return $"{criteria.Year.ToString()}-{criteria.Month.ToString()}-{criteria.Day.ToString()} " +
                       $"{criteria.Hour.ToString()}:{criteria.Minute.ToString()}:{criteria.Second.ToString()}";
            }
        }

        /// <summary> Opens the given file via the DocumentFormat package </summary>
        /// <returns></returns>
        private bool OpenFile()
        {
            try
            {
                docStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                openXmlDoc = SpreadsheetDocument.Open(docStream, false);
                wkb = openXmlDoc.WorkbookPart;                
                return true;
            }
            catch (Exception)
            {                
                return false;
            }
        }

        public void Dispose()
        {
            openXmlDoc?.Close();
            docStream?.Close();
        }

    }
}

Отличная идея, но, к сожалению, пользователь почти никогда не сохранит файл, потому что у него есть кнопка «Сохранить» для сохранения данных непосредственно в базе данных. Но в любом случае спасибо за код.

Eric Milaneze 20.04.2018 02:28

Ну, файл нужно сохранить на диск перед первым использованием, но не нужно сохранять для получения данных фильтра. Это означает, что это не сработает, если вы просто создадите новую книгу, добавите некоторые данные, примените фильтр, а затем захотите использовать код -> не будет работать. Вам нужно будет сохранить его в какой-то момент, после чего вы сможете изменить данные по своему усмотрению, и НЕ нужно сохранять, пока файл находится в файловой системе, он будет работать. Также есть возможность использовать SaveCopyAs. В целом, я не думаю, что у вас здесь слишком много вариантов, объектная модель Excel просто не готова для критериев мультифильтрации.

PetLahev 20.04.2018 08:41

Я согласен с вами в том, что у меня заканчиваются варианты, но файлы почти никогда не будут сохраняться, и почти все время несколько пользователей будут использовать один и тот же файл «.xlsx». И самое главное: это не обязательно .xlsx. Иногда это будет «.xls». Опять же, я ценю вашу помощь. Большое спасибо!

Eric Milaneze 20.04.2018 15:00

Ах, я вижу, что xls будет препятствием, проблема с сохранением может быть решена

PetLahev 20.04.2018 16:41

Другие вопросы по теме