ComboBox 编辑、排序、分组 ListView
本文展示了一个可排序的 WPF 编辑型 ListView,支持拖放分组、拖放列重新排序和列大小调整。
感谢 Marlon Grech 提供的 DatePicker 控件。有关他的 DatePicker 控件的更多信息,请点击此处。
引言
这是我为 ListView 撰写的 3 篇文章中的第 3 部分。第 1 部分在此处,第 2 部分在此处。第 3 部分将主要关注设置 ListView
的编辑功能。本文适用于任何寻求支持拖放分组、拖放列重新排序和列大小调整的 WPF 可排序编辑型 ListView 的人。还有一些随光标拖动的 Adorner,以及在悬停时出现的用于可编辑 TextBox
和 ComboBox
的 Adorner。我要特别感谢以下个人/网站:
有关 XP 和 Server 2003 上 ComboBox
弹出窗口问题的修复程序,请点击此处。
- http://blogs.interknowlogy.com/joelrumerman/archive/2007/04/03/12497.aspx
- Microsoft 的编辑 ListView 示例
- WPF Diagram Designer: Part 1
- 理解 WPF 中的视觉树和逻辑树
- 在 WPF ListView 中拖放项目。
关注点
新增和修改的类有 GroupSortAdornerListView
、EditControl
、EditControlAdorner
、EditBox
、EditBoxAdorner
、EditComboBox
和 EditComboBoxAdorner
。GroupSortAdornerListView
没有太多更改。我们为此类添加了一些方法,用于决定是否显示编辑 Adorner。
EditBox
和 EditComboBox
都继承自 EditControl
抽象类。这样做是为了减少重复代码,并使得在 GroupSortAdornerListView
类中通过一个引用点更方便地访问任一类中的属性/对象。我还将 EditControl
类作为 EditControlAdorner
属性的访问点。EditControl
的继承对应方都具有两种模式(正常模式和编辑模式),如 Microsoft 的编辑 ListView 示例中所述。
EditBoxAdorner
和 EditComboBoxAdorner
都继承自 EditControlAdorner
抽象类。这样做是为了减少重复代码,并使得通过一个引用点更方便地访问任一类中的属性/对象。这些 Adorner 从其控件中收集信息,然后将新更新的信息保存到此 ListView
控件的实际 ListView.ItemsSource
中。
有两种类型的编辑控件。一种是 TextBox
,另一种是 ComboBox
。有关 ComboBox
样式的更多信息,请点击此处。ComboBox
的实现比 TextBox
更具挑战性,因为它需要一个列表,并且您必须处理其弹出窗口,但您稍后会看到这些问题是如何解决的。
一些挑战包括何时显示编辑模式、何时切换回正常模式、何时选择 ListViewItem
以及如何更改绑定到 ListView.ItemsSource
的实际列表中的值。我可以继续下去,但您明白了。
Using the Code
现在这个应用程序有五个主要部分。首先是 Designer 控件,它处理 Canvas
控件上的自定义视觉设计器项,以查看/可视化哪些列已被分组。其次是 GridView
/ListView
控件,它们自定义 GridViewColumnHeaders
以适应排序和分组。第三是 Resources,主要用于 GridView
、ComboBox
和 ListView
编辑单元格。第四是自定义编辑单元格及其 Adorner。最后是显示自定义 ListView
和 Employee
类的窗口。
在本节中,我将讨论三个次要部分。首先,是 GroupSortAdornerListView
中添加的方法以及 GroupSortAdornerListView
类使用的两个抽象类。其次,是 ComboBox 编辑器类 EditComboBox
和 EditComboBoxAdorner
。最后我将讨论如何在 XAML 中使用这些新功能。您可以在此处阅读更多关于 EditBox
和 EditBoxAdorner
的内容。
GroupSortAdornerListView 中添加的方法
GroupSortAdornerListView
类中添加了一些方法。这个类是驱动类,它将 ListView
、DesignerCanvas
、GridViewColumn
以及 EditControl
和 EditControlAdorner
类连接起来。EditControl
和 EditControlAdorner
是抽象类,用于为 GroupSortAdornerListView
类创建单一引用点。
添加的方法
EnableDisableEditUnderCursor
- 这将编辑控件设置为进入或退出编辑模式。SelectListViewItemUnderCursor
- 这将在单击EditControlAdorner
时选择ListViewItem
。FindAllEditControlTypes
- 这会检索所有BaseType
为EditControl
的类型,并将它们存储在一个列表中。
EnableDisableEditUnderCursor 方法
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 方法
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 方法
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);
}
}
}
上面代码部分中所有**粗体**显示的地方都表明了抽象类的用处。如前所述,这减少了重复代码,并且您只需要在 GroupSortAdornerListView
类中一个引用点即可访问抽象类中所需的属性。
ComboBox 编辑器类
ComboBox
编辑器类的工作方式与此处的 EditBox
和 EditBoxAdorner
非常相似。我将解释使 Adorner 编辑器更易于使用的行为。这些行为大多由上面 EnableDisableEditUnderCursor
方法中所示的 GroupSortAdornerListView
类控制。
主要行为是
- 当鼠标悬停时显示
ComboBoxEditor
- 这使得访问编辑控件和更改值变得更容易。 - 任何时候只允许一个
EditControlAdorner
可见。 - 如果
EditComboBoxAdroner
的弹出窗口已打开,则保持编辑模式。 - 无论何时单击行,包括单击
EditControlAdorner
时,都会选择ListViewItem
。 - 当修饰器控件中的当前值发生更改时,更新
ListView.ItemsSource
的绑定列表。检查ArrangeOverride
方法中当前值是否已更改,因为这是布局更新的第二遍,此时控件的值已更新。有关ArrangeOverride
方法的更多信息,请点击此处。
EditComboBox
和 EditComboBoxAdorner
之间的关系是紧密耦合的,并且彼此了解。这样做是为了便于通信,并且在 GroupSortAdornerListView
类中再次实现了一个单一的引用点。我解释这一点的原因是关于 ListView.ItemsSource
的绑定列表是如何更新的。我将在下面的代码中向您展示它是如何完成的。
ArrangeOverride 方法
此方法位于 EditComboBoxAdorner
类中。
/// <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;
}
CheckIfValueChanged 方法
此方法也位于 EditComboBoxAdorner
类中。
//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);
}
}
UpdateListViewItem 方法
_editComboBox.ValueChanged
调用此方法。该方法使用反射来设置/更新 ListViewItem.ItemsSource
绑定列表的值。
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);
}
}
在 XAML 中使用新功能
这是 TextBox
编辑器在 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>
这是 ComboBox
编辑器在 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>
运行应用程序
我清除了其他窗口和类以减少混淆。因此,您只需要解压缩并按 F5 即可运行应用程序。
结论
这个解决方案对我来说效果很好。但是,我希望在以后的更新中对其进行一些调整,但这应该能让您现在走上正确的轨道。至此,三篇文章的最后一篇就结束了。尽管如此,我以后还在考虑添加一个 DatePicker 编辑 Adorner,并同时更新本文以添加此功能。
修订历史
- 文章创建于 - 2008 年 9 月 20 日。
- 添加了
DatePicker
控件 - 2008 年 11 月 1 日。