Corporate World..Begin.....

Corporate World..Begin.....
Going For Office..

Search This Blog

Friday, January 29, 2010

WPF ListView which can do - sorting, filtering, totals, cell-focus, editing, and more...............


Introduction
It was one of those days. In a solution, the bottleneck was figured out. Once again, it was the third party DataGrid I used. After having tried three different vendors, I was frustrated, and went back to the ListView, and the performance was back to slow. I wanted the DataGrid to present the data fast, specially when I reset the ItemsSource to a new collection.

Without a doubt, the commercial DataGrids are nice looking and feature rich. But the overhead on all that slows them down. The DataGrid from Microsoft never could win my interest, it seems somewhere in between the ListView and the commercial DataGrids. Anyways, I started to tweak the ListView, and here is what I've managed to do.
The basic usage should be easier
Some thing always annoyed me when using commercial DataGrids or the ListView itself. I was tired of writing all those CellTemplates I would have to use. WPF has its own paradigm, but a DataGrid is a DataGrid, at least most of the time.

Wanted Features

Common configuration scenarios available through properties
Default style for the whole grid and optional styles for columns
Lightweight styling and customizing without re-templating
Sorting any column, and SortName property for first-run
Filtering any column (AutoFilter row)
Totals/Summaries for any column
Frozen column left/right
GridLines vertical and horizontal
Cell-focus (cursor)
Cell-editing
Using the code
The basic usage of the DsxDataGrid is pretty straightforward. You can think of it as a 'ListView'. It is a custom control derived from Selector. The ControlTemplates are mainly based on the ListView. The following snippet shows most of the properties for the additional features:

Grid.Row="1"
Margin="5"
AllowCheckAnyTime="True"
SortField="CompanyName"
HorizontalGridLinesIsVisible="True"
VerticalGridLinesIsVisible="True"
CellAdornerIsVisible="True"
CellEditingIsEnabled="True"
HeaderVisibility="Visible"
FilterVisibility="Auto"
FooterVisibility="Auto"
ItemFixHeight="20"
AreaLeftWidth="212"
AreaRightWidth="250"
SplitterLeftWidth="3"
SplitterLeftIsSizing="False"
SplitterRightWidth="3"
SplitterRightIsSizing="False"
IsVirtualizing="False">


Header="" Width="25"
IsSizable="False" IsSortable="False"
ViewType="CheckBox" FilterType="CheckBox"
FooterType="None" EditType="None"
CellHAlign="Center"/>
Header="Company" Width="120"
IsSizable="False" ViewType="Text"
FilterType="TextBox" FooterType="Count"
EditType="TextBox" CellHAlign="Left"
CellForeground="Blue"
CellBackground="#33C3E8D1"/>
Header="Title" Width="120"
ViewType="Text" FilterType="ComboBox"
FooterType="None" EditType="ComboBox"
CellHAlign="Left"
CellContentItemsSource="{x:Static local:ContactTitles.ComboSource}"/>



Most basic columns are ready to use without CellTemplates. Behind the scenes, the CellTemplates are generated.

The basic layout of DsxDataGrid
The DataGrid is divided in three areas. Each area contains a ListView with the same style. The right area is configured to show the vertical scrollbar, and contains the additional areas that fill the special rows. The header (blue), filter (green), and footer (purple) are all GridViewHeaderRowPresenters but with different Styles (ControlTemplates). The synchronization of the vertical scrolling is done by the very useful ScrollSynchronizer: Scroll Synchronization by Karin Huber

Important: if the left and center areas are not needed, only the area on the right is shown.

Column ViewTypes
Each column has a ViewType (default is Text). This injects a CellTemplate to be created that does everything needed.

Collapse Copy Code
public enum EViewType
{
Text = 1,
Integer = 2,
Decimal = 3,
Currency = 4,
Date = 5,
Boolean = 6, // displays BulletChrome
CheckBox = 7,
Image = 8,
Progress = 9,
}In order to serve the different ListView areas, the columns are recreated inside the contained ListView Columns collections. In this step, the column is examined, and depending on the settings, the CellTemplates are generated for the different ViewTypes. In this scenario, I prefer code over XAML, because in code, you can do nasty things like reuse the existing DisplayMemberBinding, and you have full control to minimize the CellTemplates to a minimum.

