我在使用棱镜的SL3应用程序中有一个多选列表框,我需要在我的viewmodel中包含列表框中当前所选项目的集合。
viewmodel对视图一无所知,因此它无法访问列表框控件。此外,我需要能够从viewmodel清除列表框中的选定项目。
不确定如何解决此问题
感谢 迈克尔
答案 0 :(得分:40)
因此,假设您有一个具有以下属性的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);
}
}
}
答案 1 :(得分:6)
我想要真正的双向绑定,以便ListBox选择反映底层ViewModel的SelectedItems集合中包含的项目。这允许我通过ViewModel层中的逻辑来控制选择。
以下是我对SelectedItemsBehavior类的修改。如果ViewModel属性实现INotifyCollectionChanged(例如,由ObservableCollection&lt; T&gt;类型实现),它们将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;
}
}
}
答案 2 :(得分:3)
谢谢!我添加了一个小更新来支持初始加载和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);
}
}
}
答案 3 :(得分:3)
通过选择Collection Changed&amp;上的项目来更新现有行为。 Rebinded
http://rnragu.blogspot.com/2011/04/multiselect-listbox-in-silverlight-use.html
答案 4 :(得分:1)
如果你记得首先创建一个observable集合的实例,上面的原始解决方案是有效的!此外,您需要确保Observable集合内容类型与ListBox ItemSource的内容类型匹配(如果您偏离上面提到的确切示例)。
答案 5 :(得分:0)
这是一个包含此问题解决方案的博客,包括示例应用程序,以便您可以确切了解如何使其工作: http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry
我刚刚在我的应用程序中实现了它,它很好地解决了这个问题
答案 6 :(得分:0)
我的解决方案是将Alessandro Pilotti更新与Brian Genisio附加行为相结合。但删除DataContext的代码,更改Silverlight 4不支持此功能。
如果您将列表框绑定到ObservableCollection<string>
,则上述工作正常,但如果您通过DataTemplate绑定到ObservableCollection<Person> SelectedItems { get; private set; }
之类的复杂对象,则它似乎不起作用。这是由于集合正在使用的 Equals 方法的默认实现。您可以通过在确定对象是否相等时告诉Person对象要比较哪些字段来解决这个问题,这可以通过在对象上实现接口IEquatable<T>
来完成。
之后,IndexOf(item)代码将起作用并能够比较对象是否为Equal并选择列表中的项目
// 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
答案 7 :(得分:0)
我在XAML中的选择更改事件上使用EventToCommand对象并将ListBox作为参数传递。比MMVM中的命令管理所选项的ObservableCollection。它简单快捷;)
答案 8 :(得分:0)
Brian Genisio和Samuel Jack here的解决方案很棒。我成功地实现了它。但我也有一个案例,这不起作用,因为,我不是WPF或.Net的专家,我没有调试它。我仍然不确定问题是什么,但在适当的时候,我找到了多选择绑定的解决方法。在这个解决方案中,我没有必要访问DataContext。
此解决方案适用于无法使上述2种解决方案有效的用户。我想这个解决方案不会被视为MVVM。它是这样的。假设您在ViewModel中有2个集合:
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中使用。
答案 9 :(得分:0)
对于仍然无法让candritzky回答工作的人,请确保您没有像我一样修改您的Windows主题颜色。事实证明,当ListBox失焦时,我的ListBox背景颜色与选择颜色匹配,从而使其看起来没有被选中。
将ListBox背景画笔更改为红色,以检查这是否正在发生。这让我花了2个小时才意识到......