Task-It Series

This post is part of a series of blog posts and videos about the Task-It (task management) application that I have been building with Silverlight 4 and Telerik's RadControls for Silverlight 4. For a full index of these resources, please go here. One of the posts listed in the index provides a full source download for the application, and I will be referring to that source code in this post.

Communicating between components

In any Silverlight application there are going to be times when you want to communicate between different components in your app. These could be UserControls, view model classes, whatever. To do this you need a framework for publishing events and subscribing to them. So basically, you fire off an event when something happens and 0 or more other components out there listen for that event, and respond when it is fired. This is known as the Observer Pattern, and is a basic need in any Silverlight application (i.e. if you're not using it you should be).

In an earlier version of Task-It I used Prism’s EventAggregator framework for this sort of event management, but ended up moving to the MVVM Light Toolkit’s implementation because it was simpler, and involved less code.

The need for events

First let’s talk about a practical example. When the user clicks a node in the RadTreeView on the left side of the Tasks page, the RadGridView on the right side needs to know about it...or more specifically (because I’m using MVVM), the view model that drives it. After all, the RadTreeView and RadGridView do not live in the same user control and do not share the same view model.

In this case there is only one component that needs to know about this event, but there could be more. For example, what if I decided to add some sort of property grid to the right side of the Tasks page, and when a tree view node is clicked it would load information related to that node? Now we’d have 2 components that have a ‘need to know’.

Simple event sample

OK, so let’s look at how to publish and subscribe to events using the MVVM Light Toolkit’s Messenger class (it’s actually quite simple). To distinguish one event from another you pass a unique object when it is fired, so let’s say we want to pass a Person object, we could simply fire the event like this (assuming that 'person' is an instance of a Person object):

Messenger.Default.Send(person);

And listen for it like this:

Messenger.Default.Register<Person>(this, OnPersonSelected);

It really is that simple.

Notice that the Person object will be passed as an argument to the OnPersonSelected method that was defined as the method I want to call when the subscriber receives the event, so I can then do whatever I want with that Person object in that method.

public void OnPersonSelected(Person person)
{
...
}

Task-It sample (firing the event)

OK, now back to our Task-it example. To implement the RadTreeView example mentioned above I created a class called EntityFilterArgs. I called it that because it is fired when an Entity object (TaskCategory, Project, Context or Contact) is selected in the tree view. It is then used to filter the data in the RadGridView to display only the tasks related to that entity. In hindsight, EntitySelectedArgs might have made more sense since I just mentioned a property grid example that wouldn’t really involve ‘filtering’, so I may do a little refactoring on that one. :-)

First let’s take a look at the EntityFilterArgs class. Most of my event args classes live in EventArgs.cs in the TaskIt projects Core directory. I don’t usually put multiple classes in a single .cs file, but in this case the classes are all very small, so I’d rather not have a ton of .cs files.

public class EntityFilterArgs : EntityArgs
{
    public long ID { get; set; }
}
 
public abstract class EntityArgs
{
    public EntityType EntityType { get; set; }
}

So you can see that it only has one property, ID, though it does inherit from EntityArgs, which has an EntityType property...so it really has 2 properties. OK, so we’re going to pass an object that contains information about which type of Entity object was clicked, and what it’s ID is.

Now let’s look at where the event is fired in TreeViewModel.cs under the ViewModels directory in the TaskIt.Modules.Tasks project (we are in the Tasks page now, so that is why this code lives here). First, every time the user clicks a node in the RadTreeView the setter for the SelectedItem in our view model gets called. I had to set Mode=TwoWay on my SelectedItem binding for this to work. This is what determines whether the view model will be notified of changes in the UI.

public Entity SelectedItem
{
    get { return _selectedItem; }
    set
    {
        if (!IsEntity(value) || value == _selectedItem)
        {
            return; // Ignore left mouse clicks on top-level nodes
        }
        _selectedItem = value;
        FireFilterEvent(value, value.GetType());
        this.OnPropertyChanged(p => p.SelectedItem);
    }
}

Notice that in the setter FireFilterEvent is called. If you’re following along in the code, this is a good place to set a breakpoint. FireFilterEvent has a bit of complexity because I need to figure out what kind of node was clicked (TaskCategory, Project, Context or Contact). Once I’ve figured that out (you can step through the code to see how I do that) I can grab the ID from the selected Entity and now I have all the information I need. The last line fires the event using Messenger.Default.Send as I showed in the example above.

private static void FireFilterEvent(Entity entity, Type type)
{
    var relationType = GetEntityType(type);
    var info = new EntityFilterArgs {EntityType = relationType};
     
    switch (relationType)
    {
        case EntityType.TaskCategory:
            info.ID = ((TaskCategory) entity).ID;
            break;
        case EntityType.Project:
            info.ID = ((Project) entity).ID;
            break;
        case EntityType.Context:
            info.ID = ((Context) entity).ID;
            break;
        case EntityType.Contact:
            info.ID = ((Contact) entity).ID;
            break;
    }
 
    Messenger.Default.Send(info);           
}


Task-It sample (listening for the event)


OK, so now the event has been fired, and we know we have one class that is interested in listening for it. That would be the ListViewModel class (which lives in the same directory as the TreeViewModel class). It’s constructor contains the following line, which says ‘hey, I want to know when an event passing an object of type EntityFilterArgs is fired’.

Messenger.Default.Register<EntityFilterArgs>(this, OnFilterByRelation);


So we can see that when it receives the event it is going to call a method called OnFilterByRelation. I won’t go into all the details of what this method does, but the main thing is that it stores the information that was passed in its SelectedEntityFilterArgs property, and calls LoadTasks. This will result in a call via WCF RIA Services to the database to get the appropriate data.

public void OnFilterByRelation(EntityFilterArgs args)
{
    SelectedEntityFilterArgs = args;
    IsInitialized = false;
    InitializeSelections();
    LoadTasks();
 
    ...
}


Wrap up

In my next post I will show how I use the same event system to show and hide the ActivityControl that displays whenever a time-consuming operation is performed, like loading data from the database.

Related Posts

Comments

Comments are disabled in preview mode.