我找到了解决我问题的办法。我找到了它
here
. 就像这样:
wpf用户控件.xaml
<UserControl x:Class="YourNamespace.CheckBoxList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
x:Name="ThisCheckBoxList"
d:DesignHeight="450" d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<ItemsControl x:Name="host"
ItemsSource="{Binding ElementName=ThisCheckBoxList, Path=ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyCheckBox x:Name="theCheckbox"
DisplayMemberPath="{Binding ElementName=ThisCheckBoxList, Path=DisplayPropertyPath}"
Unchecked="MyCheckBox_Checked"
Checked="MyCheckBox_Checked"
Tag="{Binding Path=.}">
<local:MyCheckBox.IsChecked >
<MultiBinding Mode="OneWay" >
<MultiBinding.Converter>
<local:IsCheckedValueConverter />
</MultiBinding.Converter>
<Binding Path="."></Binding>
<Binding ElementName="ThisCheckBoxList" Path="SelectedItems"></Binding>
<Binding ElementName="ThisCheckBoxList" Path="DisplayPropertyPath"></Binding>
</MultiBinding>
</local:MyCheckBox.IsChecked>
</local:MyCheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</UserControl>
wpf用户控件.xaml.cs
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace Eden
{
/// <summary>
/// Interaction logic for CheckBoxList.xaml
/// </summary>
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public object ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(CheckBoxList),
new UIPropertyMetadata(null, (sender, args) => Debug.WriteLine(args)));
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItems. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(CheckBoxList),
new UIPropertyMetadata(null, SelectedChanged));
/// <summary>
/// This is called when selected property changed.
/// </summary>
/// <param name="obj"></param>
/// <param name="args"></param>
private static void SelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.NewValue is INotifyCollectionChanged ncc)
{
ncc.CollectionChanged += (sender, e) =>
{
CheckBoxList thiscontrol = (CheckBoxList)obj;
RebindAllCheckbox(thiscontrol.host);
};
}
}
private static void RebindAllCheckbox(DependencyObject de)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(de); i++)
{
DependencyObject dobj = VisualTreeHelper.GetChild(de, i);
if (dobj is CheckBox cb)
{
var bexpression = BindingOperations.GetMultiBindingExpression(cb, MyCheckBox.IsCheckedProperty);
if (bexpression != null) bexpression.UpdateTarget();
}
RebindAllCheckbox(dobj);
}
}
public string DisplayPropertyPath
{
get => (string)GetValue(DisplayPropertyPathProperty);
set => SetValue(DisplayPropertyPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayPropertyPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayPropertyPathProperty =
DependencyProperty.Register("DisplayPropertyPath", typeof(string), typeof(CheckBoxList),
new UIPropertyMetadata("", (sender, args) => Debug.WriteLine(args)));
private PropertyInfo mDisplayPropertyPathPropertyInfo;
private void MyCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (SelectedItems == null)
return;
MyCheckBox chb = (MyCheckBox)sender;
object related = chb.Tag;
if (mDisplayPropertyPathPropertyInfo == null)
{
mDisplayPropertyPathPropertyInfo =
related.GetType().GetProperty(
DisplayPropertyPath, BindingFlags.Instance | BindingFlags.Public);
}
object propertyValue;
if (DisplayPropertyPath == ".")
propertyValue = related;
else
propertyValue = mDisplayPropertyPathPropertyInfo.GetValue(related, null);
if (chb.IsChecked == true)
{
if (!SelectedItems.Cast<object>()
.Any(o => propertyValue.Equals(
DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null))))
{
SelectedItems.Add(related);
}
}
else
{
object toDeselect = SelectedItems.Cast<object>()
.Where(o => propertyValue.Equals(DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null)))
.FirstOrDefault();
if (toDeselect != null)
{
SelectedItems.Remove(toDeselect);
}
}
}
}
public class MyCheckBox : CheckBox
{
public string DisplayMemberPath
{
get => (string)GetValue(DisplayMemberPathProperty);
set => SetValue(DisplayMemberPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(MyCheckBox),
new UIPropertyMetadata(string.Empty, (sender, args) =>
{
MyCheckBox item = (MyCheckBox)sender;
Binding contentBinding = new Binding((string)args.NewValue);
item.SetBinding(ContentProperty, contentBinding);
}));
}
}
基本多值转换器
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Eden
{
/// <summary>
/// A base value converter that allows direct XAML usage
/// </summary>
/// <typeparam name="T">The type of this value converter</typeparam>
public abstract class BaseMultiValueConverter<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
#region Private Variables
/// <summary>
/// A single static instance of this value converter
/// </summary>
private static T Coverter = null;
#endregion
#region Markup Extension Methods
/// <summary>
/// Provides a static instance of the value converter
/// </summary>
/// <param name="serviceProvider">The service provider</param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Coverter ?? (Coverter = new T());
}
#endregion
#region Value Converter Methods
/// <summary>
/// The method to convert on type to another
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
/// <summary>
/// The method to convert a value back to it's source type
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture);
#endregion
}
}
多值转换器
using EcoDev.Data;
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Windows.Data;
namespace Eden
{
/// <summary>
///
/// </summary>
public class IsCheckedValueConverter : BaseMultiValueConverter<IsCheckedValueConverter>
{
private PropertyInfo PropertyInfo { get; set; }
private Type ObjectType { get; set; }
public override object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[1] == null) return false; // IF I do not have no value for selected simply return false
if (!(values[2] is string PropertyName)) return false;
if (string.IsNullOrEmpty(PropertyName)) return false;
if (!targetType.IsAssignableFrom(typeof(bool))) throw new NotSupportedException("Can convert only to boolean");
IEnumerable collection = values[1] as IEnumerable;
object value = values[0];
if (value.GetType() != ObjectType)
{
PropertyInfo = value.GetType().GetProperty(PropertyName, BindingFlags.Instance | BindingFlags.Public);
ObjectType = value.GetType();
}
foreach (var obj in collection)
{
if (PropertyName == ".")
{
if (value.Equals(obj)) return true;
}
else
{
if (PropertyInfo.GetValue(value, null).Equals(PropertyInfo.GetValue(obj, null))) return true;
}
}
return false;
}
public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
然后,无论您想在哪个窗口/页面中使用它,您所要做的就是使用以下代码:
<local:CheckBoxList Height="Auto"
SelectedItems="{Binding SelectedItems}"
ItemsSource="{Binding ItemsSource}"
DisplayPropertyPath="Text"/>