如果我没记错的话,在我们上一集的结尾,我们使用了一些异想天开的 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;
}
}
如果你不想去那里,我可以考虑三个三个半直接的方法来做这件事:
-
当父视图模型创建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 一个父属性。当父视图模型创建一个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<VNode>,让你摆脱困境。在这种情况下,1 号是您最好的选择,因为 VNodes 将无法访问 VM 的私有可变 ObservableCollection<VNode>。
但请自行选择。