📜 ⬆️ ⬇️

Snap is a new reporting platform. Part 2

In a previous article, I made a preliminary review of Snap, our reporting product designed to make it easier for you and your users to create business documentation.

Today we will look at how to make a finished report entirely from code. In the process of creating the application, we will take a closer look at some of the principles of how Snap works and will talk in more detail about its internal structure and the mechanisms that we have implemented in it.


')
So, under the cut you will find the promised entertaining mechanics.


Application Preparation


To begin with, we will create a new Windows Forms application in Visual Studio and drag SnapControl from the DX14.1: Reporting tab to the main form, which will be added to the toolbox after installing Snap.



Now add the menu using the corresponding items in the list that appears when you click on the smart tag of the control.





Everything, the skeleton is ready, now we will build up muscles.



Data binding


As I mentioned earlier, it makes sense to start work by providing access to the data. Snap supports multiple data source assignment scripts. The most obvious is the use of the user interface. Data sources added through the UI are assigned to a specific document, but they can also be saved for other reports. The user can independently configure the connection using the wizard, which can be invoked by selecting the Add New Data Source command on the File tab:



Or, you can select the Add Data Source item in the context menu opened by right-clicking an empty space in the Data Explorer:



The Data Source Preparation Wizard maintains an impressive list of possible suppliers:


But do not forget that, depending on the selected provider, you may need to configure various connection parameters - identification type, database name, etc. Since for the average user all this seems to be very esoteric knowledge, it makes sense to set up a turnkey system.

It should be borne in mind that Snap separates the data sources of the level of a specific report and more global data of the application level that is available for any document opened in it. Application-level data is set through the SnapControl - DataSource properties for the main default data source, and DataSources is a collection of named data sources. As a data source, standard .Net providers, lists and XML files can be used.

snapControl1.DataSource = dataSet1; snapControl1.DataSources.Add(new DataSourceInfo("NWindDataSource2", dataSet2)); 

Now when you start the application, Data Explorer will display the available data sources.



To set data sources for a specific report, use the same pair of document properties.

 snapControl1.Document.BeginUpdateDataSource(); this.snapControl1.Document.DataSources.Add(new DataSourceInfo("Cars", e1List)); this.snapControl1.Document.DataSources.Add(new DataSourceInfo("Company", e2List)); snapControl1.Document.EndUpdateDataSource(); 

If for some reason the connection to the data source failed, the SnapDocument.ConnectionError event will be raised. Its processing allows you to override the standard behavior, which consists in invoking the connection wizard to re-request parameters.

 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


Now you can proceed directly to creating a report. Like many of his other abilities, Snap has inherited the mechanism for adding dynamic content from the Rich Text Editor. Snap uses fields as a template that is filled with real data. In most cases, it is easier to allow Snap to add fields automatically. If you know the field code, you can enter it in the right place of the document yourself. Just press the Ctrl + F9 keys and enter the code between the curly braces. And you can use the API and completely create the document programmatically.

The main field used to combine the various markup elements into a single model, on the basis of which the data-filled part of the document will be created, is SnapList. SnapList has a hierarchical structure and is divided into several parts, each of which is used to define different list items: a header, a template for each line and a footer. These parts can contain nested fields, forming a tree structure that provides the ability to create complex master-detail reports. Using the following code, you can add SnapList to a document and customize it by defining a list header and a sample for each entry:

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

Now, when we launch our application, we will already receive a report filled with data:



You can also switch to the view of field codes and see how the resulting SnapList works from the inside:



Not bad, but I would like to customize the appearance of the resulting document. The code below will give a more professional look to our report:

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

As a result, we get the following picture:



SnapList also provides relevant properties for grouping, sorting and filtering data:

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

As a result, we get a full-fledged report created exclusively from code using the public Snap API. At the same time, the visual tree of all lists in the document, taking into account grouping, will be displayed in a special element intended for navigation through the document - the Report Explorer.



In addition to plain text, you can use a whole set of special fields to represent data:



Event model


Virtually any action that changes the structure of the report is accompanied by a corresponding event, which allows you to control the entire process and, if necessary, perform various additional actions.

Adding a new list:

SnapDocument.BeforeInsertSnList - in the event handler for this event, you can access the columns that will be inserted into the document and, if necessary, edit it, for example, delete some of the elements;
SnapDocument.PrepareSnList - provides access to the structure of the added list, allowing you to add arbitrary text or change it using the API described above (change the templates of the header or data lines, apply sorting, or apply a filter);
SnapDocument.AfterInsertSnList - allows you to make final edits of a dynamically inserted list, indicating the positions to which it will be added

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



Adding columns to an existing list:

The presence of hot zones allows the user to add new columns to an existing list. The following events will occur:

SnapDocument.BeforeInsertSnListColumns - in the event handler for this event, you can access the columns that will be inserted into the list and, if necessary, change them, for example, reorder or add new ones;
SnapDocument.PrepareSnListColumns - rises for each added column, allowing you to customize the header and main patterns of the list item;
SnapDocument.AfterInsertSnListColumns - occurs after all columns have been added, returning the final list in their arguments. This event provides the final opportunity to customize the list before generating the document using real data (for example, add grouping or sorting);

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



Add nested list:

If a hierarchical object is used as a data source (for example, a DataSet with several tables and relations defined between them), the user can use hot zones to create master-detail reports. In this case, the following set of events will be raised:

SnapDocument.BeforeInsertSnListDetail - rises immediately after the selected fields have been thrown on the hot zone. Through the arguments, it provides access both to the master-list, to which detail will be added, and to those fields that will be added. In the event handler for this event, you can change both the set of fields and their order (for example, you can completely clear this set, so that no changes will occur in the document as a result);
SnapDocument.PrepareSnListDetail - rises after the generation of the nested list has been completed, allowing you to programmatically change it;
SnapDocument.AfterInsertSnListDetail - rises when a nested list has been added;

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



If any report creation scenarios are left uncovered or you have any suggestions for improving the product, I invite you to the discussion in the comments. I will try to answer all your questions.

Source: https://habr.com/ru/post/229351/


All Articles