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.

Here is a live demo (if you've got Silverlight installed) - simply type in something and press Enter. The app behaves as if you had clicked the button. There is no really search hooked up though.

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

7 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
Post your comment and continue the discussion.
 
 
 

 

 
Refresh this form if the spam-protection code is not readable, or has expired. (Your input will be preserved)