Implement custom virtual containers (implement VirtualizingPanel) and WPF custom controls in wpf

Source: Internet
Author: User

Implement custom virtual containers (implement VirtualizingPanel) and WPF custom controls in wpf

Implement custom virtual containers in WPF (implement VirtualizingPanel)

 

During the development of WPF applications, performance issues are usually taken into account for the presentation of large data volumes. There is a common situation: the raw data source has a large amount of data, but the number of visible elements in the Data container is limited at a certain time, and most of the remaining elements are invisible, if all the data elements are rendered at one time, it will consume a lot of performance. Therefore, we can consider rendering only the elements in the current visible area. When the elements in the visible area need to change, we can render the elements to be displayed, and finally clear the elements that no longer need to be displayed, this greatly improves the performance. In WPF, The VirtualizingStackPanel in the System. Windows. Controls namespace can realize the virtualization function of data presentation. The default element of ListBox is the container. However, sometimes the VirtualizingStackPanel layout cannot meet our actual needs. In this case, we need to implement a virtual container with a custom layout. This article briefly introduces the container custom layout, introduces the basic principle of implementing virtual containers, and finally provides a demo program for virtualized paging containers.

 

1. custom Layout in WPF (users who know container custom layout can skip this section)

 

To implement a custom Layout container, you must inherit System. Windows. Controls. Panel and override the following two methods:

MeasureOverride-- Used to measure the expected layout size of child elements

ArrangeOverride-- Used to arrange the layout of child elements in the container.

 

The following uses a simple SplitPanel to illustrate the functions of these two methods. A SplitPanel is placed in the following Window. Each time you click the "add" button, a Rectangle filled with random colors is added to the SplitPanel, regardless of the number of rectangles in the SplitPanel, containers are everywhere in the vertical direction, and the width is evenly allocated in the horizontal direction.

The implementation code is as follows:

 

SplitPanel /// <summary> /// simple custom container // sub-elements are filled with containers in the vertical direction, horizontal draw distribution container width // </summary> public class SplitPanel: Panel {protected override Size MeasureOverride (Size availableSize) {foreach (UIElement child in InternalChildren) {child. measure (availableSize); // Measure the expected layout size of child elements (child. desiredSize)} return base. measureOverride (availableSize);} protected override Size ArrangeOverride (Size finalSize) {if (double. isInfinity (finalSize. height) | double. isInfinity (finalSize. width) {throw new InvalidOperationException ("the Width and height of the container must be a definite value");} if (Children. count> 0) {double childAverageWidth = finalSize. width/Children. count; for (int childIndex = 0; childIndex <InternalChildren. count; childIndex ++) {// calculate the layout of the child element. var rect = new Rect (childIndex * childAverageWidth, 0, childAverageWidth, finalSize. height); InternalChildren [childIndex]. arrange (rect) ;}} return base. arrangeOverride (finalSize );}}SplitPanel

 

SplitPanelMeasureOverrideThe availableSize parameter is the total layout size that the container can give. In the method body, only the Measure method of the sub-element is called sequentially. After this method is called, the DesiredSize attribute of the child element is assigned a value, indicating the expected layout size of the child element. (In SplitPanel, you do not need to know the expected layout size of the child element, so you do not need to rewrite it.MeasureOverrideMethod, but the DesiredSize attribute of sub-elements must be overwritten in some complex la S)

SplitPaneld'sArrangeOverrideThe finalSize parameter is the final layout size provided by the container. The average width of the child element is calculated for the 26 rows based on the number of child elements, and the layout area information is calculated for 30 rows based on the sub-element index. Then, line 31 calls the Arrange method of the child element to Arrange the child element in the appropriate position of the container. In this way, you can achieve the expected layout effect. When the UI is re-painted (for example, the number of sub-elements is changed, the container layout size is changed, and the UI is forcibly refreshed ),MeasureOverrideAndArrangeOverrideMethod.

Ii. Virtual container Principle

To implement a virtual container and make the virtual container work normally, the following two conditions must be met:

1. The Container inherits from System. Windows. Controls. VirtualizingPanel and implements child element instantiation, virtualization, and layout processing.

2. The virtual container is used as the ItemsPanel (actually defining an ItemsPanelTemplate) of an instance of System. Windows. Controls. ItemsControl (or a class inherited from ItemsControl)

 

The following describes how ItemsControl works:

