Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Grouping and Sorting ListView

0.00/5 (No votes)
20 Sep 2008 2  
Shows a couple of ways to create a Sortable ListView with Drag Drop Grouping
Sample Image - maximum width is 600 pixels

Introduction

This is for anyone looking for a WPF Sortable Listview with Drag and Drop Grouping. This is my first article, so be gentle. This is Part 1 of 3. Part 2 (Drag/Drop Adorners) can be found here and Part 3 (ComboBox Editing) can be found here. I wanted to give special thanks to these individuals/sites:

Points of Interest

Well, I think I am going to just generalize on a lot of the code because the references above do a great job of explaining how each piece works. I have many comments in the code as well.

I wanted to point out that these solutions are not exactly what I had in mind, but they work well for now. Of course, unless you wanted to customize the GridViewHeaderRowPresenter, which I have not figured out yet.

Right now, I am just customizing the ListView and GridViewColumn classes along with creating your own styling of the ListView. More on styling can be found here.

The main problems were trying to "share" the Left mouse button events with the GridViewHeaderRowPresenter. There are 2 ways I show how to share these events. One way is using the GroupSortModeListView class and the other is using the GroupSortListView class. Each has its pros and cons.

These two classes, GroupSortListView and GroupSortModeListView, are the driver classes that hook everything together between the ListView, DesignerCanvas, and the GridViewColumn.

GroupSortModeListView Class

With this class, you can turn on and off Grouping by right clicking for the "Allow Grouping" Context menu. This will allow the GridViewHeaderRowPresenter access to all the events which mean the resizing, column reordering and "IsPressed" styling will work. The only downfall is that you need to right click on the ListView to enable grouping through the Context Menu. There are a couple new properties to this class that I will show, but the methods are the key to these two classes.

Properties
  • GroupSortListViewColumnHeaderSortedAscendingStyle - Gets/sets the style name of the column that is Sorted ascending direction.
  • GroupSortListViewColumnHeaderSortedDescendingStyle - Gets/sets the style name of the column that is Sorted descending direction.
  • GroupSortListViewColumnHeaderNotSortedStyle - Gets/sets the style name of the column that is not Sorted.
  • GroupMenuItem - Gets/sets the Group MenuItem in the Context menu and allows access for anyone to hookup to this event if needed.
  • InGroupingMode - Gets/sets the GroupMenuItem.IsChecked in the Context menu determining if we are in Group Mode or not.
Important Methods
  • GridViewColumnHeaderClickHandler - This handles changing the styles of the column headers when sorting!!
  • GridViewColumnHeaderPreviewMouseDownHandler - This handles hooking up to the DesignerCanvas's RemoveGroup event and starting the DragDrop and grouping when the InGroupingMode is true.
  • HookupDesignerCanvasRemoveGroupEvent - This handles the DesignerCanvas's RemoveGroup event and removes the groups. More explanation has been provided below in the Using the Code section.

GroupSortListView Class

With this class the first thing you will notice is there is no Grouping mode. That is because Grouping is always on - as is the Sorting. The big downfall this way is that the columns you have sorting and grouping enabled, your column resizing and reordering doesn't work along with the "IsPressed" styling. Simply because it "Steals" and handles the events away from the GridViewHeaderRowPresenter.

DependencyProperties
  • GroupSortListViewColumnHeaderSortedAscendingStyle - Gets/sets the style name of the column that is Sorted ascending direction.
  • GroupSortListViewColumnHeaderSortedDescendingStyle - Gets/sets the style name of the column that is Sorted descending direction.
  • GroupSortListViewColumnHeaderNotSortedStyle - Gets/sets the style name of the column that is not Sorted.
Important Methods
  • GridViewColumnHeaderPreviewMouseDownHandler - This handles hooking up to the DesignerCanvas's RemoveGroup event and changing the styles of the column headers when sorting!!
  • GridViewColumnHeaderPreviewMouseMoveHandler - This handles starting the DragDrop and grouping.
  • HookupDesignerCanvasRemoveGroupEvent - This handles the DesignerCanvas's RemoveGroup event and removes the groups. More explanation has been provided below in the Using the Code section.

Using the Code

There are 4 main parts to this app. First are the Designer controls which deal with the custom visual designer items on the Canvas control to see/visualize what columns have been grouped. Second are the GridView/ListView controls that customize the GridViewColumnHeaders to accommodate for the sorting and grouping. Third are the Resources, mainly just for the GridView. Finally, the windows that display the custom ListViews.

Here is how I set up the custom GridviewColumn called GroupSortGridViewColumn and the custom ListView I called GroupSortModeListView:

