65.9K
CodeProject 正在变化。 阅读更多。
Home

ComboBox 编辑、排序、分组 ListView

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (9投票s)

2008 年 9 月 20 日

CPOL

6分钟阅读

viewsIcon

82453

downloadIcon

4715

本文展示了一个可排序的 WPF 编辑型 ListView,支持拖放分组、拖放列重新排序和列大小调整。

感谢 Marlon Grech 提供的 DatePicker 控件。有关他的 DatePicker 控件的更多信息,请点击此处

DatePickerListView.png

引言

这是我为 ListView 撰写的 3 篇文章中的第 3 部分。第 1 部分在此处第 2 部分在此处。第 3 部分将主要关注设置 ListView 的编辑功能。本文适用于任何寻求支持拖放分组、拖放列重新排序和列大小调整的 WPF 可排序编辑型 ListView 的人。还有一些随光标拖动的 Adorner,以及在悬停时出现的用于可编辑 TextBoxComboBox 的 Adorner。我要特别感谢以下个人/网站:

有关 XP 和 Server 2003 上 ComboBox 弹出窗口问题的修复程序,请点击此处

关注点

新增和修改的类有 GroupSortAdornerListViewEditControlEditControlAdornerEditBoxEditBoxAdornerEditComboBoxEditComboBoxAdornerGroupSortAdornerListView 没有太多更改。我们为此类添加了一些方法,用于决定是否显示编辑 Adorner。

EditBoxEditComboBox 都继承自 EditControl 抽象类。这样做是为了减少重复代码,并使得在 GroupSortAdornerListView 类中通过一个引用点更方便地访问任一类中的属性/对象。我还将 EditControl 类作为 EditControlAdorner 属性的访问点。EditControl 的继承对应方都具有两种模式(正常模式和编辑模式),如 Microsoft 的编辑 ListView 示例中所述。

EditBoxAdornerEditComboBoxAdorner 都继承自 EditControlAdorner 抽象类。这样做是为了减少重复代码,并使得通过一个引用点更方便地访问任一类中的属性/对象。这些 Adorner 从其控件中收集信息,然后将新更新的信息保存到此 ListView 控件的实际 ListView.ItemsSource 中。

有两种类型的编辑控件。一种是 TextBox,另一种是 ComboBox。有关 ComboBox 样式的更多信息,请点击此处ComboBox 的实现比 TextBox 更具挑战性,因为它需要一个列表,并且您必须处理其弹出窗口,但您稍后会看到这些问题是如何解决的。

一些挑战包括何时显示编辑模式、何时切换回正常模式、何时选择 ListViewItem 以及如何更改绑定到 ListView.ItemsSource 的实际列表中的值。我可以继续下去,但您明白了。

Using the Code

现在这个应用程序有五个主要部分。首先是 Designer 控件,它处理 Canvas 控件上的自定义视觉设计器项,以查看/可视化哪些列已被分组。其次是 GridView/ListView 控件,它们自定义 GridViewColumnHeaders 以适应排序和分组。第三是 Resources,主要用于 GridViewComboBoxListView 编辑单元格。第四是自定义编辑单元格及其 Adorner。最后是显示自定义 ListViewEmployee 类的窗口。

在本节中,我将讨论三个次要部分。首先,是 GroupSortAdornerListView 中添加的方法以及 GroupSortAdornerListView 类使用的两个抽象类。其次,是 ComboBox 编辑器类 EditComboBoxEditComboBoxAdorner。最后我将讨论如何在 XAML 中使用这些新功能。您可以在此处阅读更多关于 EditBoxEditBoxAdorner 的内容。

GroupSortAdornerListView 中添加的方法

GroupSortAdornerListView 类中添加了一些方法。这个类是驱动类,它将 ListViewDesignerCanvasGridViewColumn 以及 EditControlEditControlAdorner 类连接起来。EditControlEditControlAdorner 是抽象类,用于为 GroupSortAdornerListView 类创建单一引用点。

添加的方法
  • EnableDisableEditUnderCursor - 这将编辑控件设置为进入或退出编辑模式。
  • SelectListViewItemUnderCursor - 这将在单击 EditControlAdorner 时选择 ListViewItem
  • FindAllEditControlTypes - 这会检索所有 BaseTypeEditControl 的类型,并将它们存储在一个列表中。
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 编辑器类的工作方式与此处EditBoxEditBoxAdorner 非常相似。我将解释使 Adorner 编辑器更易于使用的行为。这些行为大多由上面 EnableDisableEditUnderCursor 方法中所示的 GroupSortAdornerListView 类控制。

主要行为是

  • 当鼠标悬停时显示 ComboBoxEditor - 这使得访问编辑控件和更改值变得更容易。
  • 任何时候只允许一个 EditControlAdorner 可见。
  • 如果 EditComboBoxAdroner 的弹出窗口已打开,则保持编辑模式。
  • 无论何时单击行,包括单击 EditControlAdorner 时,都会选择 ListViewItem
  • 当修饰器控件中的当前值发生更改时,更新 ListView.ItemsSource 的绑定列表。检查 ArrangeOverride 方法中当前值是否已更改,因为这是布局更新的第二遍,此时控件的值已更新。有关 ArrangeOverride 方法的更多信息,请点击此处

EditComboBoxEditComboBoxAdorner 之间的关系是紧密耦合的,并且彼此了解。这样做是为了便于通信,并且在 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 日。
© . All rights reserved.