Snap — новая платформа для создания отчетов. Часть 2

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

e1bbe5ea0c4111bc30bd9102dfe8dae1.png

Итак, под катом вас ждет обещанная занимательная механика.

Подготовка приложенияДля начала создадим в Visual Studio новое Windows Forms приложение и перетащим на главную форму SnapControl из вкладки DX14.1: Reporting, которая добавится в тулбокс после установки Snap.be809cbaba9199ab3755ee17d374f875.png

Теперь добавим меню, используя соответствующие пункты в списке, появляющемся при щелчке мышью по смарт-тегу контрола.

0aed43ba1786a958ed3c80eb57cbf9d8.png

3cf04f17207f0d484dab8625905e78d2.png

Все, скелет готов, теперь будем наращивать мышцы.

ae43656fec47955f4ffc986cb50a6c41.png

Привязка данных Как я уже упоминал, начинать работу имеет смысл с обеспечения доступа к данным. Snap поддерживает несколько сценариев назначения источника данных. Самым очевидным является использование пользовательского интерфейса. Источники данных, добавленные через UI, присваиваются конкретному документу, но их можно будет сохранить и для других отчетов. Пользователь может самостоятельно настроить подключение, используя мастер, который можно вызвать, выбрав команду Add New Data Source на вкладке File: 978e97fa4614ebda70bfed189524b2ad.png

Или же можно выбрать пункт Add Data Source в контекстном меню, открываемом щелчком правой кнопкой мыши по пустому месту в Data Explorer:

0d2c211053d5c7ccffb6282aecfe13a7.png

Мастер подготовки источников данных поддерживает внушительный список возможных поставщиков:

Microsoft SQL Server Microsoft Access 97 Microsoft Access 2007 Oracle XML File SAP Sybase Advantage SAP Sybase ASE IBM DB2 Firebird MySQL Pervasive PSQL PostgreSQL VistaDB Microsoft SQL Server CE SQLite Но не стоит забывать, что в зависимости от выбранного поставщика может понадобиться настроить различные параметры соединения — тип идентификации, имя базы данных и т.д. Поскольку для рядового пользователя все это представляется весьма эзотерическим знанием, имеет смысл настроить систему «под ключ».При этом необходимо учитывать, что Snap разделяет источники данных уровня конкретного отчета и более глобальные данные уровня приложения, которые доступны для любого открытого в нем документа. Данные уровня приложения задаются через свойства SnapControl — DataSource — для главного источника данных, используемого по умолчанию, и DataSources — коллекция именованных источников данных. В качестве источника данных могут использоваться стандартные поставщики .Net, списки и XML файлы.

snapControl1.DataSource = dataSet1; snapControl1.DataSources.Add (new DataSourceInfo («NWindDataSource2», dataSet2)); Теперь при запуске приложения Data Explorer будет отображать доступные источники данных.7c80ef03b3617e1c3393b29f14ce5149.png

Чтобы задать источники данных для конкретного отчета используется аналогичная пара свойств документа.

