1. 程式人生 > >WPF: 實現帶全選復選框的列表控件

WPF: 實現帶全選復選框的列表控件

list sele relative 源代碼 date strong 使用 所有 引用

原文:WPF: 實現帶全選復選框的列表控件

本文將說明如何創建一個帶全選復選框的列表控件。其效果如下圖:

技術分享圖片
這個控件是由一個復選框(CheckBox)與一個 ListView 組合而成。它的操作邏輯:

  • 當選中“全選”時,列表中所有的項目都會被選中;反之,取消選中“全選”時,所有項都會被取消勾選。
  • 在列表中選中部分數據項目時,“全選”框會呈現不確定狀態(Indetermine)。

由此看出,“全選”復選框與列表項中的復選框達到了雙向控制的效果。

其設計思路:首先,創建自定義控件(CheckListView),在其 ControlTemplate 中定義 CheckBox 和 ListView,並為 ListView 設置 ItemTemplate,在其中增加 CheckBox 控件,如下:

                <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 表示是否選中全選復選框。

        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名稱列表),在這裏定義一個數據接口,只要數據源中的數據項實現該接口,即可達到通用的效果。該接口定義如下:

    public interface ICheckItem
    {
        /// <summary>
        /// 當前項是否選中
        /// </summary>
        bool IsSelected { get; set; }

        /// <summary>
        /// 名稱
        /// </summary>
        string Name { get; set; }
    }

最後,我們把剛才定的屬性綁定的控件上,如下:

                        <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。代碼如下:

                        <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>
        /// <summary>
        /// 全選或清空所用選擇
        /// </summary>
        public void CheckAllItems()
        {
            foreach (ICheckItem item in ItemsSource as IList<ICheckItem>)
            {
                item.IsSelected = IsSelectAllChecked.HasValue ? IsSelectAllChecked.Value : false;
            }
        }

然後,通過選中或取消選中列表項時,更新“全選”復選框的狀態:在 DataTemplate 中,我們也為 CheckBox 的 Click 事件設置了要觸發的方法 UpdateSelectAllState,代碼如下:

                                <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>
        /// <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 頭部添加如下命名空間的引用:
               xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
               xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

這樣,這個控件就基本完成了,接下來是如何使用它。

首先,定義將要在列表中展示的數據項,並為它實現之前提到的 ICheckItem 接口,這裏定義了一個 User 類,如下:

    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 屬性即可,在此不再貼代碼了,具體請參考源代碼。

如果您有實現此控件的其它方式,歡迎提出您的建議或評論。

源代碼下載

WPF: 實現帶全選復選框的列表控件