Silverlight Behaviors

A framework for creating reusable and attachable behaviors in Silverlight, and a sample behavior that demonstrates using and building them... along with all the code.

In the past, I've written about behaviors and control extenders in the context of Ajax and HTML-based UI. This post introduces a similar behavior semantics for Silverlight and XAML-based UI as well.

The mini-behavior framework I have prototyped allows you to write and use behavior as a combination of a component + an attached property. The framework provides the logic to attach/detach behaviors for behavior authors. The rest of this post demonstrates using and writing a simple behavior based on this framework. Over the next few posts, I'll discuss a few more behaviors as a way to further experiment and demonstrate the behavior concept.

Using the DefaultCommit Behavior
Lets say you're implementing a typical form with a TextBox and a search Button. You'll probably want to allow users to press the Enter key after typing in some search keywords to kick off the search rather than force them to click the button explicitly. What you'd normally do is write a couple of event handlers to handle both the Click event from the Button and the KeyDown event from the TextBox (to look for the Enter key) in your code-behind, and trigger the search logic from each of them. This is more complicated and messy than it needs to be.

Ideally you simply want to handle a single event - the button's Click event. In fact, in HTML forms, this is built in into the notion of a Form with a submit button. However, it isn't so in Silverlight, where a Form concept is baked in. This is where this behavior comes in. Once this behavior is available, I can author the following XAML:

<TextBox x:Name="searchTextBox">
  <f:Form.Commit>
    <f:DefaultCommit ButtonName="searchButton" />
  </f:Form.Commit>
</TextBox>
<f:Button x:Name="searchButton" Content="Search"
  Click="OnSearchButtonClick" />

Essentially the DefaultCommit behavior is attached to the TextBox using the XAML syntax above. For those new to XAML and Silverlight, f:Form.Commit refers to the Commit attached property offered by the Form class. This is similar to Canvas.Left, which you've probably encountered. The only difference is that the type of the Canvas.Left attached property is a simple double value, whereas the type of the Form.Commit property is a DefaultCommit.

Underneath the covers, the DefaultCommit behavior does the same thing you'd do in code-behind, but does so in a way that the logic is now encapsulated and is easily reusable. You can instantiate as many of these as you want, especially across multiple forms/pages without duplicating the event handling logic all over.

You might have noticed the use of <f:Button> instead of a regular Button. Unfortunately, unlike WinForms and HTML buttons, the Silverlight Button control currently doesn't have any way to programmatically invoke the Click event, so I had to add that functionality in a derived class.

Implementing the DefaultCommit Behavior
The behavior framework I have put together introduces two classes Behavior and BehaviorManager.

The first step is to implement a behavior by deriving from the Behavior class. The Behavior class is a generic class, to allow indicating the type of objects that behavior can be associated with.

public class DefaultCommit : Behavior<TextBox> {
}

The next step is to add any properties exposed by the behavior itself.

public class DefaultCommit : Behavior<TextBox> {

    private string _buttonName;
    
    public string ButtonName {
        get { return _buttonName; }
        set { _buttonName = value; }
    }
}

The behavior base class holds the reference to the associated object as its AssociatedObject property. It defines a couple of abstract methods to manage the lifetime of a behavior instance: OnAttach, and OnDetach. The derived behavior class implements these to perform associated logic, such as setup and cleanup including subscribing and unsubscribing to events raised by the associated object.

public class DefaultCommit : Behavior<TextBox> {

    protected override void OnAttach() {
        AssociatedObject.KeyDown += OnTextBoxKeyDown;
    }
    
    protected override void OnDetach() {
        AssociatedObject.KeyDown -= OnTextBoxKeyDown;
    }
    
    private void OnTextBoxKeyDown(object sender, KeyEventArgs e) {
        if ((e.Key == Key.Enter) && (AssociatedObject.Text.Length != 0)) {
            Button button = AssociatedObject.FindName(_buttonName) as Button;
            if (button != null) {
                button.PerformClick();
            }
        }
    }
}

The final step is to define an attached property that can be used to declaratively create an instance of this behavior and have it be associated with a TextBox. This is where the BehaviorManager class will come into play. It provides a set of utility methods that correctly attach/detach behaviors.

public static class Form {

    public static readonly DependencyProperty CommitProperty =
        DependencyProperty.RegisterAttached("Commit", typeof(Form), typeof(DefaultCommit),
            OnCommitPropertyChanged);
    
    public static DefaultCommit GetCommit(TextBox textBox) {
        return BehaviorManager.GetBehavior<DefaultCommit>(textBox);
    }
    
    public static void SetCommit(TextBox textBox, DefaultCommit commitBehavior) {
        BehaviorManager.SetBehavior(textBox, commitBehavior);
    }
    
    private void OnCommitPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
        BehaviorManager.UpdateBehavior(o, CommitProperty,
                                       (DefaultCommit)e.OldValue,
                                       (DefaultCommit)e.NewValue);
    }
}

There you go... writing a basic behavior is quite straightforward.

