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

Group Sort Adorner ListView

0.00/5 (No votes)
20 Sep 2008 4  
This is for anyone looking for a WPF sortable Listview with drag and drop grouping, drag and drop column reordering and column resizing.

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 ListViews.

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;

            //if((ColumnReorderOnly) or (Both ColumnReorder and Groupable))
            string groupName = "";
            if (sortCol != null)
                groupName = sortCol.GroupPropertyName;

            if (groupName != "")//(Both ColumnReorder and Groupable)
                DropForGrouping(sortCol);       
            else//if there is no groupName then it must be a columnReorder!!!
                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)
        {
            //Serialize the object that is added to the DesignerCanvas
            SerializeGroupObject
			(sortCol.GroupPropertyName, sortCol.Header.ToString());

            if (dataObject != null)
            {
                //Setup the Adorner Visual to drag
                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);
                
                //reset these to null when done
                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)
            {
                //This is how you reorder the columns
                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

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