Click here to Skip to main content
15,867,997 members
Articles / Desktop Programming / WPF

WPF ListView which can do - Sorting, Filtering, Totals, Cell-focus, Editing, and More

Rate me:
Please Sign up or sign in to vote.
4.89/5 (65 votes)
21 Apr 2010CPOL6 min read 276.6K   11.3K   172   103
Enhanced WPF ListView that is almost a DataGrid
dsxDataGridIntro.gif

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 WPF ListView, and the performance was better again. 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:

XML
<dsx:DsxDataGrid x:Name="dataGrid1"
        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">

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

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

The Basic Layout of DsxDataGrid

BasicLayout.png

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.

C#
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.

C#
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:

EditControls.png

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.

C#
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:

XML
<dsx:DsxDataGrid.AlternatingRowBrushes>
   <SolidColorBrush Color="Yellow"/>
   <SolidColorBrush Color="Red"/>
   <SolidColorBrush Color="OliveDrab"/>
</dsx:DsxDataGrid.AlternatingRowBrushes>

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:

XML
<Style x:Key="dsxFilterStyle" TargetType="{x:Type GridViewColumnHeader}">
    <Setter Property="SnapsToDevicePixels"  Value="true"/>
    <Setter Property="Background"  
       Value="{Binding RelativeSource={RelativeSource Self},
               Converter={StaticResource dsxFilterStyleConverter}, 
               ConverterParameter=Background}"/>
    <Setter Property="BorderBrush" 
       Value="{Binding RelativeSource={RelativeSource Self}, 
               Converter={StaticResource dsxFilterStyleConverter}, 
               ConverterParameter=BorderBrush}"/>
    <Setter Property="Foreground"
       Value="{Binding RelativeSource={RelativeSource Self}, 
               Converter={StaticResource dsxFilterStyleConverter},
               ConverterParameter=Foreground}"/>
    <Setter Property="BorderThickness" 
       Value="{Binding RelativeSource={RelativeSource Self}, 
               Converter={StaticResource dsxFilterStyleConverter},
               ConverterParameter=BorderThickness}"/>
    <Setter Property="Padding" 
       Value="{Binding RelativeSource={RelativeSource Self},
               Converter={StaticResource dsxFilterStyleConverter}, 
               ConverterParameter=Padding}"/>
    <Setter Property="Margin" 
       Value="{Binding RelativeSource={RelativeSource Self}, 
               Converter={StaticResource dsxFilterStyleConverter},
               ConverterParameter=Margin}"/>
    <Setter Property="Template"
       Value="{StaticResource dsxFilterStyleControlTemplate}"/>
</Style>

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.

History

  • 28-Jan-2010 - Initial version
  • 17-Apr-2010 - Updated .NET 4 RTM (StaticResource order problem)
  • 20-Apr-2010 - Updated both source and sample code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionset CellContentItemsSource to distinct values Pin
Member 118339415-Oct-16 3:17
Member 118339415-Oct-16 3:17 
Questionrequest Pin
ardeva120126-Mar-15 23:17
ardeva120126-Mar-15 23:17 
QuestionGreat Functionality - is there a way I can get excel style conditional formatting to work? Pin
Member 1052361620-Aug-14 0:11
professionalMember 1052361620-Aug-14 0:11 
GeneralMy vote of 3 Pin
Dhaneshwar Lal25-Nov-13 2:02
Dhaneshwar Lal25-Nov-13 2:02 
QuestionLarge Data Issue Pin
GauravKP2-Nov-13 0:44
professionalGauravKP2-Nov-13 0:44 
AnswerRe: Large Data Issue Pin
christoph brändle8-Nov-13 9:50
christoph brändle8-Nov-13 9:50 
GeneralMy vote of 5 Pin
Champion Chen16-Oct-13 15:48
Champion Chen16-Oct-13 15:48 
QuestionColumn Hide Functionality Pin
Harikumar Ramakrishnan7-Aug-13 21:25
Harikumar Ramakrishnan7-Aug-13 21:25 
GeneralMy vote of 4 Pin
PJ du Preez1-Aug-13 0:36
PJ du Preez1-Aug-13 0:36 
QuestionHow can I add a MouseDoubleClick Event to every item of the DsxDatagrid Pin
alvina09114-Jun-13 3:02
alvina09114-Jun-13 3:02 
AnswerRe: How can I add a MouseDoubleClick Event to every item of the DsxDatagrid Pin
christoph brändle14-Jun-13 8:30
christoph brändle14-Jun-13 8:30 
QuestionA TwoWay or OneWayToSource binding cannot work on the read-only property 'Error' of type Pin
jefers1234528-Feb-13 3:24
jefers1234528-Feb-13 3:24 
AnswerRe: A TwoWay or OneWayToSource binding cannot work on the read-only property 'Error' of type Pin
christoph brändle14-Jun-13 8:27
christoph brändle14-Jun-13 8:27 
QuestionAuto generate row number Pin
jackfrnz19-Jan-13 0:07
jackfrnz19-Jan-13 0:07 
GeneralMy vote of 5 Pin
jackfrnz4-Jan-13 18:00
jackfrnz4-Jan-13 18:00 
Questionhow to change background color of particular sdx data grid's row Pin
Mogya30-Dec-12 23:28
Mogya30-Dec-12 23:28 
GeneralMy vote of 5 Pin
Sperneder Patrick13-Dec-12 20:30
professionalSperneder Patrick13-Dec-12 20:30 
QuestionHow to get the data row data from dsx grid when check box selected Pin
Mogya12-Dec-12 0:01
Mogya12-Dec-12 0:01 
AnswerRe: How to get the data row data from dsx grid when check box selected Pin
christoph brändle12-Dec-12 22:54
christoph brändle12-Dec-12 22:54 
GeneralRe: How to get the data row data from dsx grid when check box selected Pin
Mogya13-Dec-12 17:57
Mogya13-Dec-12 17:57 
GeneralRe: How to get the data row data from dsx grid when check box selected Pin
christoph brändle16-Dec-12 12:19
christoph brändle16-Dec-12 12:19 
Questionhow to get user checked checkBox row Pin
Mogya6-Dec-12 23:40
Mogya6-Dec-12 23:40 
AnswerRe: how to get user checked checkBox row Pin
christoph brändle7-Dec-12 1:39
christoph brändle7-Dec-12 1:39 
GeneralMy vote of 3 Pin
sangar202019-Nov-12 22:25
sangar202019-Nov-12 22:25 
Questionhow to set Autogenerate column property Pin
sangar202019-Nov-12 1:06
sangar202019-Nov-12 1:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.