Редактировать: Это немного менее ужасно, чем мой другой метод, по крайней мере, он не такой хаотичный и не требует такой большой обработки событий.Этот метод перестраивает огромное мультисвязывание всякий раз, когда изменяется источник, поэтому он может быть немного интенсивным.
<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);
}
}
}
Не используйте его (по крайней мере, в его нынешнем виде).