snapControl1.Document.BeginUpdateDataSource (); this.snapControl1.Document.DataSources.Add (new DataSourceInfo («Cars», e1List)); this.snapControl1.Document.DataSources.Add (new DataSourceInfo («Company», e2List)); snapControl1.Document.EndUpdateDataSource (); Если по какой-либо причине не удалось установить соединение с источником данных, вызовется событие SnapDocument.ConnectionError. Его обработка позволяет переопределить стандартное поведение, заключающееся в вызове мастера установки соединения для повторного запроса параметров. void Document_ConnectionError (object sender, DevExpress.DataAccess.ConnectionErrorEventArgs e) { Access2007ConnectionParameters parameters = (Access2007ConnectionParameters)e.ConnectionParameters; string path = «C:\\Public\\Documents\\DevExpress Demos 14.1\\Components\\Data\\nwind.mdb»; parameters.FileName = path; parameters.Password = «masterkey»; } API Теперь можно приступать непосредственно к созданию отчета. Как и многие другие свои способности, механизм добавления динамического содержимого Snap унаследовал от Rich Text Editor. В качестве шаблона, который заполняется реальными данными, Snap использует поля (fields). В большинстве случаев проще позволить Snap добавить поля автоматически. Если известен код поля, его можно ввести в нужное место документа самостоятельно. Достаточно нажать клавиши Ctrl+F9 и ввести код между фигурными скобками. А можно воспользоваться API и полностью создать документ программно.Основным полем, используемым для объединения различных элементов разметки в единую модель, на основе которой будет создана заполненная данными часть документа, является SnapList. SnapList имеет иерархическую структуру и разделяется на несколько частей, каждая из которых используется для задания различных элементов списка: хэдера, шаблона для каждой строчки и футера. Эти части могут содержать вложенные поля, формируя древовидную структуру, предоставляющую возможность создавать сложные master-detail отчеты. С помощью следующего кода можно добавить SnapList в документ и настроить его, определив заголовок списка и образец для каждой записи:

