【问题标题】:How to create Tri-state CheckBox that summarize state of all CheckBoxes in a ListView?如何创建汇总 ListView 中所有 CheckBox 状态的三态 CheckBox?
【发布时间】:2011-09-04 18:05:39
【问题描述】:

我有一个 WPF ListView,其中包含 CheckBox 列和自定义对象的其他列。我想在这个ListView--tri-state CheckBox 上加上另一个CheckBox--所以它的状态将绑定到选中的复选框。如果每个人都被选中,则上方复选框的状态将被选中。如果只有其中一些,状态将是不确定的。否则它的状态将被取消选中。 (就像在 Gmail 中一样)

【问题讨论】:

    标签: c# wpf checkbox checkboxlist


    【解决方案1】:

    我找到了一些解决方案

    <StackPanel>
      <CheckBox Name="chbxAll" Checked="chbxAll_Checked" Unchecked="chbxAll_Unchecked" Indeterminate="chbxAll_Indeterminate" IsThreeState="True" >Select All</CheckBox>
         <ListView Name="lstFoundedFiles" SelectionChanged="lstFoundedFiles_SelectionChanged" SelectionMode="Multiple" ItemsSource="{Binding Files}">
           <ListView.Resources>
             <Style TargetType="ListViewItem">
               <Style.Triggers>
                 <Trigger Property="IsSelected" Value="True">
                   <Setter Property="Background" Value="Aquamarine"></Setter>
                 </Trigger>
               </Style.Triggers>
             </Style>
           </ListView.Resources>
           <ListView.View>
             <GridView>
               <GridViewColumn Width="50" Header="Check">
                 <GridViewColumn.CellTemplate>
                   <DataTemplate>
                     <CheckBox x:Name="chbxItem" IsChecked="{Binding Path=IsSelected, 
                                                        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}"/>
                   </DataTemplate>
                 </GridViewColumn.CellTemplate>
               </GridViewColumn>
              <GridViewColumn Header="File">
                <GridViewColumn.CellTemplate>
                  <DataTemplate>
                    <TextBlock Text="{Binding Name}" ></TextBlock>
                  </DataTemplate>
                </GridViewColumn.CellTemplate>
             </GridViewColumn>
             <GridViewColumn Header="Location">
               <GridViewColumn.CellTemplate>
                 <DataTemplate>
                  <TextBlock Text="{Binding Path}"></TextBlock>
                 </DataTemplate>
              </GridViewColumn.CellTemplate>
           </GridViewColumn>
         </GridView>
       </ListView.View>
     </ListView>
    </StackPanel>
    

    ================================================ =============================

    代码隐藏:

    // True if we should ignore check change events.
    private bool IgnoreCheckChangeEvents = false;
    
    private void lstFoundedFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      if (IgnoreCheckChangeEvents) return;
    
      int temp = lstFoundedFiles.SelectedItems.Count;
      IgnoreCheckChangeEvents = true;
      if (temp == lstFoundedFiles.Items.Count)
      {
        chbxAll.IsChecked = true;
      }
      else if (temp == 0)
      {
        chbxAll.IsChecked = false;
      }
      else
      {
        chbxAll.IsChecked = null;
      }
    
      IgnoreCheckChangeEvents = false;
    }
    
    private void chbxAll_Checked(object sender, RoutedEventArgs e)
    {
      if (IgnoreCheckChangeEvents) return;
    
      IgnoreCheckChangeEvents = true;
    
      lstFoundedFiles.SelectAll();
    
      IgnoreCheckChangeEvents = false;
    }
    
    private void chbxAll_Unchecked(object sender, RoutedEventArgs e)
    {
      if (IgnoreCheckChangeEvents) return;
    
      IgnoreCheckChangeEvents = true;
    
      lstFoundedFiles.UnselectAll();
    
      IgnoreCheckChangeEvents = false;
    }
    
    private void chbxAll_Indeterminate(object sender, RoutedEventArgs e)
    {
      if (IgnoreCheckChangeEvents) return;
    
      chbxAll.IsChecked = false;
    
      IgnoreCheckChangeEvents = true;
    
      lstFoundedFiles.UnselectAll();
    
      IgnoreCheckChangeEvents = false;
    }
    

    【讨论】:

    • 聪明。我认为由此制成的 UserControl 很容易完成并且非常有用
    【解决方案2】:

    编辑: 这比我的其他方法要简单一些,至少它没有那么混乱,并且不需要太多的事件处理。每当源更改时,此方法都会重新构建一个巨大的多重绑定,因此它可能会有点性能密集。

    <GridViewColumn.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Is Active" />
            <CheckBox IsThreeState="True"
                      local:AttachedProperties.SelectAllPath="IsActive"
                      local:AttachedProperties.SelectAllItemsSource="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=ItemsSource}" />
        </StackPanel>
    </GridViewColumn.Header>
    
        public static readonly DependencyProperty SelectAllPathProperty =
                DependencyProperty.RegisterAttached("SelectAllPath", typeof(string), typeof(AttachedProperties), new UIPropertyMetadata(null, OnAttached));
        public static string GetSelectAllPath(DependencyObject obj)
        {
            return (string)obj.GetValue(SelectAllPathProperty);
        }
        public static void SetSelectAllPath(DependencyObject obj, string value)
        {
            obj.SetValue(SelectAllPathProperty, value);
        }
    
        public static readonly DependencyProperty SelectAllItemsSourceProperty =
                DependencyProperty.RegisterAttached("SelectAllItemsSource", typeof(IEnumerable), typeof(AttachedProperties), new UIPropertyMetadata(null));
        public static IEnumerable GetSelectAllItemsSource(DependencyObject obj)
        {
            return (IEnumerable)obj.GetValue(SelectAllItemsSourceProperty);
        }
        public static void SetSelectAllItemsSource(DependencyObject obj, IEnumerable value)
        {
            obj.SetValue(SelectAllItemsSourceProperty, value);
        }
    
        private static void OnAttached(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var cb = o as CheckBox;
            if (cb.IsLoaded)
            {
                Attach(cb);
            }
            else
            {
                cb.Loaded += (s, _) => Attach(cb);
            }
        }
    
        private static void Attach(CheckBox checkBox)
        {
            var itemsSource = GetSelectAllItemsSource(checkBox);
            if (itemsSource is INotifyCollectionChanged)
            {
                var isAsIcc = itemsSource as INotifyCollectionChanged;
                isAsIcc.CollectionChanged += (s, ccea) =>
                    {
                        RebuildBindings(checkBox);
                    };
            }
            RebuildBindings(checkBox);
            checkBox.Click += (s, cea) =>
                {
                    if (!checkBox.IsChecked.HasValue)
                    {
                        checkBox.IsChecked = false;
                    }
                };
        }
    
        private static void RebuildBindings(CheckBox checkBox)
        {
            MultiBinding binding = new MultiBinding();
            var itemsSource = GetSelectAllItemsSource(checkBox);
            var path = GetSelectAllPath(checkBox);
            foreach (var item in itemsSource)
            {
                binding.Bindings.Add(new Binding(path) { Source = item });
            }
            binding.Converter = new CheckedConverter();
            checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
        }
    
        private class CheckedConverter : IMultiValueConverter
        {
            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (values.Length == 0)
                {
                    return null;
                }
                else
                {
                    bool first = (bool)values[0];
                    foreach (var item in values.Skip(1).Cast<bool>())
                    {
                        if (first != item)
                        {
                            return null;
                        }
                    }
                    return first;
                }
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
            {
                var output = (bool?)value;
                var outarray = new object[targetTypes.Length];
                if (output.HasValue)
                {
                    for (int i = 0; i < outarray.Length; i++)
                    {
                        outarray[i] = output.Value;
                    }
                }
                return outarray;
            }
        }
    

    无绑定的混乱:

    以下(在 XAML 之后)有一些有史以来最丑陋的代码,它可能有来自事件处理的内存泄漏和其他可怕的缺陷,但它在某种程度上确实有效:

    <GridViewColumn.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Is Active" />
            <CheckBox IsThreeState="True"
                      Tag="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=ItemsSource}"
                      local:AttachedProperties.SelectAllPath="IsActive"/>
        </StackPanel>
    </GridViewColumn.Header>
    
        public static readonly DependencyProperty SelectAllPathProperty =
                DependencyProperty.RegisterAttached("SelectAllPath", typeof(string), typeof(AttachedProperties), new UIPropertyMetadata(null, OnAttached));
        public static string GetSelectAllPath(DependencyObject obj)
        {
            return (string)obj.GetValue(SelectAllPathProperty);
        }
        public static void SetSelectAllPath(DependencyObject obj, string value)
        {
            obj.SetValue(SelectAllPathProperty, value);
        }
    
        private static void OnAttached(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var cb = o as CheckBox;
            // Needs more closures.
            Action attach = () =>
                {
                    IEnumerable itemsSource = cb.Tag as IEnumerable;
                    if (itemsSource == null) throw new Exception("ItemsSource for attached property 'SelectAllPath' not found.");
                    string path = e.NewValue as string;
                    cb.Checked += new RoutedEventHandler(cb_Checked);
                    cb.Unchecked += new RoutedEventHandler(cb_Unchecked);
                    PropertyChangedEventHandler propertyChangeHandler = (i, pcea) =>
                    {
                        if (pcea.PropertyName == path)
                        {
                            UpdateCb(cb, itemsSource.Cast<object>(), path);
                        }
                    };
                    Action<object> tryAttachHandlerAction = (item) =>
                        {
                            if (item is INotifyPropertyChanged)
                            {
                                var asInpc = item as INotifyPropertyChanged;
                                asInpc.PropertyChanged += propertyChangeHandler;
                            }
                        };
                    foreach (var item in itemsSource)
                    {
                        tryAttachHandlerAction(item);
                    }
                    if (itemsSource is INotifyCollectionChanged)
                    {
                        var asCC = itemsSource as INotifyCollectionChanged;
                        asCC.CollectionChanged += (s, cce) =>
                            {
                                if (cce.Action == NotifyCollectionChangedAction.Add)
                                {
                                    foreach (var item in cce.NewItems)
                                    {
                                        tryAttachHandlerAction(item);
                                    }
                                }
                            };
                    }
                    UpdateCb(cb, itemsSource.Cast<object>(), path);
                };
            if (cb.IsLoaded)
            {
                attach();
            }
            else
            {
                cb.Loaded += (s, esub) => attach();
            }
        }
    
        private static void UpdateCb(CheckBox cb, IEnumerable<object> items, string path)
        {
            Type itemType = null;
            PropertyInfo propInfo = null;
            bool? previous = null;
            bool indeterminate = false;
            foreach (var item in items)
            {
                if (propInfo == null)
                {
                    itemType = item.GetType();
                    propInfo = itemType.GetProperty(path);
                }
                if (item.GetType() == itemType)
                {
                    if (!previous.HasValue)
                    {
                        previous = (bool)propInfo.GetValue(item, null);
                    }
                    else
                    {
                        if (previous != (bool)propInfo.GetValue(item, null))
                        {
                            indeterminate = true;
                            break;
                        }
                    }
                }
            }
            if (indeterminate)
            {
                cb.IsChecked = null;
            }
            else
            {
                if (previous.HasValue)
                {
                    cb.IsChecked = previous.Value;
                }
            }
        }
    
        static void cb_Unchecked(object sender, RoutedEventArgs e)
        {
            SetValues(sender, false);
        }
    
        static void cb_Checked(object sender, RoutedEventArgs e)
        {
            SetValues(sender, true);
        }
    
        private static void SetValues(object sender, bool value)
        {
            var cb = sender as CheckBox;
            IEnumerable itemsSource = cb.Tag as IEnumerable;
            Type itemType = null;
            PropertyInfo propInfo = null;
            foreach (var item in itemsSource)
            {
                if (propInfo == null)
                {
                    itemType = item.GetType();
                    propInfo = itemType.GetProperty(GetSelectAllPath(cb));
                }
                if (item.GetType() == itemType)
                {
                    propInfo.SetValue(item, value, null);
                }
            }
        }
    

    不要使用它(至少现在的形式)。

    【讨论】:

    • 感谢您的快速回答。但是你能给我寄一些样品吗?我是 wpf 的新手,我不知道把这个 sn-ps 放在哪里
    • c# 部分位于名为AttachedProperties 的公共静态类中。你知道如何将 clr-namespaces 映射到 xmlns 吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 1970-01-01
    • 2016-05-17
    • 1970-01-01
    相关资源
    最近更新 更多