Работа с Excel из C# через COM Interop, часть 1, открываем документ и выводим данные

Задача: вывести данные в таблицу Excel с красивым оформлением и открыть ее, чтобы пользователь мог напечатать или сохранить средствами самого Excel. Необходимо поддерживать все версии Office, начиная с 97, то есть вывод в новые xml-форматы Office 2007 и выше невозможен

Самая сложная часть - работа с оформлением, так как во-первых интерфейс Excel крайне неочевиден, запутан и плохо документирован, во-вторых оформление Excel таблицы из кода работает достаточно медленно. В связи с этим в большинстве случаев подойдет вариант с использованием шаблонов .xlt. Так как мы выводим файл для просмотра и печати, без расчета на сложные вычисления, диаграммы и прочие прелести жизни, мы можем привести формат всех ячеек к тексту и забыть про различные типы данных.

Таким образом задача сводится к следующему:

1) Подготовить файлы шаблонов для разных типов выборок (шапка, размер шрифта, перенос по словам, ширина столбцов)
2) Взять данные из программы и вывести их в нужное место шаблона (как правило это прямоугольный массив ячеек соответствующий DataTable)
3) Подключить к проекту dll-библиотеку Excel Microsoft.Office.Interop.Excel в раздел references и проставить параметр копирования библиотеки к скомпилированным файлами CopyLocal в True.
4) Написать класс для работы с Excel и вывести через него данные из таблицы

При такой постановки задачи "Но это же неэстетично! - Зато дешево, надежно и практично" сам класс работы с Excel получается достаточно простым, но превосходно выполняет поставленную задачу

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;

namespace TestProject
{
    // Класс  документа Excel скрывает подробности работы с эксел, требует добавления в References библиотеки Microsoft.Office.Interop.Excel
    public class ExcelDocument
    {
        private Excel.Application _application = null; 
        private Excel.Workbook _workBook = null;
        private Excel.Worksheet _workSheet = null;
        private object _missingObj = System.Reflection.Missing.Value;

        //КОНСТРУКТОР
        public ExcelDocument()
        {
            _application = new Excel.ApplicationClass();
            _workBook = _application.Workbooks.Add(_missingObj);
            _workSheet = (Excel.Worksheet)_workBook.Worksheets.get_Item(1);
        }

        public ExcelDocument(string pathToTemplate)
        {
            object pathToTemplateObj = pathToTemplate;

            _application = new Excel.ApplicationClass();
            _workBook = _application.Workbooks.Add(pathToTemplateObj);
            _workSheet = (Excel.Worksheet)_workBook.Worksheets.get_Item(1);
        }

        // ВИДИМОСТЬ ДОКУМЕНТА
        public bool Visible
        {
            get
            {
                return _application.Visible;
             }
            set
            {
                _application.Visible = value;             
            }
        }

        // ВСТАВКА ЗНАЧЕНИЯ В ЯЧЕЙКУ
        public void SetCellValue(string cellValue, int rowIndex, int columnIndex)
        {
            _workSheet.Cells[rowIndex, columnIndex] = cellValue;
        }

        public void Close()
        {
            _workBook.Close(false, _missingObj, _missingObj);

            _application.Quit();

            System.Runtime.InteropServices.Marshal. ReleaseComObject(_application);
           
            _application = null;
            _workBook = null;
            _workSheet = null;

            System.GC.Collect();
        }
    }
}


У класса есть одна особенность - он работает с неуправляемым ресурсом - приложением Excel. То есть мы создаем невидимый процесс Excel, заполняем его данными из шаблона и программ и только после этого отображаем на экран. В случае ошибок никакой сборщик мусора не приберет за нами грязь, так что закрытие документа в случае ошибки придется делать в ручную. Собственноручное изменение деструктора тоже работает весьма своеобразно с COM-интерфейсом.

В простейшем случае получим код вида


ExcelDocument excelDoc = new ExcelDocument(Application.StartupPath + "\\" + "templateFileName.xlt");
int rowIndex = 1; 
try
{
    // так и просится в отдельный метод
    foreach (DataRow currRow in someDataTable.Rows)
    {
        string valueCol1 = currRow["COLUMN1"].ToString();
        string valueCol2 = currRow["COLUMN2"].ToString();
        excelDoc.SetCellValue(valueCol1, rowIndex, 1);
        excelDoc.SetCellValue(valueCol2, rowIndex, 2);
        rowIndex++;
    }
}
catch(Exception error)
{
excelDoc.Close();
// обрабатываем саму ошибку
}
excelDoc.Visible = true;

Само собой ничто не мешает в дальнейшем расширить этот класс методами для работы с оформлением, вроде

public void SetColumnWidth(int columnIndex, int colWidth)
{
            ((Excel.Range)_workSheet.Columns[columnIndex, Type.Missing]).EntireColumn.ColumnWidth = colWidth;
}

Такой метод вряд ли подойдет для больших и сложных корпоративных систем, разрабатываемых высокооплачиваемыми профессионалами, но может спасти программиста-новичка в случае требования начальства "сделать ВЧЕРА вывод отчета в excel в соответствии с требования клиента по оформлению".

Excel

Комментарии

Работа с Excel из C# через COM Interop, часть 1, открываем документ и выводим данные — Комментарии (5)

  1. Не надо сгущать краски. Работать с Excel также просто (или сложно), как и с чем-то другим. Разные варианты загрузки данных из Excel с учетом преобразования данных требуют разное операционное время, поэтому нужно выбирать нужный для конкретного случая.

  2. Я, наверно, буду голосом большинства когда спрошу. ГДЕ ССЫЛКА НА ВТОРУЮ ЧАСТЬ ?

  3. Необходимо поддерживать все версии Office, начиная с 97, то есть вывод в новые xml-форматы Office 2007 и выше невозможен
    И где же решение данной части задачи?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>