我在使用Prism编写的SL3应用程序中有一个多选列表框,需要在视图模型中添加一个集合属性,保存当前被选中项的信息,但是视图模型无法直接访问列表框控件。同时,我需要能够在视图模型中清除列表框中的选中项。
目前还不确定该如何处理这个问题。
谢谢, Michael
我在使用Prism编写的SL3应用程序中有一个多选列表框,需要在视图模型中添加一个集合属性,保存当前被选中项的信息,但是视图模型无法直接访问列表框控件。同时,我需要能够在视图模型中清除列表框中的选中项。
目前还不确定该如何处理这个问题。
谢谢, 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中重复使用:
<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);
}
}
}
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);
}
}
}
更新了现有的行为,使其在集合更改和重新绑定时选择项目。
http://rnragu.blogspot.com/2011/04/multiselect-listbox-in-silverlight-use.html
如果您记得首先创建可观察集合的实例,那么上面的原始解决方案就可以工作!此外,您需要确保可观察集合的内容类型与您的ListBox ItemSource的内容类型匹配(如果您偏离了上面提到的确切示例)。
ObservableCollection<string>
,则以上方法可以正常工作,但是如果您通过DataTemplate将其绑定到像ObservableCollection<Person> SelectedItems { get; private set; }
这样的复杂对象,则似乎无法正常工作。这是由于集合使用的Equals方法的默认实现。您可以通过告诉您的Person对象在确定对象是否相等时比较哪些字段来解决此问题,这是通过在对象上实现接口IEquatable<T>
来完成的。// 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
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,然后设置可见性:
<ListBox x:Name="MySelectedItemsListBox" ItemsSource="{Binding SelectedItems, Mode=OneWayToSource}" SelectionMode="Multiple" Visibility="Collapsed" />
MyListBox.SelectionChanged += MyListBox_SelectionChanged;
并添加一个方法:
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MySelectedItemsListBox.ItemsSource = MyListBox.SelectedItems;
}
完成了。这肯定会起作用的。如果上面的解决方案不起作用,我想这也可以用于Silverlight。
对于那些仍然无法使candritzky的答案起作用的人,请确保您没有像我一样修改Windows主题颜色。结果发现,当ListBox失去焦点时,我的ListBox背景颜色与选择颜色匹配,从而使它看起来好像没有选中任何内容。
将您的ListBox背景刷成红色以检查是否也发生了这种情况。我花了2个小时才意识到这一点...
这里有一篇博客,提供了解决此问题的方案,包括一个示例应用程序,让您可以清楚地了解如何使其工作: http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry
我刚刚在我的应用程序中实现了这个方案,它很好地解决了问题。
我在XAML中使用EventToCommand对象,在选择更改事件中将ListBox作为参数传递。然后,MMVM中的命令管理所选项目的ObservableCollection。这很容易和快速 ;)