具有可绑定选定项目DP的多选列表框未绑定回源

时间:2018-08-10 22:28:25

标签: c# wpf data-binding

我从此答案(https://stackoverflow.com/a/51254960/3797778)改编了一些代码,希望增加SelectedValuePath功能,因为我要存储的集合实际上是列表框实际绑定的对象集合中包含的属性值的集合至。所以基本上我想将所选项目绑定到ID列表,而不是完整的对象。

我为ListBox改编的代码如下:

public class MultipleSelectionListBox : ListBox, INotifyPropertyChanged
{
    public static readonly DependencyProperty BindableSelectedItemsProperty =
        DependencyProperty.Register("BindableSelectedItems",
            typeof(IEnumerable<dynamic>), typeof(MultipleSelectionListBox),
            new FrameworkPropertyMetadata(default(IEnumerable<dynamic>),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));

    public event PropertyChangedEventHandler PropertyChanged;

    public IEnumerable<dynamic> BindableSelectedItems
    {
        get => (IEnumerable<dynamic>)GetValue(BindableSelectedItemsProperty);
        set {
            SetValue(BindableSelectedItemsProperty, value);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BindableSelectedItems"));
        }
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        BindableSelectedItems = SelectedItems.Cast<dynamic>();
    }

    private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MultipleSelectionListBox listBox)
        {
            List<dynamic> newSelection = new List<dynamic>();
            if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
                foreach (var item in listBox.BindableSelectedItems)
                {
                    var collectionValue = item.GetType().GetProperty(listBox.SelectedValuePath).GetValue(item, null);
                    foreach (var lbItem in listBox.Items)
                    {
                        if (lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null) == collectionValue)
                            newSelection.Add(lbItem);
                    }
                }
            else
                newSelection = listBox.BindableSelectedItems as List<dynamic>;

            listBox.SetSelectedItems(listBox.BindableSelectedItems);
        }
    }
}

其余的您可能会假设,但无论如何我都会屏蔽基础知识。

我在LB中的对象

public class DeviceChannelInfo
{
    public DeviceStateInfo parentDeviceState { get; set; }
    public string name { get; set; }
    public string displayName { get; set; }
    public int id { get; set; }
}

我的LB代码:

<uc:MultipleSelectionListBox ItemsSource="{Binding Source={x:Static local:SharedProperties.deviceChannelInfos}, Mode=OneWay}" SelectionMode="Extended" SelectedValuePath="name" IsSynchronizedWithCurrentItem="True" BindableSelectedItems="{Binding MyCollectionOfSelectedIDs, Mode=TwoWay}">

绑定似乎从未与我的“ MyCollectionOfSelectedIDs”道具通信。

2 个答案:

答案 0 :(得分:1)

请参考以下示例代码。

public class MultipleSelectionListBox : ListBox
{
    public static readonly DependencyProperty BindableSelectedItemsProperty =
        DependencyProperty.Register("BindableSelectedItems",
            typeof(IEnumerable<dynamic>), typeof(MultipleSelectionListBox),
            new FrameworkPropertyMetadata(default(IEnumerable<dynamic>),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));

    public IEnumerable<dynamic> BindableSelectedItems
    {
        get => (IEnumerable<dynamic>)GetValue(BindableSelectedItemsProperty);
        set => SetValue(BindableSelectedItemsProperty, value);
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        BindableSelectedItems = SelectedItems.Cast<dynamic>();
    }

    private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MultipleSelectionListBox listBox)
        {
            List<dynamic> newSelection = new List<dynamic>();
            if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
                foreach (var item in listBox.BindableSelectedItems)
                {
                    var collectionValue = item.GetType().GetProperty(listBox.SelectedValuePath).GetValue(item, null);
                    foreach (var lbItem in listBox.Items)
                    {
                        if (lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null) == collectionValue)
                            newSelection.Add(lbItem);
                    }
                }
            else
                newSelection = listBox.BindableSelectedItems as List<dynamic>;

            listBox.SetSelectedItems(listBox.BindableSelectedItems);
        }
    }
}

