Страницы: 1
RSS
Excel в C#: кастинг SpecialCells в массив
 
Здравствуйте, товарищи! Бью к вам челом!  :)   Вопрос к тем, кто программировал/программирует Excel в C# (.NET Framework).

В общем, проблема такого характера. Имеется некий диапазон. Фильтрую его. Мне нужно получить этот отфильтрованный диапазон и пихнуть его в массив. В VBA я делаю так:
Код
Sub F1()  
    Dim rng As Range, arr As Variant
    Set rng = Sheets(1).Range("AS5:AS6094").SpecialCells(xlCellTypeVisible) 
    arr = rng.Value
End Sub"
Это в VBA. А вот аналогичный метод в C#:
Код
using Excel = Microsoft.Office.Interop.Excel; 
 
...... 
 
private void LoadExcelFile()
{
   // Выбираем файл и открываем его
   var dialog = new Microsoft.Win32.OpenFileDialog { InitialDirectory = Directory.GetCurrentDirectory() };
   if (!dialog.ShowDialog(this).Value) return;
   var excelFile = dialog.FileName;
   Excel.Application xlApp = new Excel.Application() { Visible = true };
   Excel.Workbook book = xlApp.Workbooks.Open(excelFile);
   Excel.Worksheet sheet = book.Sheets[1];

   // Убираем автофильтр
   if (sheet.AutoFilter != null) sheet.AutoFilterMode = false;

   // Фильтруем
   sheet.Range["A5:BA5"].AutoFilter(Field: 25, Criteria1: "31.07.2014");

   // Получаем фильтрованный диапазон
   var rng1 = sheet.Range["AS5", "AS6094"].SpecialCells(Excel.XlCellType.xlCellTypeVisible);

   // Получаем весь диапазон
   var rng2 = sheet.Range["AS5", "AS6094"];

   // Значение диапазона (ожидаем массив)
   dynamic val1 = rng1.Value;
   dynamic val2 = rng2.Value;

   MessageBox.Show(val1.GetType().Name);  // Возвращает: String <= Ожидался массив Object[,]
   MessageBox.Show(val2.GetType().Name);  // Возвращает: Object[,] <= Правильно

   book.Close();
   xlApp.Quit();
   System.Runtime.InteropServices.Marshal.ReleaseComObject(book);
   System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
}
Как видно, диапазон, полученный через SpecialCells, даёт нам String, а не массив. Кто-нибудь знает, как решить эту проблему?  :(
There is no knowledge that is not power
 
Ничче не понимаю ни в C#, ни тем более в .Net, но даже в VBA вот это выражение
Код
Set rng = Sheets(1).Range("AS5:AS6094").SpecialCells(xlCellTypeVisible) 
формирует Range из нескольких Areas, если в заданном диапазоне есть скрытые ячейки
Выражение arr=rng.Value передает в массив только первую область, и если первая область будет представлять собой 1 ячейку, выражение
Код
arr = rng.Value
будет строковым или числовым, но не массивом. Поэтому предлагаю так:
Код
Sub get_filtered()
Dim a, rng1 As Range, rng1u As Range, i%
Set rng1 = Range("A1:A6").SpecialCells(xlCellTypeVisible)
Set rng1u = rng1.Areas(1)
If rng1.Areas.Count > 1 Then
    For i = 2 To rng1.Areas.Count
        Set rng1u = Union(rng1.Areas(i), rng1u)
    Next
End If
If rng1u.Count > 1 Then a = rng1u Else: ReDim a(0): a(0) = rng1u
End Sub
может быть, есть более простые способы - я не знаю.
F1 творит чудеса
 
Цитата
Максим Зеленский пишет: нескольких Areas,
Ах, ну да... Пресловутые Areas!!!! Всё верно. Просто хотел загнать этот диапазон в массив одной строкой, чтобы передать его в коллекции .NET Framework'а. Спасибо за напоминание.  :)  А ведь самое главное, что у меня была мысль по поводу этих Areas.  :D
There is no knowledge that is not power
 
Johny, если собрались дальше продвигаться в разработке на C# под Excel учитывайте момент:  когда получаете массив указанным выше способом, то значения ошибок выглядят не как в VBA, а в виде их числовых констант. Это может затруднить их распознавание в дальнейшем...
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Цитата
The_Prist пишет: Это может затруднить их распознавание в дальнейшем...
В данном конкретном случае проблем с ошибкой не было - компилятор сказал честно, что "не может преобразовать String в Object[,]". А так вообще да - описания ошибки как такового во многих случаях нет. Я как раз и думаю - стоит ли делать проект в C# или в VBA. Просто у C# огромные возможности (например, параллельное и асинхронное выполнение процедур, ну и много ещё чего). Пока только начал делать. Вот и думаю - не вернуться ли в VBA, пока не поздно.  :D
There is no knowledge that is not power
 
Нет, я про другие ошибки. Если внутри ячейки функция и результат функции ошибки(скажем #Н/Д) - C# не увидит именно #Н/Д. Для него это будет числовой код ошибки -2146826246. Именно это затрудняет далее обработку данных, помещенных в массив из C#. Поэкспериментируйте.
Что же до исключений - в C# довольно мощная справка, но т.к. идет обработка взаимодействия с COM - то тут описания, мягко говоря, хромают в некоторых случаях. Чаще просто общее описание, вроде - Нельзя присвоить тип объекту COM.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Какой тогда совет? :) Может, есть свой опыт или опыт знакомых работы с Excel в C#? :)
There is no knowledge that is not power
 
Совет был один - учитывать данное обстоятельство :-) Опыт был и выкручивался при помощи объекта Errors ячеек, коллекций и массивов.
Однако если есть уверенность, что ошибок нет или это несущественно - то и смысла это отслеживать нет.

И еще: при обращении к Excel из C# тратится довольно много времени именно на обращение к объектам Excel(в VBA в большинстве случаев это происходит в разы быстрее).
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Спасибо за советы! :)
There is no knowledge that is not power
 
