Winforms таблица как в excel
Перейти к содержимому

Winforms таблица как в excel

  • автор:

Старт работы с Excel на C#

В современном мире разработки приложений нередко встает необходимость работы с Excel документами. Чаще всего это разного рода отчеты, но иногда xls/x файлы используются в качестве хранилища данных. Например, если пользователь должен иметь возможность загрузить данные в приложение или выгрузить, в человеко-читаемом виде, Excel де-факто является стандартом. Относительно дружелюбный интерфейс, прозрачная структура, в купе с его распространенностью. трудно навскидку назвать решение лучше.

Однако, у многих Excel до сих пор ассоциируется с чем-то тяжелым, неповоротливым и сложным. Давайте посмотрим, как мы — обычные C# разработчики, можем легко сформировать простой Excel документ, на примере табличного отчета.

Историческая справка

Времена, когда доминировал проприетарный формат .xls(Excel Binary File Format) давно прошли и сейчас мы имеем только .xlsx(Excel Workbook), в рамках Office Open XML. Последний представляет собой обычный .zip архив с XML файлами. Не будем углубляться в его структуру, я искренне надеюсь что вам это никогда не понадобится.

На github, и не только, можно найти ряд библиотек, бесплатных и не только. Пожалуй самой популярной является EPPlus. До определенной степени, она довольно хорошо отражает концепцию Excel, именно по этому я всегда использую EPPlus. Версия 4 полностью бесплатна, начиная с 5‐й версии вам потребуется приобрести лицензию для коммерческого использования.

Задача

Итак, предположим, продукт-мэнеджеру ударила в голову идея того, что возможность выгружать некий отчет в формате Excel увеличит кол-во пользователей на 100500%. Проджет-менеджер решает выкатить эту киллер-фичу как хотфикс прямо сегодня — ведь работы всего на пару часов.

Сам по себе, отчет содержит краткое описание компании и историю изменения некоторых экономических показателей. Для простоты все свойства компании — строки. Экономические показатели — большие целые числа и числа с плавающей точкой, а также даты. Предположим, что где-то в недрах микросервисного backend-да есть сервис-генератор подобных отчетов, например по id компании. Однако, поскольку id нет смысла выводить пользователю, идентификатор отсутствует в самой модели отчета.

Аналитик, в свою очередь, выдает задачу с феноменально точным описанием — «Сгенерировать excel отчет на базе данных MarketReport». Что ж, для нашего примера, создадим заглушку — генератор фейковых данных:

Первый запуск

Подключим EPPlus версии 4.5.3.3 и создадим базовую обвязку для будущего генератора.

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

В методе main создается генератор отчетов, а также генератор Excel файлов. Далее полученный файл просто записывается на диск.

При попытке запустить приложение, получаем exception: InvalidOperationException: The workbook must contain at least one worksheet

Все правильно, Excel документ не может существовать без страниц, должна быть хотя бы одна. Добавляем ее, все интуитивно понятно:

var sheet = package.Workbook.Worksheets .Add("Market Report");

Запускаем снова и. вот оно! Теперь наше приложение генерирует документ и, хотя там еще ничего нет, он уже весит 2,5KB — значит мы работаем с Excel правильно и все идет как надо.

Вывод данных

Давайте выведем основную информацию по компании в шапку. Для доступа к конкретной ячейки объект Cells на странице пакета снабжен удобным индексатором. При этом, до конкретной ячейки можно достучаться как через номер строки и столбца, так и по привычному всем буквенно-числовому коду:

sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name;

Полный код вывода шапки.

sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name; sheet.Cells["B3"].Value = "Location:"; sheet.Cells["C3"].Value = $", " + $", " + $""; sheet.Cells["B4"].Value = "Sector:"; sheet.Cells["C4"].Value = report.Company.Sector; sheet.Cells["B5"].Value = report.Company.Description;

Для вывода исторических данных понадобится как минимум шапка таблицы и цикл по массиву History:

sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][] < new []>); var row = 9; var column = 2; foreach (var item in report.History)