void GenerateLayout (SnapDocument doc) { //Добавляем SnapList в документ SnapList list = doc.CreateSnList (doc.Range.End, «List»); list.BeginUpdate (); //Настраиваем источники данных list.EditorRowLimit = 11; list.DataSourceName = «NWindDataSource2»; list.DataMember = «Products»; // Добавляем хэдер SnapDocument listHeader = list.ListHeader; Table listHeaderTable = listHeader.InsertTable (listHeader.Range.End, 1, 3); TableCellCollection listHeaderCells = listHeaderTable.FirstRow.Cells; listHeader.InsertText (listHeaderCells[0].ContentRange.End, «Product Name»); listHeader.InsertText (listHeaderCells[1].ContentRange.End, «Units in Stock»); listHeader.InsertText (listHeaderCells[2].ContentRange.End, «Unit Price»); // Настраиваем шаблон строки данных SnapDocument listRow = list.RowTemplate; Table listRowTable = listRow.InsertTable (listRow.Range.End, 1, 3); TableCellCollection listRowCells = listRowTable.FirstRow.Cells; listRow.CreateSnText (listRowCells[0].ContentRange.End, «ProductName»); listRow.CreateSnText (listRowCells[1].ContentRange.End, «UnitsInStock»); listRow.CreateSnText (listRowCells[2].ContentRange.End, @«UnitPrice \$ $0.00»); list.EndUpdate (); list.Field.Update (); } Теперь при запуске нашего приложения мы уже будем получать заполненный данными отчет: a56e9ce6859225cb41c417cf72ff0111.png

Также можно переключиться в режим просмотра кодов полей и посмотреть, как устроен получившийся SnapList изнутри:

46301741a77035a5a9858adbac86ffb1.png

Неплохо, но хотелось бы настроить внешний вид получившегося документа. Приведенный ниже код позволит придать более профессиональный вид нашему отчету:

void FormatListHeader (SnapList list) { SnapDocument header = list.ListHeader; Table headerTable = header.Tables[0]; headerTable.SetPreferredWidth (50×100, WidthType.FiftiethsOfPercent); foreach (TableRow row in headerTable.Rows) { foreach (TableCell cell in row.Cells) { // Применяем форматирование ячеек cell.Borders.Left.LineColor = System.Drawing.Color.White; cell.Borders.Right.LineColor = System.Drawing.Color.White; cell.Borders.Top.LineColor = System.Drawing.Color.White; cell.Borders.Bottom.LineColor = System.Drawing.Color.White; cell.BackgroundColor = System.Drawing.Color.SteelBlue; // Применяем форматирование текста CharacterProperties formatting = header.BeginUpdateCharacters (cell.ContentRange); formatting.Bold = true; formatting.ForeColor = System.Drawing.Color.White; header.EndUpdateCharacters (formatting); } } } void FormatRowTemplate (SnapList list) { // Настраиваем внешний вид строки данных SnapDocument rowTemplate = list.RowTemplate; Table rowTable = rowTemplate.Tables[0]; rowTable.SetPreferredWidth (50×100, WidthType.FiftiethsOfPercent); foreach (TableRow row in rowTable.Rows) { foreach (TableCell cell in row.Cells) { cell.Borders.Left.LineColor = System.Drawing.Color.Transparent; cell.Borders.Right.LineColor = System.Drawing.Color.Transparent; cell.Borders.Top.LineColor = System.Drawing.Color.Transparent; cell.Borders.Bottom.LineColor = System.Drawing.Color.LightGray; } } } В результате мы получим следующую картину: e2910fc1b0da31e27e3ae02a37685e07.png

SnapList также предоставляет соответствующие свойства для группировки, сортировки и фильтрации данных:

SnapList.Groups; SnapList.Sorting; SnapList.Filters; void FilterList (SnapList list) { string filter = »[UnitPrice] >= 19»; if (! list.Filters.Contains (filter)) { list.Filters.Add (filter); } } void SortList (SnapList list) { list.Sorting.Add (new SnapListGroupParam («UnitPrice», ColumnSortOrder.Descending)); } void GroupList (SnapList list) { // Добавляем группировку SnapListGroupInfo group = list.Groups.CreateSnapListGroupInfo ( new SnapListGroupParam («CategoryID», ColumnSortOrder.Ascending)); list.Groups.Add (group); // Добавляем хэдер для каждой группы SnapDocument groupHeader = group.CreateHeader (); Table headerTable = groupHeader.InsertTable (groupHeader.Range.End, 1, 1); headerTable.SetPreferredWidth (50×100, WidthType.FiftiethsOfPercent); TableCellCollection groupHeaderCells = headerTable.FirstRow.Cells; groupHeader.InsertText (groupHeaderCells[0].ContentRange.End, «Category ID:»); groupHeader.CreateSnText (groupHeaderCells[0].ContentRange.End, «CategoryID»); CustomizeGroupCellsFormatting (groupHeaderCells); // Добавляем футер для каждой группы SnapDocument groupFooter = group.CreateFooter (); Table footerTable = groupFooter.InsertTable (groupFooter.Range.End, 1, 1); footerTable.SetPreferredWidth (50×100, WidthType.FiftiethsOfPercent); TableCellCollection groupFooterCells = footerTable.FirstRow.Cells; groupFooter.InsertText (groupFooterCells[0].ContentRange.End, «Count = »); groupFooter.CreateSnText (groupFooterCells[0].ContentRange.End, @«CategoryID \sr Group \sf Count»); CustomizeGroupCellsFormatting (groupFooterCells); } void CustomizeGroupCellsFormatting (TableCellCollection cells) { // Настраиваем форматирование ячеек cells[0].BackgroundColor = System.Drawing.Color.LightGray; cells[0].Borders.Bottom.LineColor = System.Drawing.Color.White; cells[0].Borders.Left.LineColor = System.Drawing.Color.White; cells[0].Borders.Right.LineColor = System.Drawing.Color.White; cells[0].Borders.Top.LineColor = System.Drawing.Color.White; } В итоге мы получаем полноценный отчет, созданный исключительно из кода, использующего публичный Snap API. При этом визуальное дерево всех списков в документе с учетом группировки будет отображаться в специальном элементе, предназначенном для навигации по документу — Report Explorer.79a46bdf8fa79b2d49d7f056fe34a870.png

Помимо простого текста, для представления данных можно использовать целый набор специальных полей:

SnapBarCode — отображает штриховые коды различных типов; SnapCheckBox — вставляет чек-бокс; SnapHyperlink — предназначено для вставки гиперссылок; SnapImage — добавляет в документ изображения; SnapSparkline — спарклайны; SnapText — вставляет форматированный текст. Модель событий Практически любое действие, изменяющее структуру отчета, сопровождается соответствующим событием, что позволяет контролировать весь процесс и при необходимости совершать различные дополнительные действия.Добавление нового списка:

SnapDocument.BeforeInsertSnList — в обработчике этого события можно получить доступ к тем колонкам, которые будут вставлены в документ, и при необходимости отредактировать его, например, удалить часть элементов; SnapDocument.PrepareSnList — предоставляет доступ к структуре добавленного списка, позволяя добавить произвольный текст или изменить его, используя описанный выше API (поменять шаблоны заголовка или строк данных, применить сортировку или наложить фильтр); SnapDocument.AfterInsertSnList — позволяет сделать окончательные правки динамически вставленного списка, указывая позиции, в которые он будет добавлен

void OnBeforeInsertSnList (object sender, BeforeInsertSnListEventArgs e) { e.DataFields = ShowColumnChooserDialog (e.DataFields); } List ShowColumnChooserDialog (List dataFields) { ColumnChooserDialog dlg = new ColumnChooserDialog (); dlg.SetFieldList (dataFields); dlg.ShowDialog (); return dlg.Result; } 9c0721583124fdc3e4cb89dbe2a1fca4.gifДобавление колонок в существующий список:

Наличие хот-зон позволяет пользователю добавлять новые колонки в уже имеющийся список. При этом будут возникать следующие события:

SnapDocument.BeforeInsertSnListColumns — в обработчике этого события можно получить доступ к тем колонкам, которые будут вставлены в список, и при необходимости изменить их, например, переупорядочить или добавить новые; SnapDocument.PrepareSnListColumns — поднимается для каждой добавленной колонки, позволяя настраивать шаблонам заголовка и основной части элемента списка; SnapDocument.AfterInsertSnListColumns — происходит после того, как все колонки были добавлены, возвращая итоговый список в своих аргументах. Это событие дает последнюю возможность настроить список перед генерацией документа с использованием реальных данных (например, добавить группировку или сортировку);

void OnPrepareSnListColumns (object sender, PrepareSnListColumnsEventArgs e) { e.Header.InsertHtmlText (e.Header.Range.Start,»Auto-generated header for column\r\n»); } 9c0721583124fdc3e4cb89dbe2a1fca4.gifДобавление вложенного списка:

Если в качестве источника данных используется иерархический объект (например, DataSet с несколькими таблицами и заданными между ними отношениями), то пользователь может использовать хот-зоны для создания master-detail отчетов. В этом случае будет подниматься следующий набор событий:

SnapDocument.BeforeInsertSnListDetail — поднимается непосредсвенно после того, как выбранные поля были брошены на хот-зону. Через аргументы предоставляет доступ как к master-списку, в который будет добавлен detail, так и в тем полям, которые будут добавляться. В обработчике этого события можно изменять как набор полей, так и их порядок (можно, к примеру, полностью очистить этот набор, так что в результате никаких измений в документе не произойдет); SnapDocument.PrepareSnListDetail — поднимается после того, как генерация вложенного списка была завершена, позволяя программно его изменить; SnapDocument.AfterInsertSnListDetail — поднимается, когда вложенный список был добавлен;

void OnAfterInsertSnListDetail (object sender, AfterInsertSnListDetailEventArgs e) { PaintTable (); snapControl1.Document.Selection = e.Master.Field.Range; } void PaintTable () { SnapDocument document = snapControl1.Document; TableCollection tables = document.Tables; if (tables.Count == 0) return; document.BeginUpdate (); for (int k = 0; k < tables.Count; k++) { Table table = tables[k]; TableCellProcessorDelegate reset = ResetCellStyle; table.ForEachCell(reset); TableCellProcessorDelegate setStyle = SetCellStyle; table.ForEachCell(setStyle); } document.EndUpdate(); } bd9f0e4c34a545dd855c84b35d1018bd.gifЕсли какие-то сценарии создания отчетов остались непокрытыми или у вас возникли предложения по улучшению продукта, приглашаю вас к обсуждению в комментариях. Я постараюсь ответить на все ваши вопросы.

© Habrahabr.ru