我想提出一个WPF应用程序,通过自定义导航的“下一步”和“后退”按钮和命令(即不使用NavigationWindow
)。 在一个屏幕上,我有一个ListBox
具有支持多种选择(使用Extended
模式)。 我对这个屏幕视图模型和所选择的项目作为属性存储,因为它们需要维护。
但是,我知道, SelectedItems
一个物业ListBox
是只读的。 我一直在尝试使用,以解决此问题在这里这个解决方案 ,但我一直无法把它采用到我的实现。 我发现,当一个或多个元素被取消,我不能区分,当我浏览屏幕之间( NotifyCollectionChangedAction.Remove
升起在这两种情况下,由于导航远离屏幕时,在技术上所有选定的项目将被取消选择)。 我的导航命令位于其中管理每个屏幕视图模型一个单独的视图模式,所以我不能把涉及与视图模型任何实现ListBox
在里面。
我发现其他几个不太优雅的解决方案,但没有这些似乎强制执行双向视图模型和视图之间的结合。
任何帮助将不胜感激。 我可以提供我的一些源代码,如果它有助于了解我的问题。
Answer 1:
尝试创建一个IsSelected
您的每个数据项的属性和绑定ListBoxItem.IsSelected
到该属性
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
Answer 2:
Rachel的解决方案的伟大工程! 但有一个问题,我遇到的-如果你覆盖的风格ListBoxItem
,你松应用到它原来的样式(在我的案件负责高亮所选项目等)。 您可以通过从原始风格继承避免这种情况:
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
注意设置BasedOn
(见这个答案 )。
Answer 3:
我不能让雷切尔的解决方案来工作,我怎么想,但我发现Sandesh的创建自定义的答案依赖属性为我很好地工作。 我不得不写类似的代码的列表框:
public class ListBoxCustom : ListBox
{
public ListBoxCustom()
{
SelectionChanged += ListBoxCustom_SelectionChanged;
}
void ListBoxCustom_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = SelectedItems;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(ListBoxCustom), new PropertyMetadata(null));
}
在我看来,型号我只是引用了财产让我选择的列表。
Answer 4:
我一直在寻找到这个,但没有运气简单的解决办法。
该解决方案瑞秋是好的,如果你已经有你的ItemsSource中的对象选定的特性。 如果你不这样做,你必须创建该业务模型的模型。
我走了不同的路线。 一个快速,但并不完美。
在您的列表框创建的SelectionChanged事件。
<ListBox ItemsSource="{Binding SomeItemsSource}"
SelectionMode="Multiple"
SelectionChanged="lstBox_OnSelectionChanged" />
现在,实现你的XAML页面的后台代码的事件。
private void lstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listSelectedItems = ((ListBox) sender).SelectedItems;
ViewModel.YourListThatNeedsBinding = listSelectedItems.Cast<ObjectType>().ToList();
}
田田。 完成。
这与的帮助下完成转换SelectedItemCollection到一个列表 。
Answer 5:
不满意给出的答案,我想通过自己找到一个...那么它原来更像是一个黑客再一个解决方案,但对我来说工作正常。 此解决方案使用MultiBindings以特殊的方式。 首先,它可能看起来像一吨的代码,但你可以用很少的努力重用。
首先,我实现了一个“IMultiValueConverter”
public class SelectedItemsMerger : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SelectedItemsContainer sic = values[1] as SelectedItemsContainer;
if (sic != null)
sic.SelectedItems = values[0];
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { value };
}
}
和SelectedItems容器/包装:
public class SelectedItemsContainer
{
/// Nothing special here...
public object SelectedItems { get; set; }
}
现在,我们为我们的ListBox.SelectedItem(奇异)绑定。 注意:您要创建的“转换器”的静态资源。 这可每应用程序来完成,并为需要转换器的所有列表框被重用。
<ListBox.SelectedItem>
<MultiBinding Converter="{StaticResource SelectedItemsMerger}">
<Binding Mode="OneWay" RelativeSource="{RelativeSource Self}" Path="SelectedItems"/>
<Binding Path="SelectionContainer"/>
</MultiBinding>
</ListBox.SelectedItem>
在视图模型我创造,我可以绑定到容器。 它与初始化是很重要的新的(),以便用值填充它。
SelectedItemsContainer selectionContainer = new SelectedItemsContainer();
public SelectedItemsContainer SelectionContainer
{
get { return this.selectionContainer; }
set
{
if (this.selectionContainer != value)
{
this.selectionContainer = value;
this.OnPropertyChanged("SelectionContainer");
}
}
}
仅此而已。 也许有人看到一些改进? 你怎么看待这件事?
Answer 6:
这里是另一种解决方案。 它类似于本的答案 ,但结合作品两种方式。 关键是要更新ListBox
的选择的项目时绑定的数据项而改变。
public class MultipleSelectionListBox : ListBox
{
public static readonly DependencyProperty BindableSelectedItemsProperty =
DependencyProperty.Register("BindableSelectedItems",
typeof(IEnumerable<string>), typeof(MultipleSelectionListBox),
new FrameworkPropertyMetadata(default(IEnumerable<string>),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));
public IEnumerable<string> BindableSelectedItems
{
get => (IEnumerable<string>)GetValue(BindableSelectedItemsProperty);
set => SetValue(BindableSelectedItemsProperty, value);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
BindableSelectedItems = SelectedItems.Cast<string>();
}
private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MultipleSelectionListBox listBox)
listBox.SetSelectedItems(listBox.BindableSelectedItems);
}
}
不幸的是,我没能使用IList
作为BindableSelectedItems类型。 这样做派null
到我的视图模型的属性,其类型是IEnumerable<string>
。
这里的XAML:
<v:MultipleSelectionListBox
ItemsSource="{Binding AllMyItems}"
BindableSelectedItems="{Binding MySelectedItems}"
SelectionMode="Multiple"
/>
有一两件事需要提防。 在我的情况下, ListBox
可以从视图中删除。 出于某种原因,这将导致SelectedItems
属性更改为空列表。 这反过来会导致视图模型的属性更改为一个空列表。 根据您的使用情况下,这可能不是理想的。
Answer 7:
这是一个重大问题,对我来说,有些我已经看到了问题的答案不是太hackish的,或需要重置SelectedItems
属性值突破附着性能OnCollectionChanged事件的任何代码。 但我设法通过直接修改收集得到可行的解决方案,并作为奖金它甚至支持SelectedValuePath
的对象集合。
public class MultipleSelectionListBox : ListBox
{
internal bool processSelectionChanges = false;
public static readonly DependencyProperty BindableSelectedItemsProperty =
DependencyProperty.Register("BindableSelectedItems",
typeof(object), typeof(MultipleSelectionListBox),
new FrameworkPropertyMetadata(default(ICollection<object>),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));
public dynamic BindableSelectedItems
{
get => GetValue(BindableSelectedItemsProperty);
set => SetValue(BindableSelectedItemsProperty, value);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (BindableSelectedItems == null || !this.IsInitialized) return; //Handle pre initilized calls
if (e.AddedItems.Count > 0)
if (!string.IsNullOrWhiteSpace(SelectedValuePath))
{
foreach (var item in e.AddedItems)
if (!BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
BindableSelectedItems.Add((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
}
else
{
foreach (var item in e.AddedItems)
if (!BindableSelectedItems.Contains((dynamic)item))
BindableSelectedItems.Add((dynamic)item);
}
if (e.RemovedItems.Count > 0)
if (!string.IsNullOrWhiteSpace(SelectedValuePath))
{
foreach (var item in e.RemovedItems)
if (BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
BindableSelectedItems.Remove((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
}
else
{
foreach (var item in e.RemovedItems)
if (BindableSelectedItems.Contains((dynamic)item))
BindableSelectedItems.Remove((dynamic)item);
}
}
private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MultipleSelectionListBox listBox)
{
List<dynamic> newSelection = new List<dynamic>();
if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
foreach (var item in listBox.BindableSelectedItems)
{
foreach (var lbItem in listBox.Items)
{
var lbItemValue = lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null);
if ((dynamic)lbItemValue == (dynamic)item)
newSelection.Add(lbItem);
}
}
else
newSelection = listBox.BindableSelectedItems as List<dynamic>;
listBox.SetSelectedItems(newSelection);
}
}
}
结合就像你本来期望MS已经做了自己的工作:
<uc:MultipleSelectionListBox
ItemsSource="{Binding Items}"
SelectionMode="Extended"
SelectedValuePath="id"
BindableSelectedItems="{Binding mySelection}"
/>
它没有被彻底的测试,但已经过去了乍一看检查。 我试图保持它采用的藏品动态类型的可重复使用的。
Answer 8:
这是很容易做到用命令和关系互动EventTrigger。 ItemsCount仅仅是一个绑定属性在您的XAML中使用,如果你想显示更新的计。
XAML:
<ListBox ItemsSource="{Binding SomeItemsSource}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}"
CommandParameter="{Binding ElementName=MyView, Path=SelectedItems.Count}" />
</i:EventTrigger>
</Interaction.Triggers>
</ListView>
<Label Content="{Binding ItemsCount}" />
视图模型:
private int _itemsCount;
private RelayCommand<int> _selectionChangedCommand;
public ICommand SelectionChangedCommand
{
get {
return _selectionChangedCommand ?? (_selectionChangedCommand =
new RelayCommand<int>((itemsCount) => { ItemsCount = itemsCount; }));
}
}
public int ItemsCount
{
get { return _itemsCount; }
set {
_itemsCount = value;
OnPropertyChanged("ItemsCount");
}
}
Answer 9:
原来绑定一个复选框IsSelected属性,并把一叠面板内的文本块和复选框并的伎俩!
文章来源: How to support ListBox SelectedItems binding with MVVM in a navigable application