查看模型:

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        //select 1 and 4 initially:
        MyCollectionOfSelectedIDs = new List<dynamic> { Items[0], Items[3] };
    }

    public IList<DeviceChannelInfo> Items { get; } = new List<DeviceChannelInfo>()
    {
        new DeviceChannelInfo{ name = "1", displayName = "1", id =1 },
        new DeviceChannelInfo{ name = "2", displayName = "2", id =2 },
        new DeviceChannelInfo{ name = "3", displayName = "3", id =3 },
        new DeviceChannelInfo{ name = "4", displayName = "4", id =4 }
    };

    private IEnumerable<dynamic> _mCollectionOfSelectedIDs;
    public IEnumerable<dynamic> MyCollectionOfSelectedIDs
    {
        get { return _mCollectionOfSelectedIDs; }
        set { _mCollectionOfSelectedIDs = value; NotifyPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

查看:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp4"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <local:MultipleSelectionListBox ItemsSource="{Binding Items}" 
                                        SelectionMode="Extended"
                                        DisplayMemberPath="displayName"
                                        SelectedValuePath="name" 
                                        BindableSelectedItems="{Binding MyCollectionOfSelectedIDs}" />

        <TextBlock Grid.Row="1" Text="{Binding MyCollectionOfSelectedIDs.Count}" />
    </Grid>
</Window>

答案 1 :(得分:1)

我把鸡蛋弄破了。

对我来说,最重要的是避免使用行为或背后的代码,我相信我已经做到了这一点(通过一些可能需要的测试,但到目前为止尚可)

public class MultipleSelectionListBox : ListBox
{
    internal bool processSelectionChanges = false;

    public static readonly DependencyProperty BindableSelectedItemsProperty =
        DependencyProperty.Register("BindableSelectedItems",
            typeof(object), typeof(MultipleSelectionListBox),
            new FrameworkPropertyMetadata(default(ICollection<object>),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));

    public dynamic BindableSelectedItems
    {
        get => GetValue(BindableSelectedItemsProperty);
        set => SetValue(BindableSelectedItemsProperty, value);
    }


    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);

        if (BindableSelectedItems == null || !this.IsInitialized) return; //Handle pre initilized calls

        if (e.AddedItems.Count > 0)
            if (!string.IsNullOrWhiteSpace(SelectedValuePath))
            {
                foreach (var item in e.AddedItems)
                    if (!BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
                        BindableSelectedItems.Add((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
            }
            else
            {
                foreach (var item in e.AddedItems)
                    if (!BindableSelectedItems.Contains((dynamic)item))
                        BindableSelectedItems.Add((dynamic)item);
            }

        if (e.RemovedItems.Count > 0)
            if (!string.IsNullOrWhiteSpace(SelectedValuePath))
            {
                foreach (var item in e.RemovedItems)
                    if (BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
                        BindableSelectedItems.Remove((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
            }
            else
            {
                foreach (var item in e.RemovedItems)
                    if (BindableSelectedItems.Contains((dynamic)item))
                        BindableSelectedItems.Remove((dynamic)item);
            }
    }

    private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MultipleSelectionListBox listBox)
        {
            List<dynamic> newSelection = new List<dynamic>();
            if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
                foreach (var item in listBox.BindableSelectedItems)
                {
                    foreach (var lbItem in listBox.Items)
                    {
                        var lbItemValue = lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null);
                        if ((dynamic)lbItemValue == (dynamic)item)
                            newSelection.Add(lbItem);
                    }
                }
            else
                newSelection = listBox.BindableSelectedItems as List<dynamic>;

            listBox.SetSelectedItems(newSelection);
        }
    }
}

我本来是交换保存所选项目的属性值的,这是在破坏我的高级绑定,但是此控件直接修改了集合,使所有引用保持完整。我还没有完全测试双向绑定,但是它在初始加载时加载了正确的值,并在列表框中进行编辑时引发了所有适当的集合更改事件。