Цитата
Johny пишет: Может, есть свой опыт или опыт знакомых работы с Excel в C#?
Посмотрите еще в сторону SpreadsheetDocument Class Можно работать с файлами xlsx без Excel, что иногда гораздо удобнее и быстрее.
 
pharmaprofi, есть на мой взгляд небольшая ложка дегтя - с файлами формата .xls не работает.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Это да. В одном из своих проектов, файлы .xls читаю так:
Код
OleDbCommand excelCommand = new OleDbCommand(); OleDbDataAdapter excelDataAdapter = new OleDbDataAdapter();
            string excelConnStr = string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=""Excel 8.0;HDR=No;IMEX=1;""", filename);
            OleDbConnection excelConn = new OleDbConnection(excelConnStr);

            excelConn.Open();
            var sheets = excelConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });
            using (var cmd = excelConn.CreateCommand())
            {
                cmd.CommandText = "SELECT * FROM [" + sheets.Rows[0]["TABLE_NAME"].ToString() + "] ";

                var adapter = new OleDbDataAdapter(cmd);

                adapter.Fill(ds);
            }
            excelConn.Close();
т.е. использую Oledb. ИМХО, если нужно только прочитать данные, лучше обойтись без запуска приложения Excel.
 
Это смотря какие данные прочитать надо :-) Если надо формулы и форматы вдобавок к прочим - то увы...А так да - запрос, естественно, быстрее.
Все от задач зависит.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Цитата
pharmaprofi пишет:
Посмотрите еще в сторону SpreadsheetDocument Class
Да, я про это уже давно знаю (Open XML SDK), но дело в том, что это слижком "низкоуровнево". Я сам пытался написать с нуля благодаря "родной" поддержке ZipArchive, начиная с .NET Framework 4.5, и классу XElement, причём VB.NET вообще идеально подходит для такой вещи, так как в нём есть XML Literals, чего нет в C#. Вся проблема в том, что очень спецификация по формату очень большая, и в ней сложновато разобраться. Например, Эксель все строковые данные, в целях оптимизации, хранит в специальном файле sharedStrings.xml, и ячейка содержит индекс к этому файлу. Но чтобы определить, что в ячейке индекс, а не само значение, у ячейки (элемент <c> ;)  есть (ЕМНИП) атрибут "t". Если он равен "s", то это индекс.  :)  Ячейка ведь может хранить и формулы. Для неё есть тоже свой тег. В общем, содержимое ячейки определяется атритубами в <c>. Вот такие хитросплетения. Но когда я коснулся форматирования ячеек, то я просто ужаснулся от этого - такой себе лабиринтик.  :)  Впрочем, если писать для себя и если нужно выдернуть чисто значения, то ZipArchive + XElement (+VB.NET) прекрасно подходит для этого.  :)  Кому интересно, спецификация ECMA-376 в помощь. :)
There is no knowledge that is not power
 
Плюс еще момент: таким методом нельзя считать данные с открытой уже книги(не пересохраняя её). А если делать некую надстройку работы с файлами Excel - это тоже может стать препятствием. Может я ошибаюсь и есть-таки метод считывания данных схем с открытого уже в Excel файла, но я не нашел.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Цитата
The_Prist пишет:
таким методом нельзя считать данные с открытой уже книги
А что за такая необходимость? И почему "не пересохраняя"? Что это за случаи такие?  :D  
В принципе, если посидеть усиленно, то в спецификации можно разобраться. Но! Ведь надо ещё всё это хозяйство соотнести к объектам. Логику можно взять прямо из объектной модели VBA. Например, класс Sheet - общий для всех листов (Worksheet, Chartsheet). У него по спецификации будут общие три (ЕМНИП) атрибута - Id, SheetId и Name. И вот тут вопрос, как лучше сделать - через интерфейс или через общий класс?

Вариант 1 - через абстрактный общий класс.
Код
abstract class Sheet
{
   internal int Id { get; set; }
   internal int SheetId { get; set; }
   internal string Name { get; set; }
}

class Worksheet : Sheet { }

class Chartsheet : Sheet {}
Вариант 2 - через интерфейс.

Код
interface ISheet
{
   int Id { get; set; }
   int SheetId { get; set; }
   string Name { get; set; }
}

class Worksheet : ISheet
{
   public int Id
   {
      get {...}
      set {...}
   }

   public string Name
   {
      get {...}
      set {...}
   }

   public int SheetId
   {
      get {...}
      set {...}
   }
}

class Chartsheet : ISheet
{
   public int Id
   {
      get {...}
      set {...}
   }

   public string Name
   {
      get {...}
      set { }
   }

   public int SheetId
   {
      get {...}
      set {...}
   }
}
There is no knowledge that is not power
 
Цитата
Johny пишет:
А что за такая необходимость? И почему "не пересохраняя"? Что это за случаи такие?
Представим, что мы собрались создать надстройку COM, которая должна менять свойства ячеек или на основании свойств ячеек производить еще какие-либо действия с другими ячейками, которые изменяют в свою очередь так же какие-либо свойства(да хоть просто значение новое задать).
Плюс мы планируем внедрить в надстройку функции пользователя - вот где точно ни о каком сохранении даже копий книги быть не может.
Надеюсь, достаточно точно пояснил случаи, когда пересохранение точно не поможет.

По поводу трех параметров: я полагаю лучше через интерфейс, т.к. в принципе для C# интерфейсы и предназначены как раз для подобных целей. Вроде бы :-)
Я сам пока не особо в C# углублялся.

А вообще поищите в сети NetOffice - там уже все реализовано и коды можно открытые скачать.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Цитата
The_Prist пишет: я полагаю лучше через интерфейс
Вся проблема в том, что логику присвоения этих трёх свойств надо прописывать столько раз, сколько классов (отдельно для Worksheet и отдельно для Chartsheet), в то время как если использовать абстрактный класс Sheet, то всю логику можно пихнуть туда. Тем более если что-то надо поменять, то понадобятся изменения только в одном классе - Sheet, а в случае интерфейса - во всех классах, которые применяют интерфейс ISheet.
There is no knowledge that is not power
Страницы: 1
Наверх