Thought Cloud UI #1: Structure of the UI project

The TDD phase of Thought Cloud is done (for now). Now we begin building the user interface.

When I set up the project structure, I added the NuGet package “Correspondence.Silverlight.App” to a Silverlight application. In addition to adding a reference to the Correspondence and Update Controls assemblies, this added a few features to the app:

  • View Model Locator
  • Synchronization Service
  • Navigation Model

View Model Locator

I am not a big fan of the View Model Locator pattern. I believe that it is backwards. A view should not locate its view model: a view model should be injected into a view. Nevertheless, Silverlight’s architecture makes it easier to work with the VML than against it. So I’ll fight that battle another day.

The ViewModelLocator class created by the NuGet package exposes a MainViewModel. It wraps the view model for the view using Update Controls’ ForView.Wrap() method. This sets up all of the property changed notification through automatic dependency tracking.

public class ViewModelLocator
{
    private readonly SynchronizationService _synchronizationService;

    private readonly MainViewModel _main;

    public ViewModelLocator()
    {
        NavigationModel navigationModel = new NavigationModel();
        _synchronizationService = new SynchronizationService(navigationModel);
        if (!DesignerProperties.IsInDesignTool)
            _synchronizationService.Initialize();
        _main = new MainViewModel(_synchronizationService.Community, navigationModel, _synchronizationService);
    }

    public object Main
    {
        get { return ForView.Wrap(_main); }
    }
}

To use the view model locator, we follow the instructions in Readme.txt. We need to add it to application resources, and reference it from the main page.

In addition, the view model locator creates the synchronization service and navigation model, and passes those into the main view model.

Synchronization Service

The SynchronizationService class is responsible for synchronizing the local data storage with the remote service. As part of that responsibility, it sets up the Community. It uses the IsolatedStorageStorageStrategy to persist facts locally, and the POXAsynchronousCommunicationStrategy to exchange those facts with the server.

public class SynchronizationService
{
    ...

    public void Initialize()
    {
        POXConfigurationProvider configurationProvider = new POXConfigurationProvider();
        _community = new Community(IsolatedStorageStorageStrategy.Load())
            .AddAsynchronousCommunicationStrategy(new POXAsynchronousCommunicationStrategy(configurationProvider))
            .Register<CorrespondenceModel>()
            .Subscribe(() => _navigationModel.CurrentUser)
            .Subscribe(() => _navigationModel.CurrentUser.SharedClouds)
            .Subscribe(() => _navigationModel.CurrentUser.Clouds)
            ;

        // Synchronize whenever the user has something to send.
        _community.FactAdded += delegate
        {
            Synchronize();
        };

        // And synchronize on startup.
        Synchronize();
    }

    ...
}

During testing, we found which subscriptions need to be set up. We add those subscriptions here. The synchronization service subscribes to the current user as provided by the navigation model.

Navigation Model

The NavigationModel class is responsible for maintaining the user temporary state. This state represents the user’s point-of-view as they navigate through the system. Typical things that you will find in a navigation model are:

  • Selected items
  • Search terms
  • Open windows
  • User session

These things are all important to the user, but not actually part of the model. They are transient, and don’t get persisted to storage or shared with other users.

public class NavigationModel
{
    private Independent<Identity> _currentUser = new Independent<Identity>();

    public Identity CurrentUser
    {
        get { return _currentUser; }
        set { _currentUser.Value = value; }
    }
}

Fields of a navigation model are independent, meaning that the user can change them directly. We have to mark them using the Update Controls Independent modifier. Dependent behaviors, like view model properties and subscriptions, will respond to changes in independent fields. So when a user logs on and CurrentUser is set, not only will the view model update (and fire the appropriate PropertyChanged events), but the Community subscriptions will refresh as well.

With these three components generated for us, we can start assembling the user interface of the application.

Leave a Reply

You must be logged in to post a comment.