Concepts and Next Steps
One rough analogy to behaviors is that of extension methods in C# 3.0. Those are at the method level. Behaviors are similar, but are live components. More specifically, a behavior is a component that encapsulates some functionality and can be attached to another component to extend its built-in functionality, without creating a derived class. A couple of example behaviors that can be attached to a TextBox besides the one implemented above include auto-complete and input filtering.

The fact that behaviors are attached components that implies a composition-like metaphor, offers two advantages whenever derivation is non-essential:

  • Allows "extending" multiple component types - in the TextBox example, the behaviors can be attached not only to TextBox but also to WatermarkTextBox.
  • A single TextBox can be associated with multiple behaviors as needed, rather than requiring the developer to build and use a kitchen-sink style control that has to anticipate every possible feature.

Almost all behaviors handle events and encapsulate a set of event handlers into a reusable component that can then be instanced declaratively. Often this leads to a more declarative style of programming (something I love) and reduced code-behind clutter.

I chose a simple behavior scenario to introduce the concept. In a future post, I'll demonstrate some more interesting behaviors, including AutoComplete , and some animation scenarios. Stay tuned. Over time, I'll continue to experiment with this. I have some ideas on implementing declarative views that are enabled via this concept.

I'd love to hear ideas you might have for behaviors that would be useful additions for Silverlight and XAML-based UI, and especially for RIA scenarios.

You can also download the code which contains the behavior framework, the sample behavior, and a sample app that makes use of them.


[ Tags: | | ]
Posted on Monday, 5/5/2008 @ 7:46 AM | #Silverlight


Comments

26 comments have been posted.

Henrik

Posted on 5/5/2008 @ 11:52 PM
Excellent stuff! Would it have been possible to solve the button problem in the same way? Just by attaching the PerformClick event to the normal Button class instead of creating a derived class? I'm asking because I'm currently in the planning stage for en enterprise level Silverlight application and I need to decide now whether to create derived classes for all UI controls or to use attached behaviors instead.

Nikhil Kothari

Posted on 5/6/2008 @ 12:12 AM
Henrik - good question.

Behaviors pretty much can only do things that are possible with the public OM of a control, or make use of public composability APIs... anything that requires access to protected APIs, changing control templates and the like require derivation.

For example, I wanted to implement Watermark as a behavior. Currently there is a derived WatermarkTextBox. Unfortunately this isn't possible, unless TextBox was designed for that level of extensibility (such as having a public adornment layer to render the watermark in).

Jatan Porecha

Posted on 5/6/2008 @ 10:19 PM
Hi Nikhil,
I am unable to view the silverlight content in this blog. I have installed the Silverlight plugin on IE 7 browser. However the other websites having silverlight content work properly.
Can you please let me know any configuration, I would have to make in order to see the silverlight content.

Thanks,
Jatan

Nikhil Kothari

Posted on 5/6/2008 @ 10:36 PM
Hi Jatan, the demos here use Silverlight 2. Is that the same version you've installed?

Any chance you've downloaded the sample, and run locally? If so, did that work - trying to isolate if its something to do with the way the Silverlight content is loading here on this site, vs. the some potential issue in the code itself.

Ryan Heath

Posted on 5/7/2008 @ 12:20 AM
I have to hover with the cursor above the silverlight area before I can actually see its contents.
Is that on purpose?

Anyway, great stuff! Is this going into the direction of something like the ajaxtoolkit? ;)

// Ryan

Nikhil Kothari

Posted on 5/7/2008 @ 7:41 AM
I noticed I needed to hover sometimes as well... certainly not intentional. The Silverlight app is in an iframe... I wonder if that causes this. I also noticed it in IE, and not FF.

It would certainly be interesting to see something like the toolkit. If there are suggestions for other behaviors, let me know.

Jatan Porecha

Posted on 5/7/2008 @ 9:58 PM
Thanks Nikhil,

It worked. I had Silverlight 1 installed. So as per you advice, I installed Silverlight 2 Beta and then I could see your silverlight examples.
Nice snippets.

Regards,
Jatan

Anthony Grace

Posted on 5/26/2008 @ 6:04 AM
Hi Nikhil,

Every time, I tried to view this posting in Firefox 3, the browser crashed. This also happens when navigating from my hotmail to msn.com home page... any ideas?

Anthony :-)

Nikhil Kothari

Posted on 5/26/2008 @ 11:36 PM
Anthony, if I remember correctly, I know SL does not currently work on Firefox 3 Beta...

KierenH

Posted on 7/21/2008 @ 6:08 PM
I can use a Popup to include UI functionality in a behavior.

Would it be possible to show a Control?

In my case, I have a control with a TemplatePart( Name="Popup", Type=typeof( Popup ) ). The OnApplyTemplate is never invoked, and I think it's because behavior isn't part of the VisualTree.

KierenH

Posted on 7/22/2008 @ 6:03 AM
I realised a behavior was not really necessary, as I could get appropriate isolation of functionality and look and feel making better use of DPs.
I'm still curious about whether the behavior is part of the Visual Tree, and how this might affect a templated control.
Cheers

John "Z-Bo" Zabroski

Posted on 7/23/2008 @ 2:02 PM
Where is your explanation for Behavior?

