如何支持列表框SelectedItems在通航的应用与MVVM结合如何支持列表框SelectedIt

2019-06-02 16:14发布

我想提出一个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