Предлагаю обратить внимание на метод LoadFromArrays, который заполняет диапазон ячеек рваным(зубчатым) массивом. Здесь мы можем видеть, что типизация теряется и передавая массив object мы ожидаем что EPPlus в конечном итоге использует ToString, чтобы записать переданное в ячейки.

Стилизация

Если вы прямо сейчас откроете документ, то вы возможно увидите не то, что хотелось бы отдать в продакшн в пятницу вечером.

Как это выглядит

Во-первых, шапка никак не выделяется, во-вторых таблица не имеет границ. выравнивание пляшет, даты отображаются магическими числами, а капитализация «уходит в какую-то математику» — как это прокомментировал аналитик.

Да, на все эти красивости у нас уйдет больше года кода, чем на сам вывод данных, и, в конечном тоге, получившаяся каша из логики вывода данных и разметки заставит некоторых усомниться в их компетентности. но, мы же backend разработчики, так давайте сверстаем Excel Sheet!

Размер ячеек

Из коробки у нас есть возможность сделать автофит а так же вручную выставить ширину в соответствии с нашей ситуацией. А ситуация у нас не самая хорошая — по задумке аналитика в шапке у ячеек должен быть автофит, а у ячеек таблицы — тоже автофит. Так в чем же подвох?

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

sheet.Cells[1, 1, row, column + 2].AutoFitColumns(); sheet.Column(2).Width = 14; sheet.Column(3).Width = 12;
Формат данных

Как и большая часть стиля ячейки, он задается через одноименное свойство Style. Обратите внимание на вычисление 3-го аргумента индексатора. Это звоночек некачественного кода, но к этому мы вернемся в позже.

sheet.Cells[9, 4, 9 + report.History.Length, 4].Style.Numberformat.Format = "yyyy"; sheet.Cells[9, 2, 9 + report.History.Length, 2].Style.Numberformat.Format = "### ### ### ##0";
Выравнивание

Его можно задать как на ячейке, так и на диапазоне. На самом деле, для EPPlus, это одна и та же сущность — некий ExcelRange, описывающий диапазон ячеек, в том числе и со всего 1 ячейкой.

sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
Стиль текста

Также легко задается, используя Style.Font, кстати, здесь, на 2-й строчке, мы впервые указываем диапазон так, как привыкли его видеть пользователи Excel:

sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true; sheet.Cells["B2:C4"].Style.Font.Bold = true;
Границы

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

sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double); sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
График

«Ну что за отчет без графиков, верно, Карл?» — ловко подметит специалист по тестированию, и не важно, что этого не было в ТЗ а на часах уже половина 9-го.

Хотя график как сущность сам по себе сложнее таблиц и с графиками мы не работаем каждый день, EPPlus предоставляет довольно понятный API. Давайте добавим простейший график, отражающий рост капитализации:

var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line); capitalizationChart.Title.Text = "Capitalization"; capitalizationChart.SetPosition(7, 0, 5, 0); capitalizationChart.SetSize(800, 400); var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"])); capitalizationData.Header = report.Company.Currency;

Еще, может понадобиться защитить страницу от редактирования:

sheet.Protection.IsProtected = true;

На этом все, репозиторий с рабочим приложением находится здесь.

Заключение

О чем говорит финальная версия метода Generate?

public byte[] Generate(MarketReport report) < var package = new ExcelPackage(); var sheet = package.Workbook.Worksheets .Add("Market Report"); sheet.Cells["B2"].Value = "Company:"; sheet.Cells[2, 3].Value = report.Company.Name; sheet.Cells["B3"].Value = "Location:"; sheet.Cells["C3"].Value = $", " + $", " + $""; sheet.Cells["B4"].Value = "Sector:"; sheet.Cells["C4"].Value = report.Company.Sector; sheet.Cells["B5"].Value = report.Company.Description; sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][] < new []>); var row = 9; var column = 2; foreach (var item in report.History) < sheet.Cells[row, column].Value = item.Capitalization; sheet.Cells[row, column + 1].Value = item.SharePrice; sheet.Cells[row, column + 2].Value = item.Date; row++; >sheet.Cells[1, 1, row, column + 2].AutoFitColumns(); sheet.Column(2).Width = 14; sheet.Column(3).Width = 12; sheet.Cells[9, 4, 9+ report.History.Length, 4].Style.Numberformat.Format = "yyyy"; sheet.Cells[9, 2, 9+ report.History.Length, 2].Style.Numberformat.Format = "### ### ### ##0"; sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; sheet.Column(4).Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true; sheet.Cells["B2:C4"].Style.Font.Bold = true; sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double); sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin; var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line); capitalizationChart.Title.Text = "Capitalization"; capitalizationChart.SetPosition(7, 0, 5, 0); capitalizationChart.SetSize(800, 400); var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"])); capitalizationData.Header = report.Company.Currency; sheet.Protection.IsProtected = true; return package.GetAsByteArray(); >

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

