在不违反MVVM的情况下将集合绑定到ListBox中的SelectedItems
本文关键字:绑定 集合 ListBox SelectedItems 中的 情况下 MVVM | 更新日期: 2023-09-27 18:29:55
我有一个名为SelectedVNodes'的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#代码都是由VSIDE代码片段创建的)。这里的好处是它是通用的,任何随机的路人都可以在任何有任何东西的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}"
/>
ViewModel:
private ObservableCollection<Node> _selectedVNodes
= new ObservableCollection<Node>();
public ObservableCollection<Node> SelectedVNodes
{
get
{
return _selectedVNodes;
}
}
如果你不想去那里,我可以想出三个三种半简单的方法:
当父视图模型创建
VNode
时,它会向新的VNode
的PropertyChanged
事件添加一个处理程序。在处理程序中,它根据(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
执行该事件,但不添加/删除,只需重新创建
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
所有者(可能是其自身)的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
已经为true,也不要设置vn.IsSelected = true
。如果你的眼睛现在像我的一样闪闪发光,并且你开始感觉到墙壁在靠近,请允许我推荐选项#3。
也许SelectedVNodes
应该公开暴露ReadOnlyObservableCollection<VNode>
,让你摆脱困境。在这种情况下,数字1是您的最佳选择,因为VNodes
将无法访问VM的私有可变ObservableCollection<VNode>
。
但你自己选吧。
在不使用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");
}
}