问题描述
|
我有一个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);
}
}
}
不要使用它(至少以当前形式使用)。