如何创建Tri-状态CheckBox,汇总ListView中所有CheckBox的状态?

时间:2011-05-30 11:10:25

标签: c# wpf checkbox checkboxlist

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

2 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

编辑:这比我的其他方法更糟糕,至少它不是那么混乱,也不需要那么多的事件处理。每当源发生变化时,此方法都会重建一个巨大的多重绑定,因此可能会有一点性能。

<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;
        }
    }

Bindingless Mess:

以下(在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);
            }
        }
    }

不要使用它(至少以其当前形式)。