When the ItemsSource attribute is specified for an ItemsControl, the Items attribute of ItemsControl will be initialized, where the original data (Problem: by modifying the Items Filter, you can Filter elements that do not switch data sources. Modifying the SortDescriptions attribute of Items can sort elements that do not switch data sources.). ItemsControl will generate according to Items.Sub-element container(ItemsControl generates ContentPresenter, ListBox generates ListBoxItem, ComboBox generates ComboBox, and so on), andSet DataContext to the corresponding data sourceFinally, each sub-element container renders the actual display effect of the sub-element based on the definition of ItemTemplate.

For Panel, ItemsControl will generate sub-element containers of all sub-elements at a time and initialize the data. This results in poor performance when the data volume is large. For VirtualizingPanel, ItemsControl does not automatically generate sub-element containers and rendering of sub-elements. This process requires programming.

Next we will introduce another important concept:GeneratorPositionThis struct is used to describe the relationship between the instantiation and the location of virtualized data Items in the Items attribute of ItemsControl.ItemContainerGenerator(Note: Before the VirtualizingPanel accesses this attribute for the first time, you must first access the InternalChildren attribute. OtherwiseItemContainerGeneratorIt will be null, which seems to be a Bug) to get the location information of the data item. In addition, this attribute can also be used to instantiate and virtualize the data item.

Obtain information about the data item GeneratorPosition:

 

DumpGeneratorContent /// <summary> // display the data GeneratorPosition information /// </summary> public void DumpGeneratorContent () {IItemContainerGenerator generator = this. itemContainerGenerator; ItemsControl itemsControl = ItemsControl. getItemsOwner (this); Console. writeLine ("Generator positions:"); for (int I = 0; I <itemsControl. items. count; I ++) {GeneratorPosition position = generator. generatorPositionFromIndex (I); Console. writeLine ("Item index =" + I + ", Generator position: index =" + position. index + ", offset =" + position. offset);} Console. writeLine ();}DumpGeneratorContent

 

Row 7th uses the static method GetItemsOwner of ItemsControl to locate the ItemsControl of the container. This way, you can access the data item set. Row 12th calls the GeneratorPositionFromIndex method of generator, obtain the GeneratorPosition information of the data item through the index of the data item.

 
Data item instantiation: 
/// <Summary> /// instantiate the sub-element /// </summary> /// <param name = "itemIndex"> data entry index </param> public void RealizeChild (int itemIndex) {IItemContainerGenerator generator = this. itemContainerGenerator; GeneratorPosition position = generator. generatorPositionFromIndex (itemIndex); using (generator. startAt (position, GeneratorDirection. forward, allowStartAtRealizedItem: true) {bool isNewlyRealized; var child = (UIElement) generator. generateNext (out isNewlyRealized); // instantiate (construct an empty sub-element UI container) if (isNewlyRealized) {generator. prepareItemContainer (child); // fill in the UI container data }}}

 

Row 3 calls the StartAt method of generator to determine the location of the data items to be instantiated, and row 3 calls the GenerateNext method of generator to instantiate the data items, if the output parameter isNewlyRealized is true, the element is instantiated from the virtualization state, and false indicates that the element has been instantiated. Note: This method only constructs the UI container of sub-elements. the actual content of the UI container is rendered according to the ItemTemplate definition of ItemsControl only when 17 rows of PrepareItemContainer method are called.

 
Data item Virtualization:VirtualizeChild /// <summary> /// virtualization sub-element /// </summary> /// <param name = "itemIndex"> data entry index </param> public void virtualizeChild (int itemIndex) {IItemContainerGenerator generator = this. itemContainerGenerator; var childGeneratorPos = generator. generatorPositionFromIndex (itemIndex); if (childGeneratorPos. offset = 0) {generator. remove (childGeneratorPos, 1); // Virtualization (clear data from the sub-element UI container )}}VirtualizeChild

 

GeneratorPosition information is obtained through the data entry index, and then the elements can be virtualized by calling the Remove Method of generator In line 11.

 
There is an intuitive understanding through several images. There are a total of 10 data entries, all of which are in the virtualization status during initialization:
 
 
Instantiate the second element:
 
 
Add the third and seven elements of Instantiation:
 
 
The second element of Virtualization:
Through observation, we can find that, 
The location information of the instantiated data item increases sequentially from 0. The offset attribute of the location information of all instantiated data items is 0. The index of the virtualized data item is consistent with the index of the previous recently instantiated element, offset increases sequentially. 
 
Iii. Practice-implement a virtualized paging container

After learning about the sub-element custom layout, data item GeneratorPosition information, virtualization, and instantiation concepts and implementation methods, there is only one more important task to implement a custom virtual container: calculate the start and end indexes of the data items to be displayed, instantiate these data items, and Virtualize data items that are no longer displayed.

Further, implement a virtualized paging container:

