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 ListView
s.
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)
{
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;
TextBlock tb = FindVisualDescendantByType<TextBlock>(item);
RemoveGroup(tb.Name);
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