Nested Grid (from MSDN) for displaying layered data

xiaoxiao2021-03-06  25

Dino esposito

Download this article: cutting -dge0310.exe (135KB)

In the "Traveler Technology" 2003 magazine, I discussed how to extend the ASP.NET DataGrid server control to use multiple table data containers (such as DataSet objects) as their data sources. If the DataSet contains a number of pairs related tables, the control will add a dynamically created button column as long as the table displayed is the parent of one of the relationships. When you click this column buttons, sub-DataGrid will be displayed, and the sub-line of the selected record will be columnized according to this relationship. Overall behavior is displayed in Figure 1, this behavior is the same as the WINDOWS® form DataGrid control in a similar situation.

Figure 1 Parent and child DataGrids

The application shown in Figure 1 is a user control of a DataGrid control that works together. The user control (see the source code in August 2003) contains all the logic required to keep two grids to synchronize. The parent DataGrid is bound to the DataSet and displays the contents of the parent table. When this occurs, the user control ensures that the DataSet is related to the internal relationship between the DataSet in which the parent is active. The sub-table contains only all records in the subtables associated with the selected record. So if you have a DataSet, and it has two tables that have established a relationship, the user control will save you because you don't need to write code for any additional display mechanism.

So what is the problem with this method? If you just pay attention to basic features, then it has no problem. However, some readers have noted that the DataGrid controls that do not use two physically separated DataGrid controls will be better. The user control built a barrier around the constituent control, allowing you to access these component controls by mapping properties and methods or by publicizing the entire internal control. From programmability point of view, using a DataGrid control to show that the hierarchical data is much simpler. First, you don't have to worry about the configuration of the parent table. Just use the standard interface of the DataGrid control. Any sub-grid that displays related data can be created dynamically and can be displayed inside the layout of the primary network.

Figure 2 Embedded sub-DataGrid

On the other hand, it is necessary to remind that the design DataGrid control is not to include layered data. Its internal layout is best for displaying table format data. The DataList control may be a nice choice, but it does not provide inherent paging support, and some code can work like DataGrid. When you quickly search "Nesting DataGrid" on Google, it returns a link to discuss how DataGrid embedded to the DataList control, which gives me some insights on this column. Here, I will construct a custom control from the DataGrid class. This control implements a custom column type (ExpandCommandColumn) and contains all logic required to display the records associated with the clicked item. Expands view by embedding the sub-DataGrid in the parent. Figure 2 shows the appearance of this control.

Texture nested grid

The hierarchical DataGrid control is only meaningful when the data source is a DataSet object that contains the relationship between the tables. For example, assume that a DataSet has a Customers table and the Orders table and establishes DataRelation between the two tables on the CustomerID column. As long as the DataGrid contains a button column, then create a sub-view for the selected customer when you click it, and bind the generated DataView object to the subnet.

Since the new control (called NestedGrid in the sample code) is inherited from the DataGrid class, it can be used in any case that is suitable for use in the DataGrid object. However, this sentence is still to be modified. Typically, when the control is derived from the base class, there may be such a situation: the controlled control cannot replace the original control due to its specific extension and additional items. In this column, I don't spend too much time to make the NestedGrid component backwardly and the BRAGRID class. For the sake of simplicity, I assume that you always bind it to the DataSet object. Regarding the NESTEDGRID control, there are several other assumptions, which will gradually explain the back part. In particular, it is that the button columns of the expanded / collapsed state that you are responsible for adding the state of each line. In theory, this column can be placed anywhere in the grid. However, I am here to assume that the launcher is the first column in the grid. (August 2003 is discussed, you can modify the behavior in order to generate this column only when DataGrid is bound to DataSet with a related table.)

If you have a little experience in using the DataGrid control, you will know that it is extremely powerful, but it can be customizable, but it cannot support the layout of the layout. Grid layout represents Table Format Data - a number of rows that are continuously in a ruling. How can I embed a sub-grid with this restriction?

An important point to reminding here is that the grid is presented as a standard HTML table. Once the cell forms a rule layout, anything can be placed in each of the cells, including a sub-table (using the ROWSPAN tag) of the sub-grid. First, delete all other cells other than the cells containing the command button to modify the number of cells that make up the selected line (ie, the user clicking the unfolded command button "). This is easy to implement if it is assumed that the expand command column is located on the left side. After all cells are deleted, you can create a new cell that across several columns (column number must be equal to the number of columrid controls).

At this point, you have had a fully customized cell that makes the row can be expanded. You can fill in any combination of server controls in this custom cell in a programming manner. For example, you can insert such a table: the top line analog the structure of the deleted cell (usually information about the father's line), and the lower line contains sub-DataGrid. The controls in Figure 2 are based on this scheme.

NESTEDGRID class

As mentioned earlier, the NESTEDGRID class inherits from the System.Web.DataGrid class and adds an additional properties (see Figure 3). This control will also trigger a custom UPDATEVIEW event when data binding is required. To control the object type assigned to the DataSource property (and make sure it is a DataSet), you can override the DataSource property, as shown in the following code:

Public Override Object DataSource

{

Get {returnrate.dataser

SET {

IF (! (value is dataset) {

// throw an exception

}

Base.DataSource = value;

}

}

The NESTEDGRID control is instantiated when the user is clicked to expand the record (a customer). To this end, the nested grid must contain a button column with certain features. First, the grid must provide a handler for the ItemCommand event to process expand / folding requests. The handler sets the ExpandedItemIndex property to a zero-based index that is clicked, and updates the grid view. So, when is it possible to modify the layout of the clicked row? After the grid layout creates, the itemDatabase is excited at the bottom of the event chain. After itemDatabase, the data binding phase is basically completed, and all cells can be displayed. The layout and data you see later will not change again. It is because of this reason, I decided to implement all necessary changes before processing the itemDatabase event.

There are still some precautions that need to be proposed before discussing the implementation of the control. First, the ExpandedItemIndex property is zero, but it represents the absolute position of the clicked row. This property is the only difference between similar grid properties (such as SelectedItemIndex and EditItemIndex) that it is not based on page-based values. Second, NESTEDGRID also implements pagination internally. To move the control between the pages of the member table, do not have any other jobs in addition to the UpdateView events and pass binding data:

Void UpdateView (Object Sender, Eventargs E) {

Binddata ();

}

void binddata () {

DataGrid.DataSource = (Dataset) cache ["mydata"];

DataGrid.databind ();

}

The NESTEDGRID class has built-in processing programs for pageIndexchanged events as follows:

Void PageIndexchanged (Object Sender, DataGridPageChangeDeventargs E)

{

CurrentPageIndex = E.NewpageIndex;

SELECTEDINDEX = -1;

EditIndex = -1;

ExpandedItemIndex = -1;

IF (UpdateView! = NULL)

UpdateView (this, eventargs.empty);

}

The key elements of the structure of the NestedGrid control are the button columns. For simplicity, this version of the control only supports a single expansion item. This feature can be easily extended by changing the ExpandedItemIndex property from an integer to an array or a collection.

ExpandCommandColumn class

You can use a string (such as " /-" or "expand / collapse") or bitmap to present a list. You may have to use different images for different applications. To implement this feature, the most flexible approach is to use several properties like ExpandText and CollapseText. So, should these properties should be defined on the NestedGrid class? In a similar scenario (on site editing), the ASP.NET development team created a custom DataGrid column and placed in this column attributes like EditText, CancelText, and UpdateText. Based on this, I created my own ExpandCommandColumn class and put several text properties to indicate the HTML output used to expand and fold the view. The following code snippet shows how to integrate this custom column with grid.

CollapseText = ""

ExpandText = "">

•••••

Binding custom DataGrid columns are not difficult. In addition to new construction from DataGridColumn inherited, there is no need to work too much. In the new class, you must write code for any additional attributes you need, and rewrite the Initializecell method. This method is called at any time called for the column. Anything that appears inside the column cell is controlled by default. The following code shows the implementation of the ExpandText property:

Public Class ExpandCommandColumn: DataGridColumn

{

Public String ExpandText {

Get {

Object data = viewState ["expandtext"];

IF (Data! = NULL)

Return (String) Data;

Return " ";

}

SET {

ViewState ["ExpandText"] = VALUE

}

}

•••••

}

The CollapseText property is only different on the name of the view status slot and its default ("-").

It is worth noting that the default value of the server control attribute should be set in the GET Accessor instead of setting up in the constructor or initialization event (such as in or LOAD). This is the practice used by Microsoft in the entire ASP.NET. By isolating such code inside the GET accessor of the attribute, the code package is implemented, and the logic behind the property value is clearer separately from the rest of the control. In particular, when the default value of the property is affected by complex rules, this method provides you with a single control point, making the entire code easier to maintain. Speaking of optimal operations should be remembered whether the value returned to the attribute is empty, and it is normalized when necessary. For example, the attribute of the String type should never return null values, but should return empty strings. The DataGrid column is centered on the Initializecell method. This method is declared as public and virtual (that is, can be rewritten in derived class), and the code inside the DataGrid control will call it at any time you need to render the column. Although it is declared as public, this method usually only uses control developers. Take a look at the signature now:

Public override vid initializecell

Tablecell Cell,

Int columnIndex,

ListitemType ItemType)

The DataGrid code calls this method and transmits the object that represents the cell to be created, the index of the column of the network, and the type of cells to be rendered (header, footnotes, items, etc.). It can be seen that there is no information about the index in the single-handed grid page. Is this information really important? Take a look at the predefined type of grid columns, answering seems to be "no". (In fact, this information is not transmitted separately.) Predefined grid columns (binding columns, button columns, hyperlink columns, template columns) use one of two algorithms to fill cells. If its Text property is set, all cells will contain a fixed constant value. Otherwise, if the data binding property (such as DataField) is set, the content of each cell will be parsed through the data binding process.

So, which category should you return the ExpandCommandColumn type? Specifically, it should not be returned to any category. Ideally, this type uses ExpandText or CollapseText (depending on the status of the presented item) to render cells. If the unit gear is matched to the EXPANDITEMINDEX property (or a collection of entries that belongs to the expand), the collapseText value will be used. Otherwise, the default ExpandText property will be used. So how can the listed INITIALIZECELL method know the cell index?

The first thought of flash in my mind is to get the Tablecell object that passes the method, calls its NamingContainer attribute, and converts the result to DataGridItem. If the acquired object is not empty, it will be a cell container, and its ItemIndex property will contain the information you want. Unfortunately, things are not as simple. The name container of the cell object is empty because the TableCell object has not been added to the grid item container when invoing Initializecell. Therefore, it does not belong to any parent container, so that the NamingContainer property returns to empty.

In order to find a solution, I turned my attention to the list of internal overwritable methods of the DataGrid control. DataGrid's InitializeItem method is proved to be what I want. This method is responsible for initializing the grid columns when creating a grid layout. InitializeItem methods are mentioned in the ASP.NET document on MSDN®, but there is no complete introduction to it. Starting from ASP.NET 1.x, the behavior of this method is very simple. InitializeItem adopts two parameters: indicating the DataGridItem object to be rendered, the DataGridColumn object array (all columns of the row): protected virtual void InitializeItem

DataGridItem item,

DataGridColumn [] columns

);

The InitializeItem method is called each column and create a TableCell object for each column. This object passes to the column-specific initializecell method, then add it to the Cells collection of the DataGridItem object. (You may have guess that TableCell's name container is only set to a non-null value.) The code in Figure 4 shows the rewrite version of InitializeItem, which passes an additional flag to the ExpandCommandColumn column class.

Figure 5 includes a code for initializing the cells of the ExpandCommandColumn class. Each cell is submitted as a link button with text, and the text on the button is determined by additional Boolean parameters (see Figure 6). Like many other elements of the DataGrid control, this element also fully supports HTML text, so you can use images to implement expand / folding.

Figure 6 is a cell as a link button

What happens when you click the link button of the column? If you need to column specific behavior, you should add a handler for the Click event. This code will execute the first execution after clicking the event. Next, the event will rise through the DataGridItem class and will cause the DataGrid level Itemmand event.

Sub-grid

Although the DataGrid control has very strong customizable, it does not provide the HTML layout that can be used to modify the line. The custom logic of DataGrid is built around this idea: the grid consists of columns, and the line is only the result of columns adjacent to each other. However, here, you need to modify the structure of the selected line to include the sub-DataGrid control. In the process, you can only change the layout of the grid in two locations: Itemcreated events or (more ideal) ItemDatabase. From the lifetime of the mesh, the ItemDatabase event excited time is slightly late, and it is the last event you see before the new row is added to the final HTML table.

The ItemCommand event rises to the grid when the user clicks a link button in the command column. If the command name is equal to Expand (the command name of the column), the code will first look at the index of the clicked item to match the ExpandedItemIndex property. If so, the user clicks the expanded item, which will be folded next. Figure 7 shows a code that implements this mechanism. As mentioned earlier, the ExpandedItemIndex property is an absolute index whose range is between the items in the 0 to the data source. That's why the reasons for comparing their analog numbers with the index of the items when writing the grid.

The last step of the code shown in Figure 7 excites the UpdateView event. For the NESTEDGRID control, this event represents the entry point that handles the UI presented. Processing events, binding any necessary data and the work of the DATABIND method that finally calls the grid is expected to be processed by the client. At this point, the life of the control is a continuous number of events, where the first event is DataBinding. Next, ItemCreated and ItemDatabase are excited for each item in the network (including headers, data rows and footnotes). At this stage, INITIALIZEITEM is called to populate the cells of each bound column. In FIG. 2, it can be seen that the grid is a space for another embedded grid (a sub-line of the display expanded). Assuming that the expiration column is the leftmost column, remove all subsequent cells, and replaced these cells with a new cell (where the ROWSPAN attribute is set correctly). The content of this new cell can be determined by you, but at least the following information should be included: deleted cells (ie, information about the record to be expanded) and sub-grid. After deleting a number of cells, it will be added again, which sounds puzzled, but this compromise is necessary for simultaneous requirements: inserting a sub-table while retaining the remaining table layout section.

In my implementation, I cache the text and width of each cell to be deleted. As another way, consider moving the Tablecell object from a Cells collection to another Cells collection (see Figure 8). The new cell contains a two-line table, wherein the first line reproduces the original cell, the second row spans the entire width to display sub-DataGrid.

When I first test this code, it produced a problem. Before encoding into the ASP.NET, I verified this idea with pure HTML. I am very sure that this layout described earlier is meaningful, so I encode it inside the ItemDatabase on ASP.NET DATAGRID. It is very surprised that it does not correctly across the entire width. I spent some time to understand the internal process. The problem is that I assigned a clear width represented by pixels to each column (including the first column behind the launch column):

Headertext = "id"

Datafield = "id"

ItemStyle-width = "150px" />

In this way, the new cell (a unit of a plan comprising sub-grid) is still the first column after the deployment, thus inheriting the width of pixels in the original ID column. In order to make this cell across the available space, the width property should not be set, but it should be left empty. Whether you do something in your code (see the ItemDatabase Event Handler), the DataGrid internal framework always generates a Style property that keeps the original width in pixels. If you look at the source html of the cell, you will see a similar design:

Style = "Width: 150px; ...; width = '';"

The width property sets twice (the second specified as specified in ItemDatabase), but for the browser, the first value is the only value that has an impact. For this issue, in addition to deleting static widths, I didn't find a better solution. If the column needs to have a width, you can define a custom attribute of the width (called HostColumnWidth) with the unit of the dominant column (the first column after the expiration column). The following code snippet shows how to dynamically set the width of the column, thereby obtaining the effect of the style equivalent to the setting item: if (E.Item.ItemIndex! = (ExpandedItem% this.pagesize) {

// Equivalent to setting itemstyle-width Declaratively

E.Item.cells [1] .width = hostColumnwidth;

Return;

}

Create a child view

At this point, the entire development process has been nearly an end, but there is still the last step is not completed, that is, the filler DataGrid. The method of completing this step depends on the inner layout of the layered data you manage. However, if you retain multi-level data in the ADO.NET Dataset with a number of pairs of related tables, use the sub-DataView object is a viable way. (This method is similar to what I discuss in columns in August 2003.)

The user selects the parent table to display in the grid and the relationship used to determine the subview. The subview is created by calling the CreateChildView method on the DataRowView object representing the parent record:

DataTable DT = DS.TABLES [this.datamember];

DataView theview = New DataView (DT);

DataRowView DRV = theview [expandediteMindex];

DataView DetailsView = Drv.createChildView (this.Rerancename);

A set of records associated with the unfolded rows in a new DataView object. For example, if a Customer-To-Orders relationship is established, the sub-record collection will be an order sent by a given customer. Subsides are dynamically created and configured, as shown below:

Detailsgrid = new datagrid ();

Detailsgrid.id = "detailsgrid";

Detailsgrid.font.name = this.font.name;

Detailsgrid.font.size = this.font.size;

Detailsgrid.width = unit.Percentage (100);

Detailsgrid.Allowpaging = true;

Detailsgrid.pageSize = 5;

Detailsgrid.pageIndexchanged = New DataGridPageChangeDeventhandler

Detailsgrid_pageIndexchanged);

Binddetails (Detailsgrid);

In particular, the sub grid is managed internally. It is provided with a built-in handler for pageIndexchanged events, which automatically moves the grid to the next page when the user clicks the page navigation button. To make this function, the programmer does not need to write more code.

Any event generated within the embedded grid is not visible outside the outermost grid. Couple with other reasons, this means that the user code will never write user code to handle the click event on the page navigation bar of sub-grid. Is there a way to solve this restriction on the structure? One possibility is to stimulate new events from the internal handler. If you don't like to paginate the child grid, you can use the scroll bar and package the grid into the scrollable panel. In HTML 4.0, if the content exceeds a fixed size, the Overflow CSS property converts some HTML elements to a rolling area. To make the sub-grid can scroll, simply package it into a panel (corresponding to the tag) and specify the overflow attribute for the panel (see Figure 9). Now, the grid's header scrolls with the rest of the control. This behavior is designed, it requires a very complex trick, not discussed in this column.

summary

ADO.NET DataSet objects work mode similar to the memory within the table and relationship collections, which allows you to create a hierarchical data representation. The combination of iterations and data binding controls such as DataList, Repeater or Label can be easily simulated, so that such data can be rendered through the grid. The disadvantage of this method is that it is required to clearly write code to perform paging. DataGrid provides a lot of interesting features, but does not provide any feature of the layered multi-table data. In the "Frontier Technology" column in August 2003, I discussed a solution based on user control. In this column, I introduced the expansion of the DataGrid control itself by inheritance. At this point, this discussion topic declares the end.

Please send questions and comments related to DINO to cutting@microsoft.com.

Dino Esposito is a teacher and consultation in Rome, Italy. As the author of Programming ASP.NET and Applied XML programming for .NET (all by Microsoft Press), he spent a lot of time to teach the class in ADO.NET and ASP.NET, and publish a lot in various conferences. Speech. Please contact him by dinoe@wintellect.com.

Go to the original English page

转载请注明原文地址:https://www.9cbs.com/read-81418.html

New Post(0)