【问题标题】:WPF M-V-VM: Get selected items from a ListCollectionView?WPF M-V-VM:从 ListCollectionView 中获取选定项目?
【发布时间】:2010-10-01 21:25:23
【问题描述】:

我有一个使用模型-视图-视图模型模式的 WPF 应用程序。
在我的 ViewModel 中,我有一个 ListCollectionView 来保存项目列表。
此 ListCollectionView 绑定到我的视图中的 ListBox。

<ListBox Grid.Row="1" ItemsSource="{Binding Useragents}" SelectionMode="Multiple"/>

ListBox 的 SelectionMode=Multiple,因此您可以一次选择多个项目。现在 ViewModel 需要知道哪些项目已被选中。

问题是:在 View-Model-ViewModel 模式中,ViewModel 无法访问 View,所以我不能只询问 ListBox 哪些项目已被选中。我只有 ListCollectionView,但我无法找到其中已选择的项目。

那么我如何找到 ListBox 中哪些项目被选中了呢?或者实现这一点的技巧(可能将某些东西绑定到我的项目中的布尔“IsSelected”?但是什么?如何?)

也许正在使用这种模式的人也可以在这里帮助我?

【问题讨论】:

  • 我几乎问过同样的问题,但没有得到可接受的答案。 stackoverflow.com/questions/1235772/…
  • 这太荒谬了。虽然 WPF 在许多方面都很棒,但仍然有一些基本的细节确实不起作用。实现如何省略 selectedItems 的简单绑定来更新视图模型中的属性。在我看来,选择的答案更像是绕道 10,000 英里到达你的后院。像这样的事情有时会让 wpf 的使用变得非常奇怪。
  • 是的,在某些方面这很糟糕 - 但它不是 WPF 本身,而是使用 M-V-VM 的绑定东西。

标签: wpf mvvm listbox


【解决方案1】:

您需要创建一个具有 IsSelected 概念的 ViewModel,并绑定到使用标准 WPF 绑定架构在 View 中表示它的实际 ListBoxItem 的 IsSelected 属性。

然后在您的代码中,它知道您的 ViewModel,但不知道它由任何特定 View 表示的事实,可以只使用该属性来找出模型中的哪些项目实际上是被选择的,而不管设计者如何选择它在视图中表示。

【讨论】:

  • 听起来不错,但是怎么样?你能给我更多关于如何使用 ListBox 和 ListCollectionView 实现这一点的提示吗?
  • 您要绑定到 LB 的 ItemsSource 的集合将包含 VM 类的实例。然后为这个 VM 类创建一个 DataTemplate,它将 ListBoxItem.IsSelected 绑定到你的类的 IsSelected 属性。当 LB 填充时,它将自动使用该模板。
  • 好吧,我仍然不知道该怎么做,特别是因为(正如我在问题中所说)可以选择多个项目。
  • 这个答案是正确的。有关此代码示例,请参阅我的答案。
【解决方案2】:

PRISM MVVM Reference Implementation 有一个称为 SynchronizeSelectedItems 的行为,在 Prism4\MVVM RI\MVVM.Client\Views\MultipleSelectionView.xaml 中使用,它将选中的项目与名为 Selections 的 ViewModel 属性同步:

        <ListBox Grid.Column="0" Grid.Row="1" IsTabStop="False" SelectionMode="Multiple"
                 ItemsSource="{Binding Question.Range}" Margin="5">

            <ListBox.ItemContainerStyle>
                <!-- Custom style to show the multi-selection list box as a collection of check boxes -->
                <Style TargetType="ListBoxItem">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ListBoxItem">
                                <Grid Background="Transparent">
                                    <CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" 
                                              IsHitTestVisible="False" IsTabStop="True"
                                              AutomationProperties.AutomationId="CheckBoxAutomationId">
                                        <ContentPresenter/>
                                    </CheckBox>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
            <i:Interaction.Behaviors>
                <!-- Custom behavior that synchronizes the selected items with the view models collection -->
                <Behaviors:SynchronizeSelectedItems Selections="{Binding Selections}"/>
            </i:Interaction.Behaviors>
        </ListBox>

转到http://compositewpf.codeplex.com/ 并全部获取或使用它:

//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace MVVM.Client.Infrastructure.Behaviors
{
    /// <summary>
    /// Custom behavior that synchronizes the list in <see cref="ListBox.SelectedItems"/> with a collection.
    /// </summary>
    /// <remarks>
    /// This behavior uses a weak event handler to listen for changes on the synchronized collection.
    /// </remarks>
    public class SynchronizeSelectedItems : Behavior<ListBox>
    {
        public static readonly DependencyProperty SelectionsProperty =
            DependencyProperty.Register(
                "Selections",
                typeof(IList),
                typeof(SynchronizeSelectedItems),
                new PropertyMetadata(null, OnSelectionsPropertyChanged));

        private bool updating;
        private WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs> currentWeakHandler;

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
            Justification = "Dependency property")]
        public IList Selections
        {
            get { return (IList)this.GetValue(SelectionsProperty); }
            set { this.SetValue(SelectionsProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged;
            this.UpdateSelectedItems();
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged;

            base.OnDetaching();
        }

        private static void OnSelectionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = d as SynchronizeSelectedItems;

            if (behavior != null)
            {
                if (behavior.currentWeakHandler != null)
                {
                    behavior.currentWeakHandler.Detach();
                    behavior.currentWeakHandler = null;
                }

                if (e.NewValue != null)
                {
                    var notifyCollectionChanged = e.NewValue as INotifyCollectionChanged;
                    if (notifyCollectionChanged != null)
                    {
                        behavior.currentWeakHandler =
                            new WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs>(
                                behavior,
                                (instance, sender, args) => instance.OnSelectionsCollectionChanged(sender, args),
                                (listener) => notifyCollectionChanged.CollectionChanged -= listener.OnEvent);
                        notifyCollectionChanged.CollectionChanged += behavior.currentWeakHandler.OnEvent;
                    }

                    behavior.UpdateSelectedItems();
                }
            }
        }

        private void OnSelectedItemsChanged(object sender, SelectionChangedEventArgs e)
        {
            this.UpdateSelections(e);
        }

        private void UpdateSelections(SelectionChangedEventArgs e)
        {
            this.ExecuteIfNotUpdating(
                () =>
                {
                    if (this.Selections != null)
                    {
                        foreach (var item in e.AddedItems)
                        {
                            this.Selections.Add(item);
                        }

                        foreach (var item in e.RemovedItems)
                        {
                            this.Selections.Remove(item);
                        }
                    }
                });
        }

        private void OnSelectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.UpdateSelectedItems();
        }

        private void UpdateSelectedItems()
        {
            this.ExecuteIfNotUpdating(
                () =>
                {
                    if (this.AssociatedObject != null)
                    {
                        this.AssociatedObject.SelectedItems.Clear();
                        foreach (var item in this.Selections ?? new object[0])
                        {
                            this.AssociatedObject.SelectedItems.Add(item);
                        }
                    }
                });
        }

        private void ExecuteIfNotUpdating(Action execute)
        {
            if (!this.updating)
            {
                try
                {
                    this.updating = true;
                    execute();
                }
                finally
                {
                    this.updating = false;
                }
            }
        }
    }
}

【讨论】:

  • 我笑了一会儿。现在我害怕得发抖。那么这就是确定在列表框中选择了哪些项目所需要的?我认为有些建筑师需要死。
  • @stmax,哇,100% 我的想法,我说得再好不过了。对于那些将 WPF 炒作未来 10 年的 UI 工具的人来说,可能很难再感到愤世嫉俗了。如果开发人员不能提供如此简单的功能,那我只能笑了。这不是针对冲浪的,但它让我很沮丧,因为我现在花了太多时间自己解决这个问题。
  • 有没有办法使用常规的&lt;ListBox.ItemTemplate&gt; 而不是设置ListBox.ItemContainerStyle.Template 来做到这一点?
  • @Dai:是的,这里使用 ItemContainerStyle.Template 只是为了自定义演示。这不是必需的。只需复制行为代码文件并在xaml中引用即可:
  • 你们没有看到微软代码中的错误吗,伙计们?它有 this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged;在 OnDetaching() 方法中。
【解决方案3】:

【讨论】:

    【解决方案4】:

    Drew Marsh 的解决方案效果很好,我推荐它。我还有另一个解决方案!

    Model View ViewModel 是Passive View,您也可以使用Presentation Model 来访问您的演示文稿的一些数据,而无需与 WPF 耦合 (此模式用于PRISMStocktrader 示例)。

    【讨论】:

      【解决方案5】:

      如果您的列表很小,Drew Marsh 的回答很好,如果您的列表很大,查找所有选定项目的性能可能会很糟糕! 我最喜欢的解决方案是在 ListBox 上创建一个附加属性,然后绑定到包含所选项目的 ObservableCollection。 然后使用您附加的属性订阅 items SelectionChanged 事件以从您的集合中添加/删除项目。

      【讨论】:

      • 听起来很有希望——你没有一些代码示例吗?
      【解决方案6】:

      对我来说最好的答案是打破一点 MVVM 的原则。

      关于后面的代码 1. 实例化你的 viewModel 2.添加事件处理程序SelectionChanged 3. 遍历您选择的项目并将每个项目添加到您的 viewModel 列表中

      ViewModel viewModel = new ViewModel();
      
      viewModel.SelectedModules = new ObservableCollection<string>();
      
      foreach (var selectedModule in listBox1.SelectedItems)
      {
          viewModel.SelectedModules.Add(selectedModule.ToString());
      }
      

      【讨论】:

      • 对我来说,这似乎是一种开始反复破坏 MVVM 的简单方法。行为解决方案实际上是写一次就一直使用。 ViewModel 上的 IsSelected 属性是我用了很长时间的那个。可以轻松创建 Selectable 视图模型,在 T 之上添加 IsSelected 属性。
      【解决方案7】:

      这是 View-Model-ViewModel 模式的另一种变体,其中 ViewModel 可以通过 IView 接口访问视图。

      我遇到了很多不能使用 WPF 绑定的场景,然后你需要在代码中同步 View 和 ViewModel 之间的状态。

      这里显示了如何做到这一点:

      WPF Application Framework (WAF)

      【讨论】:

        【解决方案8】:
        【解决方案9】:

        David Rogers 的解决方案很棒,在以下相关问题中有详细说明:

        Sync SelectedItems in a muliselect listbox with a collection in ViewModel

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-25
          • 1970-01-01
          相关资源
          最近更新 更多