Browse by Tags

All Tags » event aggregator   (RSS)

The Problem

A common UI pattern is to expose text in a read-only container, then swap it for an editable input box based on a command. There are multiple approaches to doing this (including just changing the style of the same container). Swapping between a read-only TextBlock to a TextBox is easy enough, but what if you want to also focus and select the TextBox so the user can simply begin typing? And what if the UI elements are nestled deep with in a data template so there is no straightforward way to reference them?

A Solution

I say, "a solution" because there are probably other ones, but this is how I recently tackled the problem.

Unique Identifier

First, I figured no matter how nested the text box would be, it most likely is data bound to some data element. So, in order to uniquely identify the "transaction" I could expose a unique identifier on the bound object. Assume the bound field is "Name":

public string NameIdentifier { get; private set; }

...

// constructor
NameIdentifier = Guid.NewGuid.ToString();

The Message

Using the Event Aggregator pattern, I created a message payload specifically for the "message" that the text item should receive focus:

public class TextFocusEvent
{
    public TextFocusEvent(string identifier)
    {
        Identifier = identifier;
    }
    public string Identifier { get; set; }
}

In the "edit command" we can now publish our message that the text box should receive focus (notice I'm using the DelegateCommand from Prism):

EditCommand = new DelegateCommand<object>(
    obj =>
        {
            if (!EditCommand.CanExecute(obj)) return;

            _oldName = _name;
            IsInEditMode = true;
            EventAggregator.Publish(new TextFocusEvent(NameIdentifier));
        },
    obj => !IsInEditMode);

In this case, you can infer we have an IsInEditMode flag exposed, which we can use to bind the visibility of the TextBlock and TextBox to swap them out. The event saves the old name so if the user cancels, it can be put back. We've just published an event to let the world know that text box deserves some focus. Now let's catch it!

The Behavior

I decided to go with a behavior that could subscribe to the message. Because my event aggregator is based on Reactive Extensions (Rx), instead of being a straight event subscription, it actually returns IObservable, which can be filtered using LINQ. This way an attached behavior can simply listen for the specific identifier it is "tuned" to. We want to databind the identifier because it is generated by the view model, so we expose that property as a dependency property. Here's the behavior:

public class TextBoxFocusListenerBehavior : Behavior<TextBox>
{
    [Import]
    public IEventAggregator EventAggregator { get; set; }

    public static readonly DependencyProperty IdentifierProperty =
            DependencyProperty.Register("Identifier", 
            typeof (string), 
            typeof (TextBoxFocusListenerBehavior),
            new PropertyMetadata(string.Empty));

    private IDisposable _listener;

    public string Identifier
    {
        get { return GetValue(IdentifierProperty).ToString(); }
        set { SetValue(IdentifierProperty, value);}
    }

    protected override void OnAttached()
    {
        if (DesignerProperties.IsInDesignTool)
        {
            return;
        }

        if (EventAggregator == null)
        {
            CompositionInitializer.SatisfyImports(this);
        }

        if (EventAggregator != null)
        {
            var query = from evt in EventAggregator.GetEvent<TextFocusEvent>()
                        where evt.Identifier.Equals(Identifier)
                        select true;

            _listener = query.Subscribe(evt =>
                                            {
                                                if (AssociatedObject.Focus())
                                                {
                                                    AssociatedObject.SelectAll();
                                                }
                                            });
        }

        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        if (_listener != null)
        {
            _listener.Dispose();
        }
        base.OnDetaching();
    }
}

So what's going on? First, we are importing the global event aggregator (we know this fires on the UI thread, so I'm not using a mutex to check to see if I need to satisfy the imports and request the reference from MEF). To stay design-time friendly, we don't try to compose if we're in design mode.

When the behavior is attached, the subscription is made for the event. Notice, however, that a filter is being used to filter only the identifier we are interested in. When a new event is pushed to us, we simply set the focus and auto-select the text. This has the effect of highlighting the text box so the user can begin typing right away. When the behavior is detached, we dispose of the subscription.

The XAML

Now we can put it all together and attach the behavior in our XAML:

<TextBox Text="{Binding Name, Mode=TwoWay}">
    <i:Interaction.Behaviors>
             <behaviors:TextBoxFocusListenerBehavior Identifier="{Binding NameIdentifier}"/>
    </i:Interaction.Behaviors>
</TextBox>
<HyperlinkButton Content="edit" Command="{Binding EditCommand}"/>

I've left out the nuances of the TextBlock and related code to swap into/out of view, but you get the point ... now, even if the text box is buried within a set of data templates, simple data-binding gives us the way to tie the edit event with the focus behavior. Of course, the event aggregator is a generic approach: you could also create a more strongly typed message contract between the behavior and the event.
Jeremy Likness

Thanks to those of you who read my Twist on the Observer pattern and gave me the feedback. You said,

"Hey, Jeremy, that's neat, but there is already a pattern established for what you're talking about, and a few great solutions ready to use. Besides, they are much, much more powerful..."

Thanks to Microsoft MVP Jason Rainwater for taking the time to give me an excellent explanation and for really delving into the inner workings of the solution.

The solution is the event aggregator pattern. For a good introduction, check out Jeremy Miller's brain dump on the topic.

The PRISM/CAL comes with its own event aggregator. It supplies a IEventAggregator that you can wire into your dependency injection container, then reference throughout the project.

In my base service class (seen by all) I create an event like this:

...
public class EntitySelectedEvent : CompositePresentationEvent<MyEntity>
{
}
...

In the view model for the module that has to "wake up" when the entity is selected, I inject the aggregator into the constructor and then do this:

...
eventAggregator.GetEvent<EntitySelectedEvent>().Subscribe(u=>this.entity=u); 
...

When the entity is selected (in a completely different module), I can simply publish:

...
eventAggregator.GetEvent<EntitySelectedEvent>().Publish(entity);
...

That's it! One module listens for the selection and reacts, not caring where/who or how it is published (which means we can publish a test entity in our unit test and test the subscription mechanism) ... while another module publishes the event and doesn't care who is out there listening.

For those of you who weren't familiar with the pattern or the implementation, I hope this helps get you excited and adds another element to your arsenal of coding techniques!

Jeremy Likness