将集合绑定到ListBox中的SelectedItems而不违反MVVM

时间:2016-01-12 16:29:00

标签: c# wpf

我有一个名为 SelectedVNodes 的ObservableCollection,它包含来自ObservableCollection VNodes 的项目。

SelectedVNodes 应仅包含属性为IsSelected = True的节点,否则如果为“false”则不应包含在列表中。

ObservableCollection<VNode> SelectedVNodes {...}
ObservableCollection<VNode> VNodes {...}

我已经绑定了我的财产,以便通过使用此setter

继续更新选择更改
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />

然而,这就是我所得到的。我不知道如何根据此属性更改从SelectedVNodes列表中追加/删除此项目。

这是VNode类

public class VNode : NotifyBase
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Kids { get; set; }

    private bool isSelected;
    public bool IsSelected
    {
        get { return isSelected; }
        set
        {
            Set(ref isSelected, value);
            Console.WriteLine("selected/deselected");
        }
    }
}

NotifyBase派生自INotifyPropertyChanged。

2 个答案:

答案 0 :(得分:2)

如果我没记错的话,在我们上一集结束时,我们正在使用一些异想天开的WPF控件,它不能让你正确绑定SelectedItems,这样就可以了。但如果你能做到这一点,那么它是迄今为止最好的方式:

<NonWhimsicalListBox
    ItemsSource="{Binding VNodes}"
    SelectedItems="{Binding SelectedVNodes}"
    />

但如果您使用System.Windows.Controls.ListBox,则必须使用附加属性自行编写,实际上并非如此糟糕。这里有很多代码,但它几乎完全是样板(这个附加属性中的大多数C#代码都是由VS IDE代码片段创建的)。这里的好事是它的一般性,任何随机的路人都可以在任何ListBox中使用它。

public static class AttachedProperties
{
    #region AttachedProperties.SelectedItems Attached Property
    public static IList GetSelectedItems(ListBox obj)
    {
        return (IList)obj.GetValue(SelectedItemsProperty);
    }

    public static void SetSelectedItems(ListBox obj, IList value)
    {
        obj.SetValue(SelectedItemsProperty, value);
    }

    public static readonly DependencyProperty 
        SelectedItemsProperty =
            DependencyProperty.RegisterAttached(
                "SelectedItems", 
                typeof(IList), 
                typeof(AttachedProperties),
                new PropertyMetadata(null, 
                    SelectedItems_PropertyChanged));

    private static void SelectedItems_PropertyChanged(
        DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        var lb = d as ListBox;
        IList coll = e.NewValue as IList;

        //  If you want to go both ways and have changes to 
        //  this collection reflected back into the listbox...
        if (coll is INotifyCollectionChanged)
        {
            (coll as INotifyCollectionChanged)
                .CollectionChanged += (s, e3) =>
            {
                //  Haven't tested this branch -- good luck!
                if (null != e3.OldItems)
                    foreach (var item in e3.OldItems)
                        lb.SelectedItems.Remove(item);
                if (null != e3.NewItems)
                    foreach (var item in e3.NewItems)
                        lb.SelectedItems.Add(item);
            };
        }

        if (null != coll)
        {
            if (coll.Count > 0)
            {
                //  Minor problem here: This doesn't work for initializing a 
                //  selection on control creation. 
                //  When I get here, it's because I've initialized the selected 
                //  items collection that I'm binding. But at that point, lb.Items 
                //  isn't populated yet, so adding these items to lb.SelectedItems 
                //  always fails. 
                //  Haven't tested this otherwise -- good luck!
                lb.SelectedItems.Clear();
                foreach (var item in coll)
                    lb.SelectedItems.Add(item);
            }

            lb.SelectionChanged += (s, e2) =>
            {
                if (null != e2.RemovedItems)
                    foreach (var item in e2.RemovedItems)
                        coll.Remove(item);
                if (null != e2.AddedItems)
                    foreach (var item in e2.AddedItems)
                        coll.Add(item);
            };
        }
    }
    #endregion AttachedProperties.SelectedItems Attached Property
}

假设AttachedProperties定义在&#34; local:&#34;命名空间在你的XAML ......

<ListBox 
    ItemsSource="{Binding VNodes}" 
    SelectionMode="Extended"
    local:AttachedProperties.SelectedItems="{Binding SelectedVNodes}"
    />

视图模型:

private ObservableCollection<Node> _selectedVNodes 
    = new ObservableCollection<Node>();
public ObservableCollection<Node> SelectedVNodes
{
    get
    {
        return _selectedVNodes;
    }
}

如果你不想去那里,我可以想到三种半直接的方法:

  1. 当父视图模型创建VNode时,它会为新的VNode PropertyChanged事件添加处理程序。在处理程序中,它根据(bool)e.NewValue

    sender添加/删除SelectedVNodes
    var newvnode = new VNode();
    newvnode.PropertyChanged += (s,e) => {
        if (e.PropertyName == "IsSelected") {
            if ((bool)e.NewValue) {
                //  If not in SelectedVNodes, add it.
            } else {
                //  If in SelectedVNodes, remove it.
            }
        }
    };
    
    //  blah blah blah
    
  2. 执行该操作,但不是添加/删除,只需重新创建SelectedVNodes

    var newvnode = new VNode();
    newvnode.PropertyChanged += (s,e) => {
        if (e.PropertyName == "IsSelected") {
            //  Make sure OnPropertyChanged("SelectedVNodes") is happening!
            SelectedVNodes = new ObservableCollection<VNode>(
                    VNodes.Where(vn => vn.IsSelected)
                );
        }
    };
    
  3. 做那件事,但不要让SelectedVNodes观察到:

    var newvnode = new VNode();
    newvnode.PropertyChanged += (s,e) => {
        if (e.PropertyName == "IsSelected") {
            OnPropertyChanged("SelectedVNodes");
        }
    };
    
    //  blah blah blah much else blah blah
    
    public IEnumerable<VNode> SelectedVNodes {
        get { return VNodes.Where(vn => vn.IsSelected); }
    }
    
  4. VNode一个父属性。当父视图模型创建VNode时,它会为每个VNode提供对SelectedVNodes所有者(可能是其自身)的父引用。在VNode.IsSelected.set中,VNode会在Parent.SelectedVNodes上执行添加或删除操作。

    //  In class VNode
    private bool _isSelected = false;
    public bool IsSelected {
        get { return _isSelected; }
        set {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
            // Elided: much boilerplate checking for redundancy, null parent, etc.
            if (IsSelected)
                Parent.SelectedVNodes.Add(this);
            else
                Parent.SelectedVNodes.Remove(this);
         }
     }
    
  5. 以上都不是艺术品。版本1可能是最不好的。

    如果您拥有大量商品,请不要使用IEnumerable。另一方面,它使您免于承担双向责任,即如果某个消费者直接与SelectedVNodes混淆,您应该真正处理其CollectionChanged事件并更新VNodes有问题。当然,你必须确保你不小心递交:不要在已经存在的集合中添加一个,如果{vn.IsSelected = true则不设置vn.IsSelected 1}}已经是真的了。如果你的眼睛现在像我的眼睛一样上釉并且你开始觉得墙壁闭合,请允许我推荐选项#3。

    也许SelectedVNodes公开公开ReadOnlyObservableCollection<VNode>,以帮助您摆脱困境。在这种情况下,数字1是您最好的选择,因为VNodes无法访问VM的私有可变ObservableCollection<VNode>

    但请选择。

答案 1 :(得分:0)

在不使用INotifyPropertyChanged的情况下执行此操作的一种方法是将if构造添加到setter

private bool isSelected;
    public bool IsSelected
    {
        get { return isSelected; }
        set
        {
            Set(ref isSelected, value);

            if(isSelected)
            {
                if(!SelectedVNodes.Any(v => v.Name == this.Name))
                SelectedVNodes.Add(this);
            }
            else{
                if(SelectedVNodes.Any(v => v.Name == this.Name))
                SelectedVNodes.Remove(this);
            }
            Console.WriteLine("selected/deselected");
        }
    }