Во-вторых, возможно имеет смысл искать новою работу, но, забегая вперед, я бы с этим не спешил. Если данная публикация наберет 1+ просмотров, то во второй части мы поговорим о том, как можно отделить стилизацию от логики заполнения данными, упростить манипуляции над ячейками и в целом сделаем код боле поддерживаемым.

Экспорт dataGridView в Excel

Добрый день.
Пытаюсь сделать экспорт таблицы в эксель. Вроде бы и ссылку подключила using xcelapp = Microsoft.Office.Interop.Excel;
И код вроде бы правильный, но экспорт все равно не осуществляется, появляется ошибка. Помогите, пожалуйста.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Microsoft.Office.Interop.Excel.Application xcelapp = new Microsoft.Office.Interop.Excel.Application(); xcelapp.Application.Workbooks.Add(Type.Missing); for (int i=1; i  товарDataGridView.Columns.Count +1; i++) { xcelapp.Cells[1, i] = товарDataGridView.Columns[i - 1].HeaderText; } for (int i=0; iтоварDataGridView.Rows.Count; i++) { for (int j=0; jтоварDataGridView.Columns.Count; j++) { xcelapp.Cells[i + 2, j + 1] = товарDataGridView.Rows[i].Cells[j].Value.ToString(); } } xcelapp.Columns.AutoFit(); xcelapp.Visible = true;

И еще вопрос, как сделать выборочно отчет по столбцам?

Как отобразить таблицу Excel в WinForm?

Рекомендую использовать сторонние библиотеки, поддерживающие формат xlsx. Использовать Interop не рекомендую (в основе его лежит медленный COM и требуется наличие MS Office).

Использовал библиотеку EPPlus. Она бесплатна для некоммерческого использования. Позволяет читать ячейки, а можно загрузить диапазон ячеек в DataTable.

Добавьте nuget пакет EPPlus в ваше приложение, а далее используйте вышеуказанные примеры кода для чтения Excel.

Как создать отчет с таблицей из собственного кода

Как программно создать отчет с таблицей из собственного кода

Вспомним довольно частую ситуацию, когда вам нужно сделать что-либо очень быстро и буквально “на коленках”. Но не будем забывать, что структура и содержание отчета зависит от внешних факторов. Генератор отчетов FastReport.NET очень гибкий продукт и дает вам два способа решения этой проблемы.

Способ 1. Создавать структуру отчета в коде пользовательского приложения.
Способ 2. Управлять поведением отчета внутри, с помощью скрипта.

Есть довольно много информации о том, как реализовывать в скрипте отчета те или иные особенности поведения. Но довольно мало встречается примеров создания отчета из кода. Поэтому в этой статье мы создадим отчет с таблицей из кода приложения, а также рассмотрим, как заполнять таблицу данными и поместим объект в ячейку таблицы. О способе 2 подробнее будет написано в другой статье.

Объект таблица (Table) может быть заполнен статическими данными, или динамическими. В первом случае мы знаем конкретные размеры таблицы и сразу же вносим данные в ячейки из фиксированного набора.

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

Давайте создадим WinForms приложение. Подключим в ссылках библиотеку FastReport.dll, которую вы можете найти в папке с установленным генератором отчетов. Добавим на форму кнопку, которая будет запускать построение отчета. И не забываем создать для нее обработчик нажатия.