This virtualized paging container has two dependency attributes: ChildWidth and ChildHeight, which are used to define the width and height of sub-elements in the container, in this way, when the container layout size is determined, you can calculate the total number of child elements displayed in the available layout, that is, the PageSize attribute. Specify a data source with 5000 data records for the container, and provide a paging control to control the PageIndex of the paging container to achieve the paging display effect.

Paste the main code:

 

Calculate the start and end indexes of the data item to be instantiated. /// <summary> // calculate the start and end indexes of the element. /// </summary> /// <param name = "availableSize"> available layout size </param> /// <param name = "firstVisibleChildIndex"> the first displayed sub-element index </param> /// <param name = "lastVisibleChildIndex"> final A displayed sub-element index </param> private void ComputeVisibleChildIndex (Size availableSize, out int firstVisibleChildIndex, out int lastVisibleChildIndex) {ItemsControl itemsControl = ItemsControl. getItemsOwner (th Is); if (itemsControl! = Null & itemsControl. Items! = Null & ChildWidth> 0 & ChildHeight> 0) {ChildrenCount = itemsControl. items. count; _ horizontalChildMaxCount = (int) (availableSize. width/ChildWidth); _ verticalChildMaxCount = (int) (availableSize. height/ChildHeight); PageSize = _ horizontalChildMaxCount * _ verticalChildMaxCount; // calculate the index firstVisibleChildIndex = PageIndex * PageSize; lastVisibleChildIndex = Math. min (ChildrenCount, firstVisibleChildIndex + PageSize)-1; Debug. writeLine ("firstVisibleChildIndex: {0}, lastVisibleChildIndex {1}", firstVisibleChildIndex, gradient)} else {ChildrenCount = 0; firstVisibleChildIndex =-1; priority =-1; pageSize = 0 ;}}Calculate the expected size of the child element layout of the index measurement sub-element to be instantiated and the data item instantiation // <summary> // measure the child element layout, generate the child element to be displayed /// </summary> /// <param name = "availableSize"> available layout size </param> /// <param name = "firstVisibleChildIndex "> index of the first child element displayed </param> /// <param name =" lastVisibleChildIndex "> index of the last child element displayed </param> private void MeasureChild (Size availableSize, int firstVisibleChildIndex, int lastVisibleChildIndex) {if (firstVisibleChildIndex <0) {return ;}// note that Before using ItemContainerGenerator for the first time, you must first access InternalChildren. // otherwise, ItemContainerGenerator is null, which is a Bug in UIElementCollection children = InternalChildren; IItemContainerGenerator generator = ItemContainerGenerator; // obtain the position information of the first visible element GeneratorPosition position = generator. generatorPositionFromIndex (firstVisibleChildIndex); // calculate the sub-element index int childIndex = position based on the element location information. offset = 0? Position. index: position. index + 1; using (generator. startAt (position, GeneratorDirection. forward, true) {for (int itemIndex = firstVisibleChildIndex; itemIndex <= lastVisibleChildIndex; itemIndex ++, childIndex ++) {bool isNewlyRealized; // indicates whether the newly generated element is a new materialized element. // generates the next child element var child = (UIElement) generator. generateNext (out isNewlyRealized); if (isNewlyRealized) {if (childIndex> = children. count) {AddInternalChild (child);} else {InsertInternalChild (childIndex, child);} generator. prepareItemContainer (child);} // calculates the child element layout. measure (availableSize );}}}View Code

 

Clear child elements that are no longer displayed /// <summary> /// clear child elements that do not need to be displayed /// </summary> /// <param name = "firstVisibleChildIndex"> the first sub-element index displayed </param> /// <param name = "lastVisibleChildIndex"> the last sub-element index displayed </param> private void CleanUpItems (int firstVisibleChildIndex, int lastVisibleChildIndex) {UIElementCollection children = this. internalChildren; IItemContainerGenerator generator = this. itemContainerGenerator; // clear the child elements that do not need to be displayed. Note that the operation is performed forward from the back of the set to avoid changing the element index during the operation. for (int I = children. count-1; I>-1; I --) {// obtain the element index var childGeneratorPos = new GeneratorPosition (I, 0) based on the position information of the displayed child element ); int itemIndex = generator. indexFromGeneratorPosition (childGeneratorPos); // remove the element if (itemIndex <firstVisibleChildIndex | itemIndex> lastVisibleChildIndex) {generator. remove (childGeneratorPos, 1); RemoveInternalChildRange (I, 1 );}}}Clear child elements that are no longer displayed

 

 

 

This article is excerpted from:Http://www.cnblogs.com/talywy/archive/2012/09/07/CustomVirtualizingPanel.html is great, thanks very much to the blogger

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.