Sync SelectedItems in a muliselect listbox with a collection in ViewModel
我在使用Prism的SL3应用程序中有一个多选列表框,我需要在我的ViewModel中有一个包含列表框中当前所选项目的集合。
ViewModel对视图一无所知,因此它无权访问ListBox控件。另外,我需要能够从视图模型中清除列表框中的选定项。
不知道如何处理这个问题
谢谢迈克尔
因此,假设您有一个具有以下属性的ViewModel:
1 2 | public ObservableCollection<string> AllItems { get; private set; } public ObservableCollection<string> SelectedItems { get; private set; } |
首先将Allitems集合绑定到列表框:
1 | <ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" /> |
问题是列表框上的SelectedItems属性不是DependencyProperty。这很糟糕,因为您不能将它绑定到您的ViewModel中的某个对象上。
第一种方法是将这个逻辑放在代码后面,调整视图模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public MainPage() { InitializeComponent(); MyListBox.SelectionChanged += ListBoxSelectionChanged; } private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { var listBox = sender as ListBox; if(listBox == null) return; var viewModel = listBox.DataContext as MainVM; if(viewModel == null) return; viewModel.SelectedItems.Clear(); foreach (string item in listBox.SelectedItems) { viewModel.SelectedItems.Add(item); } } |
这种方法是可行的,但确实很难看。我的首选方法是将此行为提取为"附加行为"。如果这样做,就可以完全消除代码隐藏,并在XAML中进行设置。额外的好处是,这种"附加行为"现在可以在任何列表框中重用:
1 | <ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" /> |
下面是附加行为的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public static class SelectedItems { private static readonly DependencyProperty SelectedItemsBehaviorProperty = DependencyProperty.RegisterAttached( "SelectedItemsBehavior", typeof(SelectedItemsBehavior), typeof(ListBox), null); public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached( "Items", typeof(IList), typeof(SelectedItems), new PropertyMetadata(null, ItemsPropertyChanged)); public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); } public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; } private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var target = d as ListBox; if (target != null) { GetOrCreateBehavior(target, e.NewValue as IList); } } private static SelectedItemsBehavior GetOrCreateBehavior(ListBox target, IList list) { var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior; if (behavior == null) { behavior = new SelectedItemsBehavior(target, list); target.SetValue(SelectedItemsBehaviorProperty, behavior); } return behavior; } } public class SelectedItemsBehavior { private readonly ListBox _listBox; private readonly IList _boundList; public SelectedItemsBehavior(ListBox listBox, IList boundList) { _boundList = boundList; _listBox = listBox; _listBox.SelectionChanged += OnSelectionChanged; } private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { _boundList.Clear(); foreach (var item in _listBox.SelectedItems) { _boundList.Add(item); } } } |
我希望有真正的双向绑定,以便列表框选择反映底层ViewModel的SelectedItems集合中包含的项。这允许我在ViewModel层中通过逻辑控制选择。
以下是我对SelectedItemsBehavior类的修改。如果ViewModel属性实现InotifyCollectionChanged(例如,由ObservableCollection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | public static class SelectedItems { private static readonly DependencyProperty SelectedItemsBehaviorProperty = DependencyProperty.RegisterAttached( "SelectedItemsBehavior", typeof(SelectedItemsBehavior), typeof(ListBox), null); public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached( "Items", typeof(IList), typeof(SelectedItems), new PropertyMetadata(null, ItemsPropertyChanged)); public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); } public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; } private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var target = d as ListBox; if (target != null) { AttachBehavior(target, e.NewValue as IList); } } private static void AttachBehavior(ListBox target, IList list) { var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior; if (behavior == null) { behavior = new SelectedItemsBehavior(target, list); target.SetValue(SelectedItemsBehaviorProperty, behavior); } } } public class SelectedItemsBehavior { private readonly ListBox _listBox; private readonly IList _boundList; public SelectedItemsBehavior(ListBox listBox, IList boundList) { _boundList = boundList; _listBox = listBox; _listBox.Loaded += OnLoaded; _listBox.DataContextChanged += OnDataContextChanged; _listBox.SelectionChanged += OnSelectionChanged; // Try to attach to INotifyCollectionChanged.CollectionChanged event. var notifyCollectionChanged = boundList as INotifyCollectionChanged; if (notifyCollectionChanged != null) { notifyCollectionChanged.CollectionChanged += OnCollectionChanged; } } void UpdateListBoxSelection() { // Temporarily detach from ListBox.SelectionChanged event _listBox.SelectionChanged -= OnSelectionChanged; // Synchronize selected ListBox items with bound list _listBox.SelectedItems.Clear(); foreach (var item in _boundList) { // References in _boundList might not be the same as in _listBox.Items var i = _listBox.Items.IndexOf(item); if (i >= 0) { _listBox.SelectedItems.Add(_listBox.Items[i]); } } // Re-attach to ListBox.SelectionChanged event _listBox.SelectionChanged += OnSelectionChanged; } void OnLoaded(object sender, RoutedEventArgs e) { // Init ListBox selection UpdateListBoxSelection(); } void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { // Update ListBox selection UpdateListBoxSelection(); } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Update ListBox selection UpdateListBoxSelection(); } void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { // Temporarily deattach from INotifyCollectionChanged.CollectionChanged event. var notifyCollectionChanged = _boundList as INotifyCollectionChanged; if (notifyCollectionChanged != null) { notifyCollectionChanged.CollectionChanged -= OnCollectionChanged; } // Synchronize bound list with selected ListBox items _boundList.Clear(); foreach (var item in _listBox.SelectedItems) { _boundList.Add(item); } // Re-attach to INotifyCollectionChanged.CollectionChanged event. if (notifyCollectionChanged != null) { notifyCollectionChanged.CollectionChanged += OnCollectionChanged; } } } |
通过选择集合中的项已更改并重新绑定,更新了现有行为
http://rnragu.blogspot.com/2011/04/multisect-listbox-in-silverlight-use.html
谢谢你!我添加了一个小的更新来支持初始加载和数据上下文更改。
干杯,
亚历山德罗·皮洛蒂[MVP/IIS]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class SelectedItemsBehavior { private readonly ListBox _listBox; private readonly IList _boundList; public ListBoxSelectedItemsBehavior(ListBox listBox, IList boundList) { _boundList = boundList; _listBox = listBox; SetSelectedItems(); _listBox.SelectionChanged += OnSelectionChanged; _listBox.DataContextChanged += ODataContextChanged; } private void SetSelectedItems() { _listBox.SelectedItems.Clear(); foreach (object item in _boundList) { // References in _boundList might not be the same as in _listBox.Items int i = _listBox.Items.IndexOf(item); if (i >= 0) _listBox.SelectedItems.Add(_listBox.Items[i]); } } private void ODataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { SetSelectedItems(); } private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { _boundList.Clear(); foreach (var item in _listBox.SelectedItems) { _boundList.Add(item); } } } |
如果您记得首先创建一个可观察集合的实例,那么上面的原始解决方案是有效的!此外,还需要确保可观察集合内容类型与列表框项源的内容类型匹配(如果您偏离了上面提到的确切示例)。
对于那些仍然不能让Candritzky回答工作的人,确保你没有像我一样修改你的Windows主题颜色。当列表框失去焦点时,我的列表框背景颜色与选择颜色匹配,从而使其看起来像没有选择任何内容。
将您的列表框背景画笔更改为红色,以检查这是否是发生在您身上的事情。我花了2个小时才意识到…
布莱恩·吉尼西奥和塞缪尔·杰克的解决方案很好。我已经成功地实现了它。但我也遇到过这样的情况:这不起作用,因为我对WPF或.NET不是专家,所以我未能调试它。我仍然不确定这个问题是什么,但在适当的时候,我找到了一个多选择绑定的解决方案。在这个解决方案中,我不需要访问DataContext。
此解决方案适用于无法使上述两个解决方案发挥作用的人员。我想这个解决方案不会被认为是MVVM。就像这样。假设在ViewModel中有两个集合:
1 2 | public ObservableCollection<string> AllItems { get; private set; } public ObservableCollection<string> SelectedItems { get; private set; } |
您需要一个列表框:
1 | <ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" /> |
现在添加另一个列表框并将其绑定到selecteditems并设置可见性:
1 | <ListBox x:Name="MySelectedItemsListBox" ItemsSource="{Binding SelectedItems, Mode=OneWayToSource}" SelectionMode="Multiple" Visibility="Collapsed" /> |
现在,在wpf页后面的代码中,在initializecomponent()方法之后添加到构造函数:
1 | MyListBox.SelectionChanged += MyListBox_SelectionChanged; |
并添加一个方法:
1 2 3 4 | private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { MySelectedItemsListBox.ItemsSource = MyListBox.SelectedItems; } |
你就完了。这肯定有效。如果上述解决方案不起作用,我想这也可以在Silverlight中使用。
我在XAML中使用EventToCommand对象处理Selection Changed事件,并将ListBox作为参数传递到该事件。mmvm中的than命令正在管理所选项目的ObservableCollection。它既简单又快速;)
我的解决方案是将亚历山德罗·皮洛蒂的最新消息与布莱恩·金尼西奥的行为联系起来。但是删除DataContext的代码更改Silverlight4不支持此功能。
如果要将列表框绑定到
之后,(项)代码的索引将工作,并且能够比较对象是否相等,并选择列表中的项。
1 2 3 4 | // References in _boundList might not be the same as in _listBox.Items int i = _listBox.Items.IndexOf(item); if (i >= 0) _listBox.SelectedItems.Add(_listBox.Items[i]); |
请参阅链接:http://msdn.microsoft.com/en-us/library/ms131190(vs.95).aspx
这里有一个关于这个问题的解决方案的博客,其中包括一个示例应用程序,这样您就可以确切地看到如何使其工作:http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149条目
我刚在我的应用程序中实现了这个,它很好地解决了这个问题。