In my humble opinion, you should have introduced the Behavior class implementation or at the very least it's full interface in a separate post.

NT

Posted on 7/24/2008 @ 6:45 AM
Hi there,

I found your solution too intrusive and I could not redesign the entire code just for this simple thing.
After some searching, I found nothing, so I used intellisense and some common sense and found
a very simple way of triggering an event. In my instance, I wanted to submit when enter was pressed:

private void KeyDownTextBox(object sender, System.Windows.Input.KeyEventArgs e)
{

if(e.Key == Key.Enter)
if (InputTextBox.Text.Length != 0)
{
MouseButtonEventArgs mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left);
mbea.RoutedEvent = Mouse.MouseDownEvent;
SubmitButton.RaiseEvent(mbea);
}
}

John "Z-Bo" Zabroski

Posted on 7/25/2008 @ 6:20 PM
NT,

By "too intrusive" are you referring to the fact he has to subclass the control to invoke the Click event? I believe that will be fixed when Silverlight 2 RTM comes. I have found that WinForms programmers struggle making the transition from the WinForms way of programmatically invoking Click. WPF does it a completely different way. Arguably, a better way.

Nikhil, do you think you could eventually talk about automation peers and declaratively clicking a button?

Julian Dominguez

Posted on 8/8/2008 @ 7:18 AM
This is a great post with a really good code sample! I wanted to share an example of using attached behaviors in Silverlight to emulate the ICommand interface behavior present in WPF.
http://blogs.southworks.net/jdominguez/2008/08/icommand-for-silverlight-with-attached-behaviors/
I hope you find it useful,
Julian

Nikhil Kothari

Posted on 8/11/2008 @ 12:18 PM
@KierenH - the behavior is not part of the visual tree... however its associated object is, and so a behavior does have access to the visual tree to navigate it, modify it etc.

@NT - I think what you're calling intrusive is the additional XAML. Its partly a matter of personal opinion. I personally would love to see the user interface more and more declarative, so useful functionality is encapsulated in components, that are both designable, and also help avoid writing tons of code-behind that is hard, if not impossible to test.

@John "Z-Bo" Zabroski - the guts of the behavior aren't that interesting, but you can see them by downloading the code. The most interesting points are the OnAttach, OnDetach, and AssociatedObject APIs, which I've shown above.

I'll need to look at using automation peers to do the programmatic clicking, i.e. something that was much simpler in WinForms... this has come up before, but it seems odd to trigger the accessibility machinery just for this.

João Paulo P Bochi

Posted on 9/3/2008 @ 2:56 PM
Great post! That's just what I was looking for.

But, the Silverlight plugin is not working (in firefox, at least). It requires silverlight 2 b1, but the current version is 2 b2. I admit the silverlight is not my specialty, but I believe you easily fix the pugin version in this page.

thanks,
JP

John "Z-Bo" Zabroski

Posted on 9/26/2008 @ 7:04 PM
@Nikhil

In the Java World, we just called them Robots, and could be used as accessibility machinary, but really just a way to automate a task.

Note, though, that by introducing an automation peer necessitates that you've removed programmatic clicking, and turned it into a scriptable declarative task, rather than an imperative task that must go in the code behind. Therefore, you can expose clicks using markup. Moreover, it is not merely automation that is cool, it is the concept of a peering model: Independent, but related objects serving each other as peers.

The only support SL2 has for declarative automation, of any kind, is storyboards.

Either way, you should be building an expressive application protocol independent of the programmatic API. Otherwise, you are limiting your designs to the programmatic API. This is an anti-pattern. Avoiding this anti-pattern set apart good Java UI developers from the bad ones; although even then it was still a battle removing boilerplate code and keeping in check Swing's tendency to overuse factory patterns where a strategy pattern made more architectural sense.

Phil Cockfield

Posted on 3/6/2009 @ 2:26 PM
Hi Nikhil,

This is totally awesome....thanks! I'm just grabbed the latest drop of your Framework (2/27/2009) and I can't find the 'BehaviorManager' class that you reference above in there. Am I missing something stupid, or have you perhaps changed the framework since writing this post?

Thanks!

Phil Cockfield

Posted on 3/6/2009 @ 4:01 PM
No worries ... I see what was going on inside BehaviorManager now (for the most part I think)....thanks.

ahdjkfas

Posted on 3/9/2009 @ 4:12 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:12 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:12 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:12 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

Richard

Posted on 5/22/2009 @ 8:06 PM
I'm missing the BehaviorManager class. It is not part of SilverlightFX. Also had similar trouble with the Button reference which should be Xbutton (I managed to figure that out). Any help will be appreciated. Thanks.

Aakash

Posted on 9/2/2009 @ 12:31 AM
Hi Nikhil, that's a wonderful post.
I have a question - is it possible to use Behaviors with Data Bound Controls i.e. say a Hyperlink button within a Data Form/Grid. Because when I tried to do that, it started throwing an error saying - Undeclared prefix. Is there something more to using Behaviors within Data Forms/Grids ?
Thanks!
Post your comment and continue the discussion.