我有一个名为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。

最佳答案

如果我没记错的话,在上一集的结尾,我们使用了一些异想天开的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
}


假设在您的XAML中的任何“ AttachedProperties”命名空间中都定义了local:

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


ViewModel:

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


如果您不想去那里,我可以想到以下三种简单的方法:


当父视图模型创建VNode时,它将为新的VNodePropertyChanged事件添加一个处理程序。在处理程序中,它根据(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

进行该事件,而不是添加/删除,而只需重新创建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)
            );
    }
};

进行该事件,但完全不使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); }
}

VNode一个Parent属性。当父视图模型创建一个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);
     }
 }



以上都不是艺术品。版本1可能是最坏的。

如果您有很多物品,请不要使用IEnumerable。另一方面,它使您不必承担双向的责任,即,如果某些消费者直接将SelectedVNodes弄乱了,那么您实际上应该处理其CollectionChanged事件并更新相关的VNodes。当然,然后您必须确保不要意外递归:不要将一个添加到已经存在的集合中,并且如果vn.IsSelected = true已经为真,则不要设置vn.IsSelected。如果您的眼睛现在像我的眼睛一样闪闪发光,并且您开始感到墙壁正在关闭,请允许我推荐选项3。

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

但是,请选择。

关于c# - 在不违反MVVM的情况下将集合绑定(bind)到ListBox中的SelectedItems,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34748875/

10-11 00:51