WPF中的附加行为简介

目录

介绍

背景

附加行为

示范

结论

参考


介绍

本文解释了什么是附加行为,以及如何在WPF应用程序中实现它们。本文的读者应该对WPFXAML、附加属性和模型-视图-视图模型(MVVM)模式有些熟悉。我强烈建议您也阅读我的使用ViewModel模式简化WPF TreeView”一文,因为这里的材料是其中介绍的材料的扩展。

背景

早在20085月,我发表了一篇名为使用ViewModel模式简化WPF TreeView”的文章。那篇文章专注于MVVM模式。今天早上,我醒来发现一个名叫Pascal Binggeli的家伙在那篇文章的留言板上提出了一个很好的问题。

Pascal想知道当其关联的ViewModel对象选择它时如何滚动一个TreeViewItemTreeView控件的可视区域中。这似乎很简单,但经过进一步研究,它并不像人们最初期望的那样简单。目标和问题是找到合适的位置,将调用BringIntoView()的代码放在选中的TreeViewItem上,这样就不会违背MVVM模式的原则。

例如,假设用户通过TreeView 搜索其显示文本与用户定义的搜索字符串匹配的项。当搜索逻辑找到匹配项时,匹配的ViewModel对象将其IsSelected属性设置为true。然后,通过数据绑定的魔力,TreeViewItem与该ViewModel对象关联的对象进入选定状态(即,它的IsSelected属性也设置为true)。但是,TreeViewItem不一定会在视图中,这意味着用户不会看到与其搜索字符串匹配的项目。当ViewModel确定它处于选定状态时,Pascal想要一个显示TreeViewItem

ViewModel对象不知道TreeViewItem存在,并且绑定到它们,因此期望ViewModel对象将TreeViewItem带入视图是没有意义的。现在,问题变成了,当ViewModel强制选择它时,谁负责将其TreeViewItem放入可视区?

我们当然不想将该代码放入ViewModel中,因为它在ViewModel对象和可视元素之间引入了人为且不必要的耦合。我们不想将该代码放在TreeView绑定到ViewModel的每个位置的代码隐藏中,因为它重新引入了我们首先使用ViewModel避免的一些问题。我们可以创建一个具有内置支持的TreeViewItem子类,以便在选择时将其自身显示出来,但是,在WPF世界中,这绝对是一个轻量级问题的强硬解决方案。

我们如何以一种轻量级和可重用的方式优雅地解决这个问题?

附加行为

上述问题的解决方案是使用附加行为。将行为附加到对象只是意味着让对象做一些它自己不会做的事情。以下是我在“使用WPF TreeView中的复选框一文中所写的附加行为的解释:

这个想法是您在元素上设置附加属性,以便您可以从公开附加属性的类中访问该元素。一旦该类可以访问该元素,它就可以在其上挂钩事件,并响应这些事件触发,使该元素执行它通常不会执行的操作。它是创建和使用子类的一种非常方便的替代方法,并且对XAML非常友好。

在那篇文章中,演示应用程序以复杂的方式使用附加行为,但在本文中,我们将保持简单。足够的背景和理论,让我们看看如何创建一个附加的行为来解决我们的朋友Pascal提出的问题。

示范

本文的演示应用程序(可在本页顶部下载)使用使用ViewModel模式简化WPF TreeView”一文提供的文本搜索演示。我做了一些更改,例如向TreeView中添加更多项目、增加字体大小以及添加附加行为。附加的行为在一个名为TreeViewItemBehavior的新的静态类中。该类公开了一个Boolean附加属性,该属性可以在上TreeViewItem设置,名为IsBroughtIntoViewWhenSelected 。下面是TreeViewItemBehavior类:

/// <summary>
/// Exposes attached behaviors that can be
/// applied to TreeViewItem objects.
/// </summary>
public static class TreeViewItemBehavior
{
    #region IsBroughtIntoViewWhenSelected

    public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
    }

    public static void SetIsBroughtIntoViewWhenSelected(
      TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
    }

    public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
        DependencyProperty.RegisterAttached(
        "IsBroughtIntoViewWhenSelected",
        typeof(bool),
        typeof(TreeViewItemBehavior),
        new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));

    static void OnIsBroughtIntoViewWhenSelectedChanged(
      DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        TreeViewItem item = depObj as TreeViewItem;
        if (item == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            item.Selected += OnTreeViewItemSelected;
        else
            item.Selected -= OnTreeViewItemSelected;
    }

    static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
    {
        // Only react to the Selected event raised by the TreeViewItem
        // whose IsSelected property was modified. Ignore all ancestors
        // who are merely reporting that a descendant's Selected fired.
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        TreeViewItem item = e.OriginalSource as TreeViewItem;
        if (item != null)
            item.BringIntoView();
    }

    #endregion // IsBroughtIntoViewWhenSelected
}

上面看到的附加行为基本上只是一种奇特的方式,用来连接TreeViewItemSelected属性,当事件被引发时,调用该项上的BringIntoView()。这个难题的最后一部分是查看TreeViewItemBehavior类是如何获得对TreeView中每个TreeViewItem的引用的。我们通过给TreeView中每个项目的Style添加一个Setter来实现这一点,如下所示:

<TreeView.ItemContainerStyle>
  <Style TargetType="{x:Type TreeViewItem}">
    <!--
    This Setter applies an attached behavior to all TreeViewItems.
    -->
    <Setter 
      Property="local:TreeViewItemBehavior.IsBroughtIntoViewWhenSelected" 
      Value="True" 
      />

    <!-- 
    These Setters bind a TreeViewItem to a PersonViewModel.
    -->
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    <Setter Property="FontWeight" Value="Normal" />
    <Style.Triggers>
      <Trigger Property="IsSelected" Value="True">
        <Setter Property="FontWeight" Value="Bold" />
      </Trigger>
    </Style.Triggers>
  </Style>
</TreeView.ItemContainerStyle>

当演示应用程序加载时,搜索文本将自动设置为字母Y。单击Find按钮几次,您会看到每次选择一个项目时,它将包含字母Y并滚动到视图中。它在被选中时滚动到视图中的事实意味着附加的行为正常工作。

结论

将一个事件挂在一个物体上并在它触发时做某事肯定不是一个突破性的创新,无论想象如何。从这个意义上说,附加行为只是做同样的事情的另一种方式。然而,这种技术的重要性在于它有一个名称,这可能是任何设计模式中最重要的方面。此外,您可以创建附加行为并将它们应用于任何元素,而无需修改系统的任何其他部分。这是对Pascal Binggeli提出的问题以及许多其他问题的干净解决方案。这是您的工具箱中非常有用的工具。

参考

https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值