Click here to Skip to main content
15,888,351 members
Articles / Desktop Programming / WPF

Introduction to Attached Behaviors in WPF

Rate me:
Please Sign up or sign in to vote.
4.94/5 (103 votes)
30 Aug 2008CPOL5 min read 757.5K   9.6K   245   106
Explains the concept of attached behaviors and shows how to use them in the context of the MVVM pattern.

AttachedBehavior.jpg

Introduction

This article explains what an attached behavior is, and how you can implement them in a WPF application. Readers of this article should be somewhat familiar with WPF, XAML, attached properties, and the Model-View-ViewModel (MVVM) pattern. I highly recommend that you also read my ‘Simplifying the WPF TreeView by Using the ViewModel Pattern’ article, because the material here is an extension of the material presented in it.

Background

Back in May of 2008, I published an article called ‘Simplifying the WPF TreeView by Using the ViewModel Pattern’. That article focused on the MVVM pattern. This morning, I woke up to find that a fellow by the name of Pascal Binggeli had asked an excellent question on that article’s message board.

Pascal wanted to know how to scroll a TreeViewItem into the viewable area of the TreeView control when its associated ViewModel object selects it. That seems simple enough, but upon further examination, it is not quite as straightforward as one might initially expect. The objective, and problem, is to find the proper place to put code that calls BringIntoView() on the selected TreeViewItem, such that the principles of the MVVM pattern are not violated.

For example, suppose that the user searches through a TreeView for an item whose display text matches a user-defined search string. When the search logic finds a matching item, the matching ViewModel object will have its IsSelected property set to true. Then, via the magic of data binding, the TreeViewItem associated with that ViewModel object enters into the selected state (i.e., its IsSelected property is set to true, too). However, that TreeViewItem will not necessarily be in view, which means the user will not see the item that matches their search string. Pascal wanted a TreeViewItem brought into view when the ViewModel determines that it is in the selected state.

The ViewModel objects have no idea that a TreeViewItem exists, and is bound to them, so it does not make sense to expect the ViewModel objects to bring TreeViewItems into view. The question becomes, now, who is responsible for bringing a TreeViewItem into view when the ViewModel forces it to be selected?

We certainly do not want to put that code into the ViewModel because it introduces an artificial, and unnecessary, coupling between a ViewModel object and a visual element. We do not want to put that code in the code-behind of every place a TreeView is bound to a ViewModel, because it reintroduces some of the problems that we avoid by using a ViewModel in the first place. We could create a TreeViewItem subclass that has built-in support for bringing itself into view when selected, but, in the WPF world, that is definitely a heavy-handed solution to a lightweight problem.

How can we elegantly solve this problem in a lightweight and reusable way?

Attached Behaviors

The solution to the problem explained above is to use an attached behavior. Attaching a behavior to an object simply means making the object do something that it would not do on its own. Here is the explanation of attached behaviors that I wrote in my ‘Working with CheckBoxes in the WPF TreeView’ article:

The idea is that you set an attached property on an element so that you can gain access to the element from the class that exposes the attached property. Once that class has access to the element, it can hook events on it and, in response to those events firing, make the element do things that it normally would not do. It is a very convenient alternative to creating and using subclasses, and is very XAML-friendly.

In that article, the demo application uses attached behaviors in complicated ways, but in this article, we will keep it simple. Enough with the background and theory, let’s see how to create an attached behavior that solves the problem posed by our friend Pascal.

Demonstration

This article’s demo app, which is available for download at the top of this page, uses the Text Search demo provided by the ‘Simplifying the WPF TreeView by Using the ViewModel Pattern’ article. I made a few changes, such as adding more items to the TreeView, increasing the font size, and adding an attached behavior. The attached behavior is in a new static class called TreeViewItemBehavior. That class exposes a Boolean attached property that can be set on a TreeViewItem, called IsBroughtIntoViewWhenSelected. Here is the TreeViewItemBehavior class:

C#
/// <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
}

The attached behavior seen above is basically just a fancy way of hooking the Selected property of a TreeViewItem and, when the event is raised, calling BringIntoView() on the item. The final piece of this puzzle is seeing how the TreeViewItemBehavior class gets a reference to every TreeViewItem in the TreeView. We accomplish that by adding a Setter to the Style applied to every item in the TreeView, as seen below:

XML
 <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>

When the demo application loads up, the search text will be set to the letter Y automatically. Click the Find button a few times, and you will see that each time an item is selected, it will contain the letter Y and will scroll into view. The fact that it scrolls into view upon being selected means that the attached behavior is working properly.

Conclusion

Hooking an event on an object and doing something when it fires is certainly not a breakthrough innovation, by any stretch of the imagination. In that sense, attached behaviors are just another way to do the same old thing. However, the importance of this technique is that it has a name, which is probably the most important aspect of any design pattern. In addition, you can create attached behaviors and apply them to any element without having to modify any other part of the system. It is a clean solution to the problem raised by Pascal Binggeli, and many, many other problems. It's a very useful tool to have in your toolbox.

References

Revision History

  • August 30, 2008 – Created the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Josh creates software, for iOS and Windows.

He works at Black Pixel as a Senior Developer.

Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.

Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.

