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.

The page state issue

One of the great things about MEF is that it allows you to maintain state across pages. So basically, when a user spends some time on a page and makes some changes, then leaves and returns, it certainly helps from a usability perspective if the page is in the same state on re-entry as when they left. However, if your view and view model are recreated each time this happens you will have to write some code to store state somehow, perhaps in isolated storage.

The MEF solution

One of the nice things about MEF (the Managed Extensibility Framework) is that when it creates view model instances (via Import / Export), it caches them, so although your view may go away when you leave the page, your view model instance is still hanging around. This allows you to 'store' your state in the view model.

Instantiating the view model with MEF

So the first thing we need to do is have MEF create our view model instances for us. To do this, we simply need to do three things:

1) Add an Export attribute to the view model class:

[Export]
public class ListViewModel : TasksViewModelBase
{
    ...

2) Add an Import attribute to the ViewModel property in the view:

[Import]
public ListViewModel ViewModel { get; set; }

3) Call SatisfyImports in the view's constructor (and set the DataContext to the view model)

CompositionInitializer.SatisfyImports(this);
DataContext = ViewModel;

When SatisfyImports is called, it will look for properties that are tagged with the Import attribute (which our ViewModel is) and then will hunt for the view class associated with the property and if it is marked with Export attribute, it will create an instance. Please note that if you forget the Export attribute on the view model class it will throw an error.

If you set a breakpoint at the first line above (the SatisfyImports line), you'll see that the ViewModel property is null. If you go to the next line you will see that it is no longer null, because MEF has created the ListViewModel for us.

Maintaining page state

OK, so now that MEF has created our view model instance it will hang around in memory. So in this example we are dealing with ListViewModel, the view model that drives the right side of the Tasks page in Task-It:

Tasks Page

So now let's say that we change the status dropdown from Incomplete to Complete:

Status Dropdown

The status dropdown (RadComboBox) is bound to our view model, so when its SelectedItem changes, our view model now knows what the currently selected status is.

<telerikInput:RadComboBox ItemsSource="{Binding TaskStatuses}" SelectedItem="{Binding SelectedTaskStatus, Mode=TwoWay}" Visibility="{Binding IsStatusVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Width="95"/>

It is stored in the view model's SelectedTaskStatus property.

public TaskStatus SelectedTaskStatus
{
    get { return _selectedTaskStatus; }
    set
    {
        _selectedTaskStatus = value;
        if (IsInitialized)
        {
            // If the user selects a different status, reload the tasks
            LoadTasks();
        }
        this.OnPropertyChanged(m => m.SelectedTaskStatus);
    }
}

So now if we leave the Tasks page and return the view model "remembers" what the selected status is, so the Completed tasks will be loaded.

Reloading the data

The only minor issue with what we've talked about so far is that when we return to the Tasks page, the view model does not yet know that we have returned, and therefore does not know to load fresh data. However, we can remedy this situation by notifying it that the page has changed. For this we will fire an event every time the page changes.

The event handler that is called each time we go back to the Home page and move to a new page lives in HomeViewModel. In this method (OnNavigate), we use the MVVM Light Toolkit's Messenger class to send an event specifiying which 'module' (a.k.a. 'page) we are navigating to.

public virtual void OnNavigate(ModuleEnum module)
{           
    Messenger.Default.Send(module);
}

Now in our ListViewModel we can listen for this event.

Messenger.Default.Register<ModuleEnum>(this, OnPageEnter);

And if the page we are entering is the Tasks page, we can reload the Tasks.

private void OnPageEnter(ModuleEnum args)
{
    if (args == ModuleEnum.Tasks)
    {
        LoadTasks();
    }
}

Note that this method (OnPageEnter) will be called each time we navigate to a new page, so it is important that we check to see if the page we are entering is the Tasks page, so that we do not reload the tasks collection when we enter other pages.

The pros

So we've seen the benefit of having MEF instantiate our view models and keep them around in memory. However we do need to be aware of some downsides to this.

The cons

The thing we need to be careful of is that because the view model hangs around in memory, so does any memory consuming data. So, we can refactor the method above to clear the collection of Tasks if the page we are entering is no the Tasks page. We really don't need to keep that data around, since we know we will be reloading it when we re-enter the page anyway.

private void OnPageEnter(ModuleEnum args)
{
    if (args == ModuleEnum.Tasks)
    {
        LoadTasks();
    }
    else
    {
        Utils.DataContext.Tasks.Clear();
    }
}


About the Author

Stefan Dobrev

 is Team Lead in Black Dragon Team

Related Posts

Comments

Comments are disabled in preview mode.