Introduction
This is Part 2 of 3 articles I am doing for the ListView
. Part 1 (Grouping and Sorting) is found here. And Part 3 of the series (ComboBox
Editing) is here. This is for anyone looking for a WPF sortable Listview
with drag and drop grouping, drag and drop column reordering and column resizing. There is also an Adorner
that drags around with the cursor. 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 also have many comments in the code as well.
I also wanted to point out that the GroupSortAdornerListView
is the solution that I had in mind, and works very well. However, there is a bit more work to do to only allow one DesignerItem
per Column on the DesignerCanvas
, which can be done later.
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 GroupSortAdornerListView
class is new, but a similar class to the GroupSortListView
and the GroupSortModeListView
classes I spoke about in Part 1.
The main problem I had was trying to "share" the Left mouse button events with the Sorting, Grouping, Resizing, and Reordering of the columns in the GroupSortAdornerListView
. To do this, you must override 3 methods in the ListView
class.
The 3 methods are:
OnPreviewMouseLeftButtonDown
OnPreviewMouseMove
OnPreviewMouseLeftButtonUp
Here are the properties and some of the important methods of the GroupSortAdornerListView
class. This class is like the other 2 classes because it too is the driver class that hooks everything together between the ListView
, DesignerCanvas
, and the GridViewColumn
.
GroupSortAdornerListView Class
With this class, you have full functionality with the columns. Your grouping drag/drop, column reordering drag/drop, sorting, and resizing all work nicely together. The biggest key to have them all work together is to keep track of 2 events. One is whether or not you can drag, and the second is to know when you are dragging. There are further explanations below with important properties and methods for this class.
Properties
GroupSortListViewColumnHeaderSortedAscendingStyle
- Gets/sets the style name of the column that is Sorted in ascending direction.
GroupSortListViewColumnHeaderSortedDescendingStyle
- Gets/sets the style name of the column that is Sorted in descending direction.
GroupSortListViewColumnHeaderNotSortedStyle
- Gets/sets the style name of the column that is not Sorted
ColumnUnderDragCursor
- Gets/sets the GridViewColumnHeader
that is under the Drag cursor so we can highlight it when we drag over it. It is also used to find the index number of the column when we drop for column Reorder
.
ColumnBeingDragged
- Gets/sets the GridViewColumnHeader
that is being dragged. Also, obtains information for the column reorder and grouping.
CanStartDragOperation
- Gets the bool
that tells us if we can start dragging or not.
Important Methods
OnPreviewMouseLeftButtonDown
- This method tells us if dragging is allowed, and initializes needed objects to start the drag process.
OnPreviewMouseMove
- This handles the Adorner
, and if you can drag/drop both grouping and reordering or just reordering.
OnPreviewMouseLeftButtonUp
- This handles changing the styles of the column headers when sorting.
MouseDragged
- Tells us if the mouse has been dragged more than one pixel.
DropForGrouping
- Handles the dropping on to the DesignerCanvas
for grouping the columns.
DropForColumnReorderOnly
- Handles the Reordering of the columns.
GetColumnHeaderUnderDragCursor
- This gets the column header under the drag cursor using a custom IsMouseOver2
method.
GetThumbUnderDragCursor
- Gets the thumb under the drag cursor using the custom IsMouseOver2
method.
InitializeAdornerLayer
- Sets up the Adorner
used when dragging the column.
UpdateDragAdornerLocation
- Updates the location of the Adorner
while dragging.
Using the Code
As stated in Part 1, 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 one of the styles are setup for the GridViewColumnHeader
. There are three styles. A descending, an ascending and one for a column that is not sorted.
<Style x:Key="UpArrowHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<Grid>
<Border Name="HeaderBorder"
BorderThickness="0,1,0,1"
BorderBrush="{StaticResource NormalBorderBrush}"
Background="{StaticResource LightBrush}"
Padding="2,0,2,0">
<StackPanel x:Name="spHeader"
Orientation="Horizontal">
<ContentPresenter Name="HeaderContent"
Margin="0,0,0,1"
VerticalAlignment="{TemplateBinding
VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding
SnapsToDevicePixels}"/>
<Path x:Name="arrow"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StrokeThickness="1" Fill="Gray"
Data="M 5,10 L 15,10 L 10,5 L 5,10"/>
</StackPanel>
</Border>
<Thumb x:Name="PART_HeaderGripper"
HorizontalAlignment="Right"
Margin="0,0,-9,0"
Style="
{StaticResource GridViewColumnHeaderGripper}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="HeaderBorder"
Property="Background"
Value="{StaticResource NormalBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="HeaderBorder"
Property="Background"
Value="{StaticResource PressedBrush}"/>
<Setter TargetName="HeaderContent"
Property="Margin" Value="1,1,0,0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource
{x:Static
SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="mlv:
ListViewColumnHeaderDragState.IsUnderDragCursor"
Value="True">
<Setter Property="Foreground" Value="red"/>
<Setter TargetName="HeaderBorder" Property="Background"
Value="{StaticResource DragOverBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Role" Value="Padding">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<Border Name="HeaderBorder"
BorderThickness="0,1,0,1"
BorderBrush="{StaticResource NormalBorderBrush}"
Background="{StaticResource LightBrush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
OnPreviewMouseMove Dragging
This section is the main difference between how everything was implemented in part 1 and how it is implemented here. Before I stated, you needed to keep track of whether or not you can drag, and to know when you are dragging. This overridden method does just that:
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
if( !this.CanStartDragOperation )
return;
if (!MouseDragged(e.GetPosition(this)))
return;
isDragging = true;
GroupSortGridViewColumn sortCol = ColumnBeingDragged.Column as
GroupSortGridViewColumn;
string groupName = "";
if (sortCol != null)
groupName = sortCol.GroupPropertyName;
if (groupName != "")
DropForGrouping(sortCol);
else
DropForColumnReorderOnly();
}
DropForGrouping Method
This sets up for both the column grouping and the column reordering. First, it sets up the serialized object for the column grouping. Second, it sets up the Adorner
for dragging. Lastly, it drops and depending on what the DragDropEffects
are, it may or may not group by that column.
private void DropForGrouping(GroupSortGridViewColumn sortCol)
{
SerializeGroupObject
(sortCol.GroupPropertyName, sortCol.Header.ToString());
if (dataObject != null)
{
AdornerLayer layer2 = InitializeAdornerLayer(ColumnBeingDragged);
DragDropEffects allowedEffects =
DragDropEffects.Copy | DragDropEffects.Move;
DragDropEffects dde = DragDrop.DoDragDrop
(this, dataObject, allowedEffects);
if (dde == DragDropEffects.Copy)
Group(sortCol.GroupPropertyName);
if (layer2 != null)
layer2.Remove(this.dragAdorner);
ColumnUnderDragCursor = null;
ColumnBeingDragged = null;
}
}
DropForColumnReorderOnly Method
This sets up for only the column reorder. It first sets up the Adorner
and then drops for the column reorder.
private void DropForColumnReorderOnly()
{
AdornerLayer layer2 = InitializeAdornerLayer(ColumnBeingDragged);
DragDropEffects allowedEffects = DragDropEffects.Move;
DragDropEffects dde = DragDrop.DoDragDrop
(this, "Something", allowedEffects);
if (layer2 != null)
layer2.Remove(this.dragAdorner);
ColumnUnderDragCursor = null;
ColumnBeingDragged = null;
}
Overridden OnDrop Method
This is where the column reorder happens when the drop occurs on the GridViewColumnHeader
. You can reorder the columns by using their index numbers and the Move
method on the column like so:
protected override void OnDrop(DragEventArgs e)
{
if (ColumnBeingDragged != null && ColumnUnderDragCursor != null)
{
GridViewColumn colDrag = ColumnBeingDragged.Column;
GridViewColumn colDrop = ColumnUnderDragCursor.Column;
e.Effects = DragDropEffects.Move;
int colDragged =
gridViewHeaderRowPresenter.Columns.IndexOf(colDrag);
int colDroppedOn =
gridViewHeaderRowPresenter.Columns.IndexOf(colDrop);
gridViewHeaderRowPresenter.Columns.Move(colDragged, colDroppedOn);
}
}
Running the App
The StartupWindow
has 4 buttons that open each of the other windows (Window1
, Window2
, Window3
, and Window4
). The newest button is the GroupSort Adorner Window
which is the button with this new functionality.
Conclusion
This is the solution I was looking for. I am planning on doing a part 3 with ListView
editing in a couple of weeks to conclude this series.
Revision History
- 9/1/2008 - Article created
- 9/20/2008 - Added Part 3 of the series here