Check out his Advanced MVVM[^] book.

Visit his WPF blog[^] or stop by his iOS blog[^].

See his website Josh Smith Digital[^].

Comments and Discussions

 
GeneralRe: Error in VB implementation Pin
RokShox15-Nov-08 20:30
RokShox15-Nov-08 20:30 
QuestionViewModel Factory ? Pin
quicoli3-Oct-08 1:30
quicoli3-Oct-08 1:30 
AnswerRe: ViewModel Factory ? Pin
Josh Smith3-Oct-08 4:31
Josh Smith3-Oct-08 4:31 
GeneralIt's fine, but not work with virtualization Pin
ivchpavel24-Sep-08 10:36
ivchpavel24-Sep-08 10:36 
GeneralRe: It's fine, but not work with virtualization Pin
Josh Smith26-Sep-08 6:21
Josh Smith26-Sep-08 6:21 
GeneralRe: It's fine, but not work with virtualization Pin
ivchpavel28-Sep-08 22:19
ivchpavel28-Sep-08 22:19 
GeneralRe: It's fine, but not work with virtualization Pin
JoshP8412-Jun-09 15:03
JoshP8412-Jun-09 15:03 
GeneralRe: It's fine, but not work with virtualization [modified] Pin
dhdh1-Dec-10 11:02
dhdh1-Dec-10 11:02 
Josh,

Did you ever look at this again? Have you come up with any solution? I agree with the last poster than Ben Carter's solution based on knowing an index isn't very useful - in a realistic scenario, how will you know the index of the 2314th child of a child of a child of a child of the root?

I tried your modification above (simply providing an explicit VSP instead of using the attached IsVirtualizaton property) in your demo app, and unless I'm doing something wrong, it actually doesn't work (WinXP, VS 2008, .NET 3.5 sp1). There is no virtualization with this technique, which is why the search/select worked. You can't tell from running your demo because you have so few data items there isn't any display delay regardless of whether virtualization is actually on. I tried adding another 1500 or so data items to the deepest Person, enough so that there is a noticeable display delay when opening that node without virtualization. And there is no difference in my testing between your original demo and with your proposed modification above - both have a very noticeable display delay. But with the attached IsVirtualization property set to true, there is essentially no display delay.

The original problem - of having the "selected" event not fire until the node is manually scrolled into view, when virtualization is turned on - seems pretty significant, and seems to me will eliminate the possibility of using TreeView virtualization in many situations. When I first read about the possibility of TreeView virtualization, and turned it on in our project and got a 10x improvement in display time for a multi-thousand-item subtree, I almost jumped out of my chair with joy. Until I found that our "find" feature was broken.

I would really appreciate any further thoughts you may have on this issue.

Thanks for your time, and for your excellent articles.


Doug

modified on Thursday, December 2, 2010 8:43 AM

GeneralRe: It's fine, but not work with virtualization Pin
r0xz0rz9-Dec-10 10:53
r0xz0rz9-Dec-10 10:53 
GeneralRe: It's fine, but not work with virtualization Pin
Member 44329751-Apr-15 5:28
Member 44329751-Apr-15 5:28 
GeneralRe: It's fine, but not work with virtualization Pin
Josh Smith16-Oct-08 15:41
Josh Smith16-Oct-08 15:41 
GeneralRe: It's fine, but not work with virtualization Pin
ChrisN52-Mar-10 13:50
ChrisN52-Mar-10 13:50 
GeneralRe: It's fine, but not work with virtualization Pin
Kunal Choudhary8-Feb-11 5:02
Kunal Choudhary8-Feb-11 5:02 
GeneralRe: It's fine, but not work with virtualization Pin
ChrisN52-Mar-10 13:47
ChrisN52-Mar-10 13:47 
GeneralRe: It's fine, but not work with virtualization Pin
DaveCSumo26-Apr-12 22:52
DaveCSumo26-Apr-12 22:52 
GeneralGreat Articles Josh! Pin
mtonsager23-Sep-08 2:21
mtonsager23-Sep-08 2:21 
GeneralRe: Great Articles Josh! Pin
Josh Smith23-Sep-08 2:40
Josh Smith23-Sep-08 2:40 
Generalnice one agent smith Pin
Sacha Barber11-Sep-08 20:46
Sacha Barber11-Sep-08 20:46 
GeneralRe: nice one agent smith Pin
Josh Smith12-Sep-08 3:35
Josh Smith12-Sep-08 3:35 
GeneralYou are a real WPF guru Pin
cwp424-Sep-08 22:03
cwp424-Sep-08 22:03 
GeneralRe: You are a real WPF guru Pin
Josh Smith5-Sep-08 6:33
Josh Smith5-Sep-08 6:33 
GeneralSweet. Pin
valure2-Sep-08 10:11
valure2-Sep-08 10:11 
GeneralRe: Sweet. Pin
Josh Smith2-Sep-08 10:30
Josh Smith2-Sep-08 10:30 
GeneralGreat (as usual) Pin
corcav31-Aug-08 7:25
corcav31-Aug-08 7:25 
GeneralRe: Great (as usual) Pin
Josh Smith31-Aug-08 9:15
Josh Smith31-Aug-08 9:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.