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

ComboBox Editing, Sorting, Grouping ListView

0.00/5 (No votes)
1 Nov 2008 2  
This article shows a WPF sortable editing ListView with drag and drop grouping, drag and drop column reordering, and column resizing.

I thank Marlon Grech for his DatePicker control. There is more info on his DatePicker control here.

DatePickerListView.png

Introduction

This is Part 3 of 3 articles I am doing for the ListView. Part 1 is available here and Part 2 is here. This part 3 will focus mainly on setting up editing for the ListView. This is for anyone looking for a WPF sortable editing listview with drag and drop grouping, drag and drop column reordering and column resizing. There are also Adorners that drag around with the cursor, along with Adorners for the editable TextBox and ComboBox that appear when they are hovered over. I wanted to give special thanks to these individuals/sites:

There is a fix for the ComboBox's popup issue on XP and Server 2003 here.

Points of Interest

The classes added and changed are GroupSortAdornerListView, EditControl, EditControlAdorner, EditBox, EditBoxAdorner, EditComboBox, and EditComboBoxAdorner. There were not many changes to the GroupSortAdornerListView. We added some methods to this class that helps out on deciding whether or not to show the Editing Adorner.

Both EditBox and EditComboBox inherit from the EditControl abstract class. This is done to reduce duplicate code, and to make it easier to access the properties/objects in either class with one reference point in the GroupSortAdornerListView class. I also made the EditControl class the access point for the EditControlAdorner properties as well. The EditControl's inheriting counterparts both have two modes (normal mode and editing mode), as explained in Microsoft's Editing ListView Sample.

Both EditBoxAdorner and EditComboBoxAdorner inherit from the EditControlAdorner abstract class. This is done to reduce duplicate code, and to make it easier to access the properties/objects in either class with one reference point. These Adorners gather the information from their controls, and then save the newly updated information to the actual ListView.ItemsSource for this ListView control.

There are two types of edit controls. One is the TextBox and the other is the ComboBox. More on ComboBox styling can be found here. The ComboBox was a bit more challenging to implement than the TextBox because it needs a list and it has a popup that you must deal with, but you will later see how these problems were dealt with.

Some of the challenges were when to show the Editing mode, when to switch back to Normal mode, when to select the ListViewItem, and how to change the value in the actual list bound to the ListView.ItemsSource. I could keep going, but you get the picture.

Using the Code

There are now five 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 for the GridView, ComboBox, and the ListView edit cells. Fourth is the custom edit cells and their adorners. Finally, the window that displays the custom ListView and the Employee class.

In this section, I will talk about three minor parts. First, the added methods in the GroupSortAdornerListView along with the two abstract classes that the GroupSortAdornerListView class uses. Second, the ComboBox editor classes EditComboBox and EditComboBoxAdorner. The last thing I will talk about is how to consume/use these new features in XAML. You can read more on the EditBox and the EditBoxAdorner here.

Added Methods in GroupSortAdornerListView

There were a few methods added to the GroupSortAdornerListView class. This class is the driver class that hooks everything together between the ListView, DesignerCanvas, GridViewColumn, and the EditControl and EditControlAdorner classes. EditControl and EditControlAdorner are abstract classes to create a single point of reference for the GroupSortAdornerListView class.

Added Methods
  • EnableDisableEditUnderCursor - This sets the edit controls into and out of edit mode.
  • SelectListViewItemUnderCursor - This will select the ListViewItem when clicking on the EditControlAdorner.
  • FindAllEditControlTypes - This retrieves all the types with a BaseType of EditControl and stores them in a list.
EnableDisableEditUnderCursor Method
 private void EnableDisableEditUnderCursor()
 {
   //The bottom code is where the common code
   //in both the "EditControl" and the 
   //"EditControlAdorner" abstract classes come in handy..

   //we can only have ONE EditControl enabled at one time.
   //we need to know when the combobox popup is open too.
            
   FindAllEditControlTypes();

   List<visual> list = new List<visual>();
   FindAllElements2(typeof(EditControl), this, list
   bool AnythingElseInEditMode = false;

   foreach (Type typ in EditControlTypeList)
   {
     List<visual> elementList = new List<visual>();
     FindAllElements2(typ, this, elementList);
     foreach (Visual element in elementList)
     {
       EditControl editControl = element as EditControl;
       if (editControl != null)
       {
         if (editControl.IsEditing)
              AnythingElseInEditMode = true;
       }
     }
     foreach (Visual element in elementList)
     {
       EditControl editControl = element as EditControl;
       if (editControl != null)
       {
         //if the mouse is over the editcontrol and nothing else is in 
         //edit mode then set to true.
         if (IsMouseOver2(editControl) && !AnythingElseInEditMode)
         {                            
           editControl.IsEditing = true;

         }
         //If mouse is NOT over the EditControlAdorner and the Adorner's 
         //drop down is not open, then set to false
         else if (!IsMouseOver2(editControl.Adorner) && 
                  !editControl.Adorner.IsDropDownOpen)
           editControl.IsEditing = false;
       }
     }
  }
}
SelectListViewItemUnderCursor Method
private ListViewItem SelectListViewItemUnderCursor()
{
    List<visual> elementList = new List<visual>();
    FindAllElements2(typeof(ListViewItem), this, elementList);
    foreach (Visual element in elementList)
    {
        ListViewItem lvi = element as ListViewItem;
        if (lvi != null)
        {
            if (IsMouseOver2(lvi))
                lvi.IsSelected = true;
            else
                lvi.IsSelected = false;
         }
    }

    return null;
}
FindAllEditControlTypes Method
private void FindAllEditControlTypes()
{
    if (EditControlTypeList == null)
    {
        EditControlTypeList = new List<type>();
        Assembly assem = this.GetType().Assembly;
        Type[] typs = assem.GetTypes();
        foreach (Type typ in typs)
        {
            // find all types here and save them in a list
            if (typ.BaseType == typeof(EditControl))
                EditControlTypeList.Add(typ);
        }
    }
}

Everything that is bold in the code sections above is where the use of abstract classes is very helpful. As stated before, this reduces duplicate code, and you only need one reference point in the GroupSortAdornerListView class to access the properties needed in the abstract class.

ComboBox Editor Classes

The ComboBox editor classes work very similar to the EditBox and the EditBoxAdorner here. I will explain the behaviors that make the Adorner editors easier to use. Most of these behaviors are controlled in the GroupSortAdornerListView class shown in the EnableDisableEditUnderCursor method above.

The main behaviors are:

  • Show the ComboBoxEditor when mouse hovers over it - this makes it easier to access the edit control and change the values.
  • Only allow one EditControlAdorner visible at any time.
  • If the EditComboBoxAdroner's popup is open, stay in edit mode.
  • Select the ListViewItem anytime the row is clicked on, including when you click on the EditControlAdorner.
  • Update the ListView.ItemsSource's bound list when the current value in the adorner control has changed. Check to see if the current value has changed in the ArrangeOverride method, because this is the second pass of the layout update, and by this time, the control's value has been updated. For more on the ArrangeOverride method, click here.

The relationship between EditComboBox and EditComboBoxAdorner is tightly coupled and know about each other. This is done for ease of communication, and again for a single reference point in the GroupSortAdornerListView class. The reason I am explaining this is because of how the ListView.ItemsSource's bound list gets updated. I will show you how it was done in the below code.

The ArrangeOverride Method

This method resides in the EditComboBoxAdorner class.

/// <summary>
/// override function to arrange elements.
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
    if (_isVisible)
    {
        _comboBox.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));

        CheckIfValueChanged();
    }
    else // if is not is editable mode, no need to show elements.
    {
        _comboBox.Arrange(new Rect(0, 0, 0, 0));
    }
    return finalSize;
}
The CheckIfValueChanged Method

This method also resides in the EditComboBoxAdorner class.

//This is used to Update your underlying ItemsSource for the ListView..
private void CheckIfValueChanged()
{
    if (_currentValue != _comboBox.SelectedValue)
    {
        _currentValue = _comboBox.SelectedValue;
        
        //This is where the EditComboBox is referenced
        _editComboBox.ValueChanged(_currentValue);
    }
}
The UpdateListViewItem Method

_editComboBox.ValueChanged calls this method. The method uses Reflection to set/update the value of the ListViewItem.ItemsSource's binding list.

protected void UpdateListViewItem(object newValue)
{
    //May need some null checks in here later.
    object emp = _listViewItem.Content as object;
    
    foreach (PropertyInfo prp in emp.GetType().GetProperties())
    {
        if (prp.Name.ToLower() == this.DisplayMemberBindingName.ToLower())
            prp.SetValue(emp, newValue, null);
    }
}

Consuming New Features in XAML

This is how the TextBox editor is implemented in the GridView:

<MyListView:GroupSortGridViewColumn Header="First Name" Width="70" >
  <MyListView:GroupSortGridViewColumn.CellTemplate>
    <DataTemplate>
      <MyListView:EditBox DisplayMemberBindingName="FirstName" Height="25" 
      Value="{Binding FirstName}"/>
    </DataTemplate>
  </MyListView:GroupSortGridViewColumn.CellTemplate>
</MyListView:GroupSortGridViewColumn>

This is how the ComboBox editor is implemented in the GridView:

<MyListView:GroupSortGridViewColumn Width="100" Header="Employee Dept" 
    SortPropertyName="EmployeeDept" 
    GroupPropertyName="EmployeeDept" >
 <MyListView:GroupSortGridViewColumn.CellTemplate>
  <DataTemplate>
   <MyListView:EditComboBox DisplayMemberBindingName="EmployeeDept" Height="25" 
    ValueList="{Binding Path=EmployeeDeptList, 
    RelativeSource={RelativeSource AncestorType={x:Type MyListView:Window1}}, 
        Mode=OneWay}" 
    Value="{Binding EmployeeDept}"  />
  </DataTemplate>
 </MyListView:GroupSortGridViewColumn.CellTemplate>
</MyListView:GroupSortGridViewColumn>

Running the App

I purged the other windows and classes to reduce confusion. So, all you need to do is unzip and press F5 to run the app.

Conclusion

This solution works well for me. However, I would like to tweak it a bit more in some updates later, but this should set you on the right track for now. This concludes the last of the three articles. Although, later on I am thinking of adding a DatePicker Editing Adorner and update this article for that addition as well.

Revision History

  • Article created - 9/20/2008.
  • Added the DatePicker control - 11/1/2008.

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