Pull to refresh

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

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

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



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


Подготовка приложения


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



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





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



Привязка данных


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



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



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

  • 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 будет отображать доступные источники данных.



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

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();
}

Теперь при запуске нашего приложения мы уже будем получать заполненный данными отчет:



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



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

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;
        }
    }
}

В результате мы получим следующую картину:



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.



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

  • 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<DataFieldInfo> ShowColumnChooserDialog(List<DataFieldInfo> dataFields) {
	ColumnChooserDialog dlg = new ColumnChooserDialog();
	dlg.SetFieldList(dataFields);
	dlg.ShowDialog();
	return dlg.Result;
}



Добавление колонок в существующий список:

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

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

void OnPrepareSnListColumns(object sender, PrepareSnListColumnsEventArgs e) {
    e.Header.InsertHtmlText(e.Header.Range.Start, "<u>Auto-generated header for column</u>\r\n");
}



Добавление вложенного списка:

Если в качестве источника данных используется иерархический объект (например, 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();
}



Если какие-то сценарии создания отчетов остались непокрытыми или у вас возникли предложения по улучшению продукта, приглашаю вас к обсуждению в комментариях. Я постараюсь ответить на все ваши вопросы.
Tags:
Hubs:
+25
Comments13

Articles

Change theme settings

Information

Website
www.developersoft.ru
Registered
Founded
1998
Employees
201–500 employees
Location
Россия