I thank Marlon Grech for his DatePicker control. There is more info on his DatePicker control here.
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()
{
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 (IsMouseOver2(editControl) && !AnythingElseInEditMode)
{
editControl.IsEditing = true;
}
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)
{
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.
protected override Size ArrangeOverride(Size finalSize)
{
if (_isVisible)
{
_comboBox.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
CheckIfValueChanged();
}
else {
_comboBox.Arrange(new Rect(0, 0, 0, 0));
}
return finalSize;
}
The CheckIfValueChanged Method
This method also resides in the EditComboBoxAdorner
class.
private void CheckIfValueChanged()
{
if (_currentValue != _comboBox.SelectedValue)
{
_currentValue = _comboBox.SelectedValue;
_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)
{
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.