Прежде всего подключаем библиотеки FastReport.

using FastReport; using FastReport.Data; using FastReport.Table; using FastReport.Utils; private void button1_Click(object sender, EventArgs e)  using (Report report = new Report())  //Создаем объект отчета  ReportPage page = new ReportPage();  //Создаем объект страницы отчета page.Name = "Page1";  //Задаем имя страницы report.Pages.Add(page);  //Добавляем страницу в коллекцию страниц отчета DataSet ds = new DataSet();  //Создаем источник данных ds.ReadXml("~/../../../App_Data/nwind.xml");  //Загружаем в него базу данных xml report.RegisterData(ds);  //Регистрируем источник данных в отчете report.GetDataSource("Products").Enabled = true;  //Включаем источник данных  DataBand dataBand = new DataBand();  //Создаем бэнд с данными dataBand.Name = "DataBand";  //Задаем имя бэнда page.Bands.Add(dataBand);  //Добавляем бэнд в коллекцию бэндов страницы TableObject table = new TableObject();  //Создаем объект таблицы table.Name = "Table1";  //Задаем имя объекта  table.RowCount = 10;  //Задаем количество строк  table.ColumnCount = 2;  //Задаем количество колонок //В цикле заполняем все ячейки какими-либо данными for (int i = 0; i  10; i++) for (int j = 0; j  2; j++)  table[j, i].Text = (10 * i + j + 1).ToString(); table[j, i].Border.Lines = BorderLines.All; > dataBand.Objects.Add(table); //dataBand.Objects.Add(picture); if (report.Prepare()) report.ShowPrepared(); >

Таблица, заполненная произвольными данными

А теперь рассмотрим случай, когда таблицу нужно заполнить данными из источника. Заменим цикл выше на другой код:

table.Columns[0].AutoSize = true; //table.Columns[1].AutoSize = true; DataSourceBase data = report.GetDataSource("Products"); data.Init();  //Инициализируем источник данных data.First();  //Получаем первую запись for (int i = 0; i  10; i++)  table[0, i].Text = data["ProductName"].ToString(); table[0, i].Border.Lines = BorderLines.All;  // Задаем рамки table[1, i].Text = data["UnitPrice"].ToString(); table[1, i].Border.Lines = BorderLines.All; data.Next(); >

В результате мы получим таблицу, с данными из базы:

Таблица, заполненная данными из базы

Без заголовка таблица выглядит неполноценно. Давайте добавим его:

table.RowCount = 11;  table[0, 0].Text ="Product Name"; table[0, 0].Border.Lines = BorderLines.All; table[1, 0].Text = "Unit Price"; table[1, 0].Border.Lines = BorderLines.All; for (int i = 1; i  10; i++)  table[0, i].Text = data["ProductName"].ToString(); table[0, i].Border.Lines = BorderLines.All; table[1, i].Text = data["UnitPrice"].ToString(); table[1, i].Border.Lines = BorderLines.All; data.Next(); >

Уточню, что в первой строке таблицы мы задали заголовки, значит цикл начинаем не с первого, а со второго элемента.

Таблица с заголовками

Рассмотрим последний на сегодня случай — как поместить объект в ячейку таблицы. Например, картинку:

PictureObject picture = new PictureObject(); //Создаем объект картинки picture.Bounds = new RectangleF(40, 0, Units.Centimeters * 0.5f, Units.Centimeters * 0.5f);
//Задаем размер объекта
picture.CreateUniqueName(); //Задаем произвольное имя picture.Image = Image.FromFile("C:/Users/FR/Downloads/28.png"); //Задаем путь к изображению picture.LoadImage(); //Загружаем изображение picture.Parent = table[1, 1]; //Задаем родительский объект для изображения

Именно свойство Parent влияет на то, что картинка будет отображена в ячейке. Давайте посмотрим, как она будет выглядеть:

Таблица с картинкой в ячейке

Таким образом, мы рассмотрели способ создания таблицы в отчете из кода приложения и два варианта её заполнения — статическим данными и динамическими. Кроме того, теперь вы знаете о возможностях программного добавления объекта в ячейку таблицы.

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

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