在WPF应用程序开发中,经常会遇到需要为控件添加一些额外行为的需求。这些行为可能是控件本身不提供的功能,或者希望以一种更优雅、更可重用的方式实现这些功能。在这种情况下,附加行为(Attached Behavior)提供了一种非常灵活的解决方案。本文将介绍附加行为的概念,以及如何在WPF应用程序中实现和使用它们。
在2008年5月,发表了一篇名为《使用ViewModel模式简化WPF TreeView》的文章,该文章主要讨论了MVVM模式。今天早上,收到了一个名叫Pascal Binggeli的人在文章留言板上提出的一个优秀问题。Pascal想知道,当其关联的ViewModel对象选择它时,如何将TreeViewItem滚动到TreeView控件的可视区域内。这个问题看似简单,但经过深入研究,发现它并不像最初想象的那样直接。目标和问题在于找到合适的位置来放置调用BringIntoView()方法的代码,以便不违反MVVM模式的原则。
例如,假设用户在TreeView中搜索一个显示文本与用户定义的搜索字符串匹配的项。当搜索逻辑找到一个匹配项时,匹配的ViewModel对象的IsSelected属性将被设置为true。然后,通过数据绑定的魔力,与该ViewModel对象关联的TreeViewItem进入选中状态(即,其IsSelected属性也被设置为true)。然而,该TreeViewItem并不一定在视图中,这意味着用户将看不到与他们的搜索字符串匹配的项。Pascal希望当ViewModel确定它处于选中状态时,TreeViewItem被带入视图。
解决上述问题的方案是使用附加行为。将行为附加到对象意味着让对象做一些它自己不会做的事情。在之前的文章《在WPF TreeView中使用CheckBox》中,对附加行为的解释如下:这个想法是在元素上设置一个附加属性,以便可以从暴露附加属性的类中访问该元素。一旦该类访问了元素,它就可以在它上面挂钩事件,并且响应这些事件的触发,使元素做一些它通常不会做的事情。这是一种非常方便的替代创建和使用子类的方案,并且非常适合XAML。
本文的演示应用程序,可以在本页顶部下载,使用了“使用ViewModel模式简化WPF TreeView”文章中提供的文本搜索演示。做了一些改动,比如向TreeView添加了更多的项,增加了字体大小,并添加了一个附加行为。附加行为位于一个名为TreeViewItemBehavior的新静态类中。该类公开了一个可以设置在TreeViewItem上的Boolean附加属性,称为IsBroughtIntoViewWhenSelected。以下是TreeViewItemBehavior类:
public static class TreeViewItemBehavior
{
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)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource)) return;
TreeViewItem item = e.OriginalSource as TreeViewItem;
if (item != null)
item.BringIntoView();
}
}