将muliselect列表框中的SelectedItems与ViewModel中的集合同步
我有一个使用棱镜的SL3应用程序中的多选列表框,我需要在我的viewmodel中包含列表框中当前选定项目的集合。
viewmodel不知道任何有关视图,所以它无法访问列表框控件。 此外,我需要能够从视图模型清除列表框中选定的项目。
不知道如何解决这个问题
谢谢Michael
所以,假设你有一个具有以下属性的ViewModel:
public ObservableCollection<string> AllItems { get; private set; } public ObservableCollection<string> SelectedItems { get; private set; }
您将开始将您的AllItems集合绑定到ListBox:
<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" />
问题是ListBox上的SelectedItems属性不是一个DependencyProperty。 这是非常糟糕的,因为你不能将它绑定到你的ViewModel中的东西。
第一种方法是把这个逻辑放在代码隐藏中,来调整ViewModel:
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中设置。 奖金是,这个“附加行为”现在可以在任何列表框中重用:
<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />
这里是附加行为的代码:
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); } } }
我想要真正的双向绑定,以便ListBoxselect反映底层ViewModel的SelectedItems集合中包含的项目。 这使我可以通过ViewModel层中的逻辑来控制select。
这是我对SelectedItemsBehavior类的修改。 如果ViewModel属性实现INotifyCollectionChanged(例如由ObservableCollection <T>types实现),它们将ListBox.SelectedItems集合与基础ViewModel属性同步。
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; } } }
谢谢你! 我添加了一个小的更新,以支持初始加载和DataContext更改。
干杯,
Alessandro Pilotti [MVP / IIS]
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); } } }
更新现有行为与select集合更改和重新绑定的项目
http://rnragu.blogspot.com/2011/04/multiselect-listbox-in-silverlight-use.html
上面的原始解决scheme,如果你记得首先创build一个可观察集合的实例! 此外,您需要确保Observable集合内容types与您的ListBox ItemSource的内容types相匹配(如果您偏离上面提到的确切示例)。
这里有一个解决这个问题的博客,其中包括一个示例应用程序,以便您可以看到如何使其工作: http : //alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry
我刚刚在我的应用程序中实现了这一点,它很好地解决了这个问题
对我来说,解决scheme是将Alessandro Pilotti更新与Brian Genisio附加的行为结合起来。 但删除DataContext的代码更改Silverlight 4不支持这一点。
如果您将列表框绑定到ObservableCollection<string>
上面的工作正常,但如果您绑定到复杂的对象,如ObservableCollection<Person> SelectedItems { get; private set; }
ObservableCollection<Person> SelectedItems { get; private set; }
ObservableCollection<Person> SelectedItems { get; private set; }
通过DataTemplate它似乎不工作。 这是由于集合正在使用的Equals方法的默认实现。 通过在确定对象是否相等时告诉Person对象比较哪些字段可以解决这个问题,这是通过在对象上实现接口IEquatable<T>
来完成的。
之后,IndexOf(项目)代码将工作,并能够比较对象是否相等,并select列表中的项目
// 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
我使用selectEventToCommand对象在XAML中更改事件,并通过那里ListBox作为参数。 MMVM中的命令是pipe理选定项目的ObservableCollection。 它简单快捷;)
Brian Genisio和Samuel Jack的解决scheme非常棒。 我已经成功实施了。 但是我也遇到了这种情况,因为我没有WPF或.Net的专家,我没有debugging它。 我仍然不确定问题是什么,但在适当的时候,我发现了一个解决多select绑定的方法。 而在这个解决scheme中,我不必到达DataContext。
此解决scheme适用于无法使上述两种解决scheme起作用的人员。 我想这个解决scheme不会被视为MVVM。 它是这样的。 假设你在ViewModel中有两个集合:
public ObservableCollection<string> AllItems { get; private set; } public ObservableCollection<string> SelectedItems { get; private set; }
你需要一个列表框:
<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" />
现在添加另一个ListBox并将其绑定到SelectedItems并设置Visibility:
<ListBox x:Name="MySelectedItemsListBox" ItemsSource="{Binding SelectedItems, Mode=OneWayToSource}" SelectionMode="Multiple" Visibility="Collapsed" />
现在,在WPF页面的代码后面,在InitializeComponent()方法之后添加到构造函数中:
MyListBox.SelectionChanged += MyListBox_SelectionChanged;
并添加一个方法:
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { MySelectedItemsListBox.ItemsSource = MyListBox.SelectedItems; }
你完成了。 这将工作肯定。 我猜这也可以在Silverlight中使用,如果上述解决scheme不起作用。
对于那些仍然无法使candritzky答案工作,请确保你没有像我一样修改你的Windows主题颜色。 事实certificate,我的列表框的背景颜色匹配select的颜色时,列表框不在焦点,从而使它看起来像没有被选中。
改变你的ListBox背景画笔为红色,以检查这是否是发生在你身上的事情。 这让我花了2个小时,直到我意识到