如何创建三态CheckBox来汇总ListView中所有CheckBox的状态?

问题描述

| 我有一个WPF
ListView
,其中包含
CheckBox
列和自定义对象的其他列。我想在此
ListView
(三态
CheckBox
)上再放置一个
CheckBox
,因此其状态将绑定到选中的复选框。如果每个人都被选中,则上方复选框的状态将被选中。如果只有其中一些,状态将是不确定的。否则,将不检查其状态。 (例如在Gmail中)     

解决方法

        我找到了一些解决方案
<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;
}
    ,        编辑:这比我的其他方法要糟糕一些,至少它不那么混乱,并且不需要太多的事件处理。每当源更改时,此方法都会重建巨大的多重绑定,因此可能会导致性能过高。
<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),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,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\",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,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,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);
            }
        }
    }
不要使用它(至少以当前形式使用)。     

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...