In the warehouse management system recently created using OEA, many interfaces require table controls to display data. First, these tables have many columns, some even reach 200 columns, and several tables may be displayed on the interface of a module at the same time. This leads to slow interface speed, especially when a large amount of data needs to be displayed. It has been detected that although table rows have been virtualized, many columns eventually lead to too many elements in the visual tree, leading to the interface layout.CodeRunning too slowly. Assume that there are only 30 rows, and only five visible elements are generated for one cell. 200 visible elements are generated for columns of cells, the measure method of the layout system needs to call the corresponding measure method for every element in the visual tree. As you can imagine, this will of course be very slow.
To solve the preceding problems, only by virtualizing rows and columns of a table at the same time can the visual elements of the table be effectively reduced and the system performance be improved. Fortunately, the treegrid in OEA itself is our own custom control for OEA, so we can directly transform it.
However, to achieve row and column Virtualization in a table control at the same time? First, let's look at how to implement Virtualization in WPF.
WPF virtualization knowledge
I wrote one before.Article"Proficient in wpf ui virtualization alization", which cited many articles from foreigners, illustrates several things that need to be done to achieve interface virtualization. Here I will summarize:
- * Set scrollviewer. cancontentscroll to true. When the default value is false, scollviewer implements the rolling logic by itself. In measure, infinite is passed to the content element. When this value is set to true, scrollviwer thinks that its content element implements iscrollinfo and processes all the rolling logic.
- * Inherit a subclass from the virtualizingpanel and implement iscrollinfo for this new Panel (uivpanel.
- * Implement virtualization logic in uivpanel to generate or destroy interface elements.
1. To know how to implement iscrollinfo, you need to understand the design principles of iscrollinfo:
If the uivpanel element needs to process the scroll information by itself, it must know the offset of the current scroll bar and inform scrollviewer Of the total size required to correctly display the scroll bar. When the measure method of the uivpanel element is called by scrollviwer, the parameter can only be passed in and out of the window size. Therefore, the peripheral scrollviewer wants to interact more data with the uivpanel, for example, if you pass in offset (verticaloffset and horizontaloffset) and obtain extent (height/width), you can only use the public attributes of the uivpanel to interact with each other, therefore, uivpanel must implement all attributes and methods defined in iscrollinfo. (Note that all the methods in iscrollinfo only need to set a new offset, but the granularity of scrolling is different .)
2. Details about the interaction between iscrollinfo uivpanel and scrollviewer are as follows:
* When the scollviewer changes the scroll bar, it calls the setverticaloffset or related method of the uivpanel to change the offset value. The uivpanel calls invalidatemeasure in setverticaloffset to remeasure itself.
* In the measureoverride method of uivpanel, the parameter is the size of the window passed in by scrollviewer, and the internal data verticaloffset is obtained. Finally, extentheight/extentwidth (total height/total width) in iscrollinfo is calculated ). If this value changes, call scrollowner. invalidatescrollinfo to notify scrollowner to obtain the latest total height and calculate the latest size of the scroll bar.
When interacting with scrollviewer, The uivpanel should also call a set of element generation methods for the itemcontainergenerator attribute in the basic class virtualizingpanel Based on the provided window size, generate a new container to be displayed, and remove the invisible container to achieve the virtualization effect.
3. The meaning of the generatorposition class:
(If you do not know the generatorposition type, read the code implementing a virtualizingpanel Part 2: iitemcontainergenerator in this article .)
When using itemcontainergenerator to generate elements, you must understand the meaning of generatorposition. It has two attributes: Index and offset. Their meanings can be understood from the indexfromgeneratorposition method:
If the index is greater than or equal to 0, it indicates that the index of a generated item container in all the generated items containers. Assume that the container is A, then, based on a, if the offset is 0, the entire generatorposition indicates the project container A. If the offset is not 0, it indicates a non-generated item container B, which is offset from the relative position of.
If index is-1, offset indicates the offset from the target container to the starting point. If it is negative, offset indicates the offset from the target container to the ending point.
The design of the generatorposition type is obscure and hard to understand. This is related to the internal data structure of Virtualization in virtualizingpanel. itemcontainergenerator. Virtualization splits the entire list into multiple small blocks, which are mainly divided into two types: unrealizeditemblock (uninstantiated block) and realizeditemblock (instantiated block ). The entire list is expressed by a combination of these blocks. If 30 pieces of data can be displayed on one page, a list of 10 thousand rows may consist of the following small blocks: realizeditemblock 60, unrealizeditemblock 8000, realizeditemblock 150, unrealizeditemblock 1790, total: 10 thousand. All the blocks are stored in the field _ itemmap by a two-way linked list in itemcontainergenerator. _ Itemmap. Next is the first block, which can also be understood as the start point or the end point. The unrealizeditemblock and realizeditemblock classes all inherit from itemblock. Itemblock has two important attributes: itemcount and containercount. Itemcount indicates how many pieces of data this block represents. The two items are consistent. Containercount indicates the number of generated containers. For unrealizeditemblock, 0 is always returned, and the itemcount returned by realizeditemblock indicates that the number of containers is the number of items.
As you can see, generatorposition stores the index number of an itemblock and the offset of the specific container relative to this itemblock. The itemcontainergenerator operation uses generatorposition to easily interact with internal data structures. (This design may be due to performance considerations ?)
After finishing the uiv knowledge, let's start designing treegrid table virtualization.
Table Virtualization
We can see from the previous content that if you want to implement a uivpanel with rows and columns supporting virtualization in WPF, you only need to inherit the next uivpanel type from the virtualizingpanel, calculate and generate corresponding cells based on the column width. However, this design will cause all cells to be placed in the uivpanel. That is to say, treegrid acts as an itemscontrol, and all the cells in the treegridcell must act as its logical sub-container. Although this design achieves interface virtualization, it is not desirable. This is because the common use of treegrid is: each item in treegrid is a table row treegridrow, and treegridrow is an itemscontrol, each item in the row is the horizontally arranged cell treegridcell. In this scenario, the treegrid interface design should also be a hierarchical interface such as treegrid-> treegridrow-> treegridcell. The logic tree and the visual tree should also be constructed in this hierarchy, easy to use and debug.
So how can we achieve the virtualization of using only one scroll bar with such a hierarchy requirement? Fortunately, the DataGrid that comes with WPF also has the row and column virtualization function. Let's take a look at how the DataGrid is implemented. Is the visual tree generated after the row and column virtualization functions are enabled by the DataGrid:
Figure 1 DataGrid virtualized visual Tree Structure
Based on the figure above, check the DataGrid source code. You can see that:
* There is only one scrollviewer in the entire DataGrid table. The table acts as an itemscontrol. Each item in the table is a datagridrow, and the Panel used as the itemshost is of the datagridrowspresenter type. The datagridrowspresenter inherits from the virtualizingstackpanel and indirectly inherits the virtualizingpanel and implements the iscrollinfo interface. It provides rolling information for the outermost scrollviewer and the datagridrow row virtualization function.
* In each datagridrow, A datagridcellspresenter inherited from itemscontrol is used to generate the container of each cell, and it uses datagridcellspanel as the itemshost panel. Datagridcellspanel is also a virtualization panel inherited from the virtualizingpanel. However, it does not implement iscrollinfo. To use the information of the scroll bar in the outermost scrollviewer, it finds the datagridrowspresenter through the visual tree to obtain the horizontaloffset of the scroll bar in the horizontal direction, to calculate the cells to be displayed in the horizontal direction for virtualization.
* In addition, we need to explain the two itemscontrol data sources: The itemssource of the DataGrid is of course the list of data models specified at the application layer. In this way, the datacontext of each datagridrow is one of the data model objects. Interestingly, the datagridcellspresenter In the table row is used as a control to display cells horizontally. It is also an itemscontrol and needs to set its itemssource data source attribute. The datacontext of each row should also be the datacontext of each cell, so the datagridcellspresenter. itemssource should be set as a list of data model objects, where each element is a datagridrow. datacontext object. The length of the list is the number of table columns, so that the number of cells consistent with the number of columns can be generated. (Internal implementation, the MS uses a multiplecopiescollection collection type that implements the ilist interface, you only need to set the copieditem and count attributes to show the length of Count, each element is copieditem behavior .)
Treegrid Virtualization
Based on the previous analysis, we know what elements are required for table DataGrid virtualization and how elements interact. Our treegrid control is designed to imitate this structure, and the corresponding treegridrowspanel, treegridcellspresenter, and treegridcellspanel types are added. The final table control, tested, can render 20000 rows of data and 300 columns within 0.5 s:
Figure 2 treegrid with massive data displayed after Virtualization
A large amount of data in the table only generates a small number of visual elements. The final generated visual tree structure is as follows:
Figure 3 visible tree elements after treegrid Virtualization
Because the cells in each column are generated by dragging the horizontal scroll bar, there is a certain delay when dragging, and it does not feel smooth. Therefore, when the number of columns is small, you do not need to enable column virtualization. Currently, when the number of columns exceeds 50, column virtualization is automatically enabled for the table to improve rendering performance.
Future improvements
In fact, as the core control of the OEA framework interface layer, treegrid mainly provides the tree table and general table functions in WPF. In general, the performance guarantee in the table state is achieved by the virtualization technology. In the tree state, it mainly supports the lazy loading of Tree nodes and only instantiates the existing rows. That is, only the parent row in the expanded tree will generate its corresponding child rows. As shown in:
Figure 4 tree table lazy loading
Tree tables are not virtualized at the moment.
To improve performance, virtualizingstackpanel calculates the scroll bar information based on the number of items rather than pixels. This causes errors in Calculation of vertical scroll bars when the height of each row is not uniform, resulting in poor user experience. This is also why the virtualization of controls such as ListBox is disabled in the group state: each item after the group is actually a groupitem type, and the height of each group is inconsistent.
In treegrid, treegridrowspanel that supports row virtualization is inherited from the virtualizingstackpanel. The table row treegridrow class inherits from the headereditemscontrol type, and its total row height should be the height of the row plus the height of all sub-rows, which is not a fixed value. Therefore, the virtualization function is also disabled. When row virtualization is disabled, the column virtualization mechanism depends on the scrollviewer on the outermost layer. That is to say, you cannot enable column virtualization instead of row virtualization.
These functions can be turned on, but the premise is that the treegridrowspanel must inherit from the virtualizingpanel rather than the virtualizingstackpanel, and implement the computing logic of custom rows, which is relatively complex. Considering that there is no problem in the performance of lazy loading in tree tables, virtualization is not implemented for the time being.
(In addition, even if the row virtualization panel is rewritten, The treegridrow is used to calculate the height of all its sub-nodes, and the rows to be displayed are instantiated. You can only enable the virtualization function of the outermost layer of treegridrow, and the tree may have Layer 2, Layer 3 ......, These layers cannot be virtualized. If you want to implement Virtualization of these layers, it will be more complicated ...... :()
In fact, the lazy loading and Virtualization Technologies are essentially the same. They all delay instantiation of elements that do not need to be displayed.
Remarks
Because the design idea of treegrid virtualization technology mainly comes from the DataGrid, some code is even directly copied from the DataGrid, so the code will not be pasted here. The next time you update OEA, you can download it from the open source address.
Treegrid table virtualization technology involves restructuring the internal organizational structure of the entire control, which is a primary part of treegrid restructuring in this phase. In the next article, we will discuss the reconstruction of other aspects of the treegrid control.