Column FilterTypes / EditTypes
Each column can have a FilterType and or an EditType, both of type EEditType. If a FilterType is set, the FilterRow displays the FilterCell. The editing is done by placing an Adorner over the current cell. All controls used in the Adorner are configured to look nice even if the cell is of variable height. This approach is much lighter than placing full controls inside column-cells.

Collapse Copy Code
public enum EEditType
{
None = 0,
TextBox = 1,
CheckBox = 2,
DatePicker = 3,
ComboBox = 4,
Slider = 5,
CellTemplate = 6, // not valid for Filter
}Note: All EditTypes are templated to look nice if the ItemHeight is larger than the EditControl usually is:



Column FooterTypes
A FooterType can be set on a column to display a result on the displayed data. If the displayed data is filtered, the result is narrowed to the filtered data.

Collapse Copy Code
public enum EFooterType
{
None = 0,
Sum = 1,
Avg = 2,
Min = 3,
Max = 4,
Count = 5,
}How Sorting/Filtering/Summarize work
Sorting can be invoked either by setting the SortField property on the DataGrid, or by clicking the column-header. Both check first if the referring column allows sorting. The sorting triggers the DisplaySource (result of displayed data) to be re-evaluated. In there, the ICollectionView will do the sorting.

Filtering is invoked by the Adorner and is not accessible by property. The filtering acts on PropertyChanged, but has a short timer to wait for some time before invoking the routine. The filtering triggers the DisplaySource to be re-evaluated too. A callback handles all filter-columns to be checked.

Summarizing the totals is (like you might guess) also done while rebuilding the DisplaySource. Since there is no way to build a sum without enumerating all relevant items at least once, this is done by a simple loop before presenting the data.

Custom CellTemplates
In the sample, the column 'Address' uses a custom CellTemplate, triggering the EditMode property that takes control over the editing (entering/exiting). It would probably easier to enhance the existing types in code, so one could as well use a CellTemplate.

How to Define Alternating RowColors
AlternationCount/-Index is already prepared in the ItemsControl. DsxDataGrid just has the collection for the brushes all ready for use, so you just do this, and it will work:

Collapse Copy Code




How to Use Styles with DsxDataGrid
My approach is to have a default style for header/filter/footer on the DataGrid that is used for every column, but some columns override the style. The GridViewHeaderRowPresenter gets the style from the custom Control-Template, which refers (if not replaced) to, e.g., the dsxFilterStyle (for FilterRow). In this style, you can see this:

Collapse Copy Code
All used properties to style the filters refer to a converter. The converter itself evaluates if there is a style on the column overriding the style on the DataGrid, or maybe none at all.

More Details, Sorry so much!
Unfortunately, I cannot explain all the little tweaks I built in, there are many, and my time is not sufficient for that. Some solutions maybe no rocket science, but they do the job. And yes, I suppose some could be done much more elegantly. But for now, I hope there is some usage for those who come along here because they need ideas.

Limitations
DsxDataGrid must use ItemFixHeight if IsVirtualizing is set or there are multiple areas (frozen column on left and/or right side).

The filters do not listen to changes in the filtered fields of the underlying source.

What is Missing/Needs Redesign?
Grouping, best if IsVirtualizing could be kept (flattened GroupCollections)
Make the filter listen to changes
Redesign frozen columns (WPFToolKit DataGrid has an implementation)
What I have Learned?
Having different areas or frozen columns organized in different independent ListViews is not the best practice. I rarely use that so I can go with the current solution for now. If I knew from the start, I would have invested my time in writing my own GridViewRowPresenter. But I did not want to go down that deep when I started. Well, like the saying: all people are smart - some before, some after

No comments:

Post a Comment