【问题标题】:Binding a collection to SelectedItems in a ListBox without violating MVVM在不违反 MVVM 的情况下将集合绑定到 ListBox 中的 SelectedItems
【发布时间】:2016-01-12 16:28:59
【问题描述】:

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

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

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

我已绑定我的属性以通过使用此设置器在选择更改时保持更新

<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。

【问题讨论】:

  • 嗨,你应该在你的复选框声明中添加一个命令和一个命令参数。在您的视图模型中添加命令,命令参数应该是选中的元素(VNode 类型),更新属性 IsSelected = false 并且它应该正常更新。请注意,您必须使用 RelativeSource 命令,因为它嵌套在列表视图中
  • @Guillaume 你能具体说明我会怎么做吗?
  • wpftution.blogspot.ca/2012/05/… 它的实现相同,而不是按钮它的复选框,在 removeCommand 中,您将更新您的 Vnodes.IsSelected = true 并且您应该更改 AncestorType={x:Type DataGrid}}}" "DataGrid"到 Gridview 或 listView 。对不起,我在午餐时间的快速回复:)
  • 您使用什么控件来显示/选择VNodes?如果控件支持多选,那么它应该已经有一个像 SelectedItems 或类似的集合。
  • 我在画布上选择东西

标签: c# wpf


【解决方案1】:

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

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

但如果你使用System.Windows.Controls.ListBox,你必须使用附加属性自己编写,这实际上并没有那么糟糕。这里有很多代码,但几乎完全是样板文件(此附加属性中的大部分 C# 代码是由 VS IDE 代码 sn-p 创建的)。这里的好处是它是通用的,任何随机的路人都可以在任何包含任何东西的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 定义在 XAML 中的“local:”命名空间中...

<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 时,它会为新的VNodePropertyChanged 事件添加一个处理程序。在处理程序中,它根据 (bool)e.NewValue

    SelectedVNodes 添加/删除 sender
    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 所有者的Parent 引用(可能是它自己)。在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 已经为真,则不要设置 vn.IsSelected = true。如果您的眼睛现在像我一样呆滞,并且您开始感觉到墙壁正在关闭,请允许我推荐选项#3。

也许SelectedVNodes 应该公开 公开ReadOnlyObservableCollection&lt;VNode&gt;,让你摆脱困境。在这种情况下,1 号是您最好的选择,因为 VNodes 将无法访问 VM 的私有可变 ObservableCollection&lt;VNode&gt;

但请自行选择。

【讨论】:

  • 这些都是很好的方法。在我的情况下,控件没有 selected Items 属性,因为我使用的是画布。有没有办法向画布添加扩展以收集所选项目?
  • 那么 Canvas 是 ItemsControl (或其他)使用的 ItemsPanel 吗?像这样:stackoverflow.com/a/1265419/424129 什么的?
  • 这会让事情变得更简单吗?
  • @JokerMartini 好的,我写了一个附件属性,它将 ListBox 的 SelectedItems 状态反映到一个集合(任何实现 IList 的东西,由 viewmodel 提供。ItemsPanel 是什么没有区别。
  • @JokerMartini 好了,完成了。它用于反映通过 UI 进入的选择的变化。尚未测试其他方式的更改。
【解决方案2】:

不使用 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");
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-04
    • 2012-04-10
    • 1970-01-01
    • 1970-01-01
    • 2015-06-08
    • 2014-05-03
    • 2011-08-20
    相关资源
    最近更新 更多