65.9K
CodeProject 正在变化。 阅读更多。
Home

在 View Model 中管理多选 (.NET Metro Style Apps)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (6投票s)

2012 年 6 月 29 日

CPOL

3分钟阅读

viewsIcon

76562

downloadIcon

3021

本文提供了一种基于附加行为的方法,用于从 View Model 中管理基于集合的 UI 控件中的多选。 本文中的所有代码严格适用于 Win 8 metro style apps。虽然这些行为可以轻松地适应 WPF/Silverlight。

介绍 

本文展示了如何使用附加行为在 View Model 中管理多选。

为什么?

我正在将一些 WPF 代码(一个 Metro 应用程序)重构为 MVVM 设计模式,该模式大量使用了代码隐藏。 我偶然发现了一个需要绑定到 GridViewSelectedItems 属性的需求。 在捕捉模式下有一个列表视图,而在其他模式(完整/填充)下有网格视图。

要求

  1. 在 ViewModel 中定义 SelectedItems
  2. GridView.ItemsSourceListView.ItemsSource 绑定到 Items
  3. 以某种方式将 GridViewListViewSelectedItems 绑定到 SelectedItems
  4. 在步骤 3 之后,希望 GridView 中的任何选择更改都应反映到 ListView,反之亦然。

挑战

  1. SelectedItems 属性是只读的,不能直接设置。
  2. SelectedItems 不能用于绑定表达式,因此无法在 View Model 中检索。
  3. WinRT 不支持行为。 *(出于未知原因,我希望使用附加行为。)* 值得庆幸的是,CodePlex 上存在 WinRTBehaviors。

注意 - WinRT 本身不支持行为。 WinRTBehaviors 是一个用于提供行为支持的开源项目。 这个库很棒,并提供了一个行为扩展,与 WPF 框架完全类似。

附加行为概述

  1. 行为名称 -> MultiSelectBehavior。 它将针对 ListViewBase (原因 -> 提供多选机制的基类)。
  2. SelectedItems 依赖属性添加到行为。 此属性将跟踪关联 UI 元素 (以后将 ListView 派生类称为 UI 元素) 的所选项目。
  3. 在行为的 OnAttachedOnDetached 事件中挂钩 UI 元素的 SelectionChanged 事件。 在 OnSelectionChanged 事件中,将更改同步到 SelectedItems (行为)。 它会将 UI 选择更改传播到 MultiBehavior 中的 SelectedItems
  4. SelectedItems(在行为中)的 PropertyChanged 回调中,侦听绑定对象的 CollectionChanged。 将 CollectionChanged 事件中的更改传播到 UI 元素。
  5. 将行为添加到 XAML 中的 UI 元素。
  6. 定义从 SelectedItems (在行为中) 到 View Model 中 SelectedItems 的数据绑定。

代码演练

ListViewGridView 继承自 ListViewBaseListViewBase 提供了多选机制 (SelectedItemsSelectionMode 属性)。

定义了 MultiSelectBehavior 类,目标是 ListViewBase

public class MultiSelectBehavior : Behavior<ListViewBase>

MultiSelectBehavior 类中创建了 SelectedItems 依赖属性。 它内部保存了 ListViewBase 派生类中的所有选定项目。

public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
    "SelectedItems",
    typeof(ObservableCollection<object>),
    typeof(MultiSelectBehavior),
    new PropertyMetadata(new ObservableCollection<object>(), PropertyChangedCallback));

public ObservableCollection<object> SelectedItems
{
    get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
    set { SetValue(SelectedItemsProperty, value); }
}

在行为中挂钩了 SelectionChanged 事件。

protected override void OnAttached()
{
    base.OnAttached();
    AssociatedObject.SelectionChanged += OnSelectionChanged;
}

protected override void OnDetaching()
{
    base.OnDetaching();
    AssociatedObject.SelectionChanged -= OnSelectionChanged;
}

当在元素上触发 SelectionChanged 时,将填充 SelectedItems_selectionChangedInProgress 标志指示选择更改正在进行中。 如果设置了此标志,则不执行进一步的处理(因为它将触发无限循环和堆栈溢出异常)。

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (_selectionChangedInProgress) return;
    _selectionChangedInProgress = true;
    foreach (var item in e.RemovedItems)
    {
        if (SelectedItems.Contains(item))
        {
            SelectedItems.Remove(item);
        }
    }

    foreach (var item in e.AddedItems)
    {
        if (!SelectedItems.Contains(item))
        {
            SelectedItems.Add(item);
        }
    }
    _selectionChangedInProgress = false;
}

挂钩绑定 ObservableCollection 的集合更改事件。 将任何更改传播到 ListView base 派生 UI 元素。 这在 PropertyChangedCallback 处理程序中完成。

private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
    NotifyCollectionChangedEventHandler handler =  (s, e) => SelectedItemsChanged(sender, e);
    if (args.OldValue is ObservableCollection<object>)
    {
        (args.OldValue as ObservableCollection<object>).CollectionChanged -= handler;
    }

    if (args.NewValue is ObservableCollection<object>)
    {
        (args.NewValue as ObservableCollection<object>).CollectionChanged += handler;
    }
}

private static void SelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (sender is MultiSelectBehavior)
    {
        var listViewBase = (sender as MultiSelectBehavior).AssociatedObject;

        var listSelectedItems = listViewBase.SelectedItems;
        if (e.OldItems != null)
        {
            foreach (var item in e.OldItems)
            {
                if (listSelectedItems.Contains(item))
                {
                    listSelectedItems.Remove(item);
                }
            }
        }

        if (e.NewItems != null)
        {
            foreach (var item in e.NewItems)
            {
                if (!listSelectedItems.Contains(item))
                {
                    listSelectedItems.Add(item);
                }
            }
        }
    }
}

现在 MultiSelectBehavior 已完成。

将行为应用于 UI 元素。

在 XAML 中导入命名空间 ( i -> 行为框架库, custom -> MultiSelectBehavior 类)

xmlns:i ="using:WinRtBehaviors"
xmlns:custom="using:WinRtExt.Behavior" 

添加行为,并将行为的绑定 SelectedItems 附加到 ViewModel 的 SelectedItems

<i:Interaction.Behaviors>
    <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">
    </custom:MultiSelectBehavior>
</i:Interaction.Behaviors>

以下 XAML 适用于两个控件 (一个 ListView,另一个 GridView)

<GridView SelectionMode="Multiple" ItemsSource="{Binding Items}" 
        BorderBrush="White" BorderThickness="2" 
        ItemTemplate="{StaticResource textBlockDataTemplate}">
    <i:Interaction.Behaviors>
        <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">
        </custom:MultiSelectBehavior>
    </i:Interaction.Behaviors>
</GridView>
<Rectangle Width="20"></Rectangle>
<ListView SelectionMode="Multiple" ItemsSource="{Binding Items}" 
       BorderBrush="White" BorderThickness="2" 
       ItemTemplate="{StaticResource textBlockDataTemplate}">
    <i:Interaction.Behaviors>
        <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">
        </custom:MultiSelectBehavior>
    </i:Interaction.Behaviors>
</ListView>

列表视图和网格视图都启用了多选并保持同步。 任何控件中的任何选择更改都会传播到另一个控件。

代码是用 VS2012 RC 编写的,在 Win 8 Release preview 中。(它与旧版本、Win 8 Consumer preview、Win 8 developer preview 不兼容,并且可能会在未来的版本中损坏。)

要使用代码,请复制 MultiSelectBehavior.cs

要执行该应用程序,请在 Win 8 release preview 的 VS2012RC 中打开。构建应用程序,部署应用程序,然后启动该应用程序。

兴趣点 

感谢 WinRTBehaviors ,它让我从 View Model 管理多选,并以文章的形式分享信息。

历史 

这是该文章/源代码的第一个版本。

© . All rights reserved.