<MyListView:GroupSortModeListView x:Name="sortableListView1" AllowDrop="False" 
	       HorizontalAlignment="Stretch" Grid.Row="1"
	       Margin="0,0,0,0" VerticalAlignment="Stretch" 
			IsSynchronizedWithCurrentItem="True"               
	       GroupSortModeListViewColumnHeaderSortedAscendingStyle=
						"UpArrowHeaderStyle"
	       GroupSortModeListViewColumnHeaderSortedDescendingStyle=
						"DownArrowHeaderStyle"
	       GroupSortModeListViewColumnHeaderNotSortedStyle="NoArrowHeaderStyle">
	 <ListView.GroupStyle>
	       <GroupStyle>
	               <GroupStyle.ContainerStyle>
	                       <Style TargetType="{x:Type GroupItem}">
	                       <Setter Property="Margin" Value="0,0,0,5"/>
	                       <Setter Property="Template">
	                       <Setter.Value>
	                               <ControlTemplate TargetType=
							"{x:Type GroupItem}">
	                               <Expander IsExpanded="True" 
						BorderThickness="0,0,0,1">
	                               <Expander.Header>
	                               <DockPanel>
	                               <TextBlock FontWeight="Bold" 
					Text="{Binding Path=Name}" Margin="5,0,0,0"
	                                       Width="100"/>
	                               <TextBlock FontWeight="Bold" 
					Text="{Binding Path=ItemCount}"/>
	                               <TextBlock FontWeight="Bold" Text=" Items"/>
	                               </DockPanel>
	                               </Expander.Header>
	                               <Expander.Content>
	                                       <ItemsPresenter />
	                               </Expander.Content>
	                               </Expander>
	                               </ControlTemplate>
	                       </Setter.Value>
	                       </Setter>
	                       </Style>
	               </GroupStyle.ContainerStyle>
	       </GroupStyle>
	</ListView.GroupStyle>
	<MyListView:GroupSortModeListView.View>
	       <GridView>
	       <MyListView:GroupSortGridViewColumn Width="93" Header="Print Report">
	               <MyListView:GroupSortGridViewColumn.CellTemplate>
	               <DataTemplate>
	                       <Button Content="Print" Height="40" Width="80" 
	                                       HorizontalAlignment="Center" />
	               </DataTemplate>
	               </MyListView:GroupSortGridViewColumn.CellTemplate>
	       </MyListView:GroupSortGridViewColumn>
	       <MyListView:GroupSortGridViewColumn Width="70" Header="First Name" 
	               DisplayMemberBinding="{Binding FirstName}" />
	       <MyListView:GroupSortGridViewColumn Width="70" Header="Last Name" 
	               SortPropertyName="Version" 
			DisplayMemberBinding="{Binding LastName}"/>
	       <MyListView:GroupSortGridViewColumn Width="95" Header="Project Number" 
	               SortPropertyName="ProjectNumber" 
			DisplayMemberBinding="{Binding ProjectNumber}"/>
	       <MyListView:GroupSortGridViewColumn Width="100" 
					Header="Project Quantity" 
	               SortPropertyName="NumberOfProjects" 
			DisplayMemberBinding="{Binding NumberOfProjects}"/>
	       <MyListView:GroupSortGridViewColumn Width="120" 
					Header="Project Sequence" 
	               SortPropertyName="ProjectsSequenceNumber" 
			GroupPropertyName="ProjectsSequenceNumber" 
	               DisplayMemberBinding="{Binding ProjectsSequenceNumber}"/>
	       <MyListView:GroupSortGridViewColumn Width="100" Header="Employee Dept" 
	               SortPropertyName="EmployeeDept" GroupPropertyName="EmployeeDept" 
	               DisplayMemberBinding="{Binding EmployeeDept}"/>
	       </GridView>
	</MyListView:GroupSortModeListView.View>
</MyListView:GroupSortModeListView>

There is also some styling involved of course.

Canvas/ListView Communication

Everything on the Grouping and Sorting side is packaged and handled by the GroupSortModeListView. The way the communication works is that the GroupSortModeListView hooks up to the DesignerCanvas's RemoveGroup event here:

private void HookupDesignerCanvasRemoveGroupEvent()
{
	if (!RemoveGroupEventHookedup)
	{
	       //find the DesignerCanvas used and attach to its "RemoveGroup" event!!
           DesignerCanvas dc = FindVisualDescendantByType<DesignerCanvas>
			(this as DependencyObject) 
	       as DesignerCanvas;
           dc.RemoveGroup += new RemoveGroupHandler(dc_RemoveGroup);
           RemoveGroupEventHookedup = true;
	}
}

And the DesignerCanvas sends the event here:

private UIElement SendRemoveGroupEvent(UIElement element)
{
	DesignerItem item;
       if (element is DesignerItem)
	{
	       item = element as DesignerItem;
               //need to find the descendant of type TextBlock here!!!
               TextBlock tb = FindVisualDescendantByType<TextBlock>(item);
               //tb.Name contains the groupName we need to remove the group 
	       			//with this event to the window!!!
               RemoveGroup(tb.Name);//Send Event
               return item;
	}
       return null;
}

This GroupSortModeListView also has a ContextMenu (you can see it by right clicking on the ListView in Window2) that "Allows Grouping". When you turn this on the columns that have Grouping setup will not sort, reorder, or resize because I have to "steal" the event away from the GridViewHeaderRowPresenter.

The GroupSortListView (this is implemented in Window3) is another "option" if you wish that allows both sorting and grouping at the same time without the ContextMenu. However, these columns will not reorder or resize either because I have to "steal" the event away from the GridViewHeaderRowPresenter.

Running the App

The StartupWindow has three buttons that open each of the other windows (Window1, Window2, and Window3).

Conclusion

This is a good working solution to has both sorting and drag and drop grouping on the ListView for what I need it for, but not the best way. Ideally, we would customize the GridViewHeaderRowPresenter to have all the functionality work exactly as you want it to. I am always open to anyone who has other ideas...

Revision History

  • Article created - 8/15/2008
  • Article added as part 1 of a 3 part series - 9/1/2008 (Part 2 here)
  • Part 3 of the series is here

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here