• WPF实现带全选复选框的列表控件


    本文将说明如何创建一个带全选复选框的列表控件。其效果如下图:

    这个控件是由一个复选框(CheckBox)与一个 ListView 组合而成。它的操作逻辑:

    • 当选中“全选”时,列表中所有的项目都会被选中;反之,取消选中“全选”时,所有项都会被取消勾选。
    • 在列表中选中部分数据项目时,“全选”框会呈现不确定状态(Indetermine)。

    由此看出,“全选”复选框与列表项中的复选框达到了双向控制的效果。

    其设计思路:首先,创建自定义控件(CheckListView),在其 ControlTemplate 中定义 CheckBox 和 ListView,并为 ListView 设置 ItemTemplate,在其中增加 CheckBox 控件,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <ControlTemplate TargetType="{x:Type control:CheckListView}">
              <Grid Background="{TemplateBinding Background}">
                <Grid.RowDefinitions>
                  <RowDefinition Height="Auto" />
                  <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                 
                <CheckBox Content="全选" />           
                 
                <ListView x:Name="list"
                     Grid.Row="1">
                  <ListView.ItemTemplate>
                    <DataTemplate>
                      <CheckBox />                 
                    </DataTemplate>
                  </ListView.ItemTemplate>
                </ListView>
              </Grid>
            </ControlTemplate>

    其次,为控件添加两个依赖属性,其中一个为 ItemsSource,即该控件所要接收的数据源,也即选择列表;本质上,这个数据源会指定给其内的 ListView。另外也需要一个属性 IsSelectAllChecked 表示是否选中全选复选框。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static readonly DependencyProperty IsSelectAllCheckedProperty =
          DependencyProperty.Register("IsSelectAllChecked", typeof(bool?), typeof(CheckListView), new PropertyMetadata(false));
     
        public static readonly DependencyProperty ItemsSourceProperty =
          DependencyProperty.Register("ItemsSource", typeof(object), typeof(CheckListView), new PropertyMetadata(null));
     
        /// <summary>
        /// 返回或设置全选复选框的选中状态
        /// </summary>
        public bool? IsSelectAllChecked
        {
          get { return (bool?)GetValue(IsSelectAllCheckedProperty); }
          set { SetValue(IsSelectAllCheckedProperty, value); }
        }
     
        /// <summary>
        /// 数据源
        /// </summary>
        public object ItemsSource
        {
          get { return (object)GetValue(ItemsSourceProperty); }
          set { SetValue(ItemsSourceProperty, value); }
        }

    需要注意的一点是,作为一个自定义控件,我们必须考虑它的通用性,所以为了保证能设置各式各样的数据源(如用户列表、物品列表或 XX名称列表),在这里定义一个数据接口,只要数据源中的数据项实现该接口,即可达到通用的效果。该接口定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface ICheckItem
      {
        /// <summary>
        /// 当前项是否选中
        /// </summary>
        bool IsSelected { get; set; }
     
        /// <summary>
        /// 名称
        /// </summary>
        string Name { get; set; }
      }

    最后,我们把刚才定的属性绑定的控件上,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <CheckBox Content="全选" IsChecked="{Binding IsSelectAllChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
     <ListView x:Name="list" Grid.Row="1" ItemsSource="{TemplateBinding ItemsSource}">
       <ListView.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" />
              </DataTemplate>
            </ListView.ItemTemplate>
    </ListView>

    接下来,实现具体操作:

    首先,通过“全选”复选框来控制所有列表项:这里通过其 Click 事件来执行 CheckAllItems 方法, 在此方法中,会对数据源进行遍历,将其 IsSelected 属性设置为 True 或 False。代码如下:

    1
    2
    3
    4
    5
    6
    7
    <CheckBox Content="全选" IsChecked="{Binding IsSelectAllChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
          <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
               <ei:CallMethodAction MethodName="CheckAllItems" TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
             </i:EventTrigger>
            </i:Interaction.Triggers>
     </CheckBox>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /// <summary>
    /// 全选或清空所用选择
    /// </summary>
        public void CheckAllItems()
        {
          foreach (ICheckItem item in ItemsSource as IList<ICheckItem>)
          {
            item.IsSelected = IsSelectAllChecked.HasValue ? IsSelectAllChecked.Value : false;
          }
        }

    然后,通过选中或取消选中列表项时,更新“全选”复选框的状态:在 DataTemplate 中,我们也为 CheckBox 的 Click 事件设置了要触发的方法 UpdateSelectAllState,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <DataTemplate>
       <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}">
         <i:Interaction.Triggers>
         <i:EventTrigger EventName="Click">
          <ei:CallMethodAction MethodName="UpdateSelectAllState" TargetObject="{Binding RelativeSource={RelativeSource AncestorType=control:CheckListView}}" />
         </i:EventTrigger>
         </i:Interaction.Triggers>
      </CheckBox>
    </DataTemplate>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /// <summary>
    /// 根据当前选择的个数来更新全选框的状态
    /// </summary>
        public void UpdateSelectAllState()
        {
          var items = ItemsSource as IList<ICheckItem>;
          if (items == null)
          {
            return;
          }
     
          // 获取列表项中 IsSelected 值为 True 的个数,并通过该值来确定 IsSelectAllChecked 的值
          int count = items.Where(item => item.IsSelected).Count();
          if (count == items.Count)
          {
            IsSelectAllChecked = true;
          }
          else if (count == 0)
          {
            IsSelectAllChecked = false;
          }
          else
          {       
            IsSelectAllChecked = null;
          }
        }

    这里也有两点需要提醒:

    我一开始定义属性 IsSelectAllChecked 时,它的类型是 bool 类型,那么,由于 CheckBox 控件的 IsChecked 值为 null 时,它将呈现 Indetermine 状态,所以后来把它改为 bool? 类型。

    在XAML 代码中可以看出,对事件以及事件的响应使用了行为,所以,需要添加引用 System.Windows.Interactivity.dll 和 Microsoft.Expression.Interactions.dll 两个库,并在XMAL 头部添加如下命名空间的引用:

    这样,这个控件就基本完成了,接下来是如何使用它。

    首先,定义将要在列表中展示的数据项,并为它实现之前提到的 ICheckItem 接口,这里定义了一个 User 类,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class User : BindableBase, ICheckItem
      {
        private bool isSelected;
        private string name;
     
        public bool IsSelected
        {
          get { return isSelected; }
          set { SetProperty(ref isSelected, value); }
        }
     
        public string Name
        {
          get { return name; }
          set { SetProperty(ref name, value); }
        }
      }

    接下来在 ViewModel 中定义一个列表 List<ICheckItem>,并添加数据,最后在 UI 上为其绑定 ItemsSource 属性即可,在此不再贴代码了,具体请参考源代码。

    源码下载

    The TargetObject is the object which has the method to be invoked, if you have the method in your window code-behind then the object is the window itself

    <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
                 ...etcetera...
                 x:Name="UserControl">
    

    So your CallMethodAction would be:

    <ei:CallMethodAction MethodName="MyMethod"
                         TargetObject="{Binding ElementName=UserControl, Mode=OneWay}"/>
    
     
  • 相关阅读:
    Leetcode 589. N-ary Tree Preorder Traversal
    Leetcode 912. Sort an Array
    Leetcode 1020. Number of Enclaves
    Leetcode 496. Next Greater Element I
    Leetcode 1019. Next Greater Node In Linked List
    Leetcode 503. Next Greater Element II
    Leetcode 1018. Binary Prefix Divisible By 5
    龟兔赛跑算法详解
    Leetcode 142. Linked List Cycle II
    Leetcode 141. Linked List Cycle
  • 原文地址:https://www.cnblogs.com/sjqq/p/7891383.html
Copyright © 2020-2023  润新知