Tuesday, June 1, 2010

how to build an outlook style application – part 1

how to build an outlook style application – part 1: "

[Update] This is part 1 of this post. Read the second post here.

At the end of building prism V2, we have played around with different application styles to see how easy it is to consume our own libraries. In this blog post, I’m going to describe my attempt at creating an outlook style application. My implementation shows the following aspects:

  • How to create application parts that can be activated and deactivated, can be put in a list, and are very lightweight.
  • How to use an Application Model?
  • How to do pure ViewModel first development. In WPF, you can apply implicit Data Templates to visualize objects, but in silverlight that doesn’t work. So I’ll demonstrate two ways to do this.
  • How to handle RegionContext in a more direct way.

You can download the Source Code here:

<Outlook style app>

You will need the following references to make the solution work:

Download prism, compile it, and place the binaries in the lib folder. Also, place the Unity binaries in the lib folder.

Requirements

So what are my requirements for the outlook style app.

  • Be able to show a list of buttons that can activate parts of an application (like the outlook buttons for Mail, calendar, contacts, etc.)
  • It should be possible to activate and deactivate ‘Application parts’. If an application part becomes active, It’s initial view should show up in the main area.
  • Only one ‘application part’ can be active at any point in time.
  • When a view or an application part becomes active or inactive, other views might also have to be added or removed, such as toolbars.
  • Show a list of available ‘application parts’, without really instantiating all their views immediately. Only instantiate the views the first time you show them.

Solution

So this is my Outlook style app:

image

With a little bit of imagination, I hope you can see how this app could resemble outlook ;)

The following diagram shows the overall structure of the application and the most important pieces.

image

My shell has several regions, each brightly colored to make it clear where they are.

The ApplicationModel defines that the application has a list of UseCases. The ButtonBar (in red) is bound to the list of use cases. Each use case is represented as a button. When you click that button, the ActivateUseCase command is fired which will activate the selected UseCase.

An UseCase (like the MainEmailUseCase) implements the IActiveAwareUseCaseController and likely inherits from ActiveAwareUseCaseController. This makes it activatable (I know, it’s not a word). Lastly, the EmailUseCase itself defines which views it needs and where those views should go.

In the following paragraphs, i’m going to describe some of the moving pieces of this design.

Application model

The approach I took when creating the OutlookStyle app is to have a central ‘application model’ that knows how the application is structured. The modules also know a bit about this application model, through the IApplicationModel interface.

The ApplicationModel has a list of Main Usecases (more on usecases later), that I could bind my list of buttons to. It also has a command that can activate a UseCase when one of the buttons is clicked.

Using an ApplicationModel is kind of a double edged sword. It ties you somewhat down to a specific style of application. This makes your modules less portable, but it also makes the interaction between your modules and your shell more explicit and thus easier to understand. Using an application model is only one approach to creating an outlook style application and there are many others. But I thought it was an elegant solution and since we weren’t showing how to create an ApplicationModel in the Prism RI or the Quickstarts I thought it would be nice to show it here.

Internally, the application model uses a SingleActiveRegion to store the UseCases. This might seem strange, but a Region is nothing more than a model that can store a collection of objects that can be added, removed and activated. The SingleActiveRegion is ideal for this purpose, because it will make sure only one usecase is active at a given time. I choose not to add the region to a regionmanager, because I want the ApplicationModel to control access to this region.

Showing application parts: UseCaseControllers.

My implementation for the ‘application parts’ that can be activated and deactivated are called ‘UseCaseControllers’. This were my requirements for them:

  • I want to be able to put it in a list bind a collection of buttons to
  • I want to be able to activate and deactivate it.
  • It should be very lightweight, because all available controllers will be activated at once.

Motivations for naming the ‘application parts’

It was quite hard to find a right name for the ‘application parts’:

  • It’s not a module, because a module will likely contain many application parts. Also, a module should be viewed as a unit of deployment.
  • It’s not a view, because it’s more likely a set of views, working together to fulfill a single use case. These views, can be main views, tool bars, etc..
  • I considered the term ‘feature’ for a while. However, if you have ever built installers, you’ll know that a feature is also an overloaded term. It also refers more to a unit of deployment than what we’re looking for here.

So called my ‘Application parts’ UseCaseControllers. In my example, the main buttons on the outlook app kind of map to the ‘main’ or initial UseCases in my application.

Why call it controller? Controller is quite an overloaded term, because of the Model View Controller pattern. However, controllers are also commonly used to coordinate some kind of process and couple several pieces together to form a single unit. So because the controller hooks several controls together to perform a single use case, I called it a UseCaseController.

ActiveAwareUseCaseController

The ActiveAwareUseCaseController is a handy base class that you can use to fulfill the IActiveAwareUseCaseController interface. It does the following things for you:

  • BeforeFirstActivation Method. Have a handy place for the first time you activate this controller. In this method (BeforeFirstActivation), you can create all your Views or ViewModels and perform any eventwiring. For example, tie any toolbar commands to your ViewModels. The ObjectFactory also helps with this.
  • ViewToRegionBinder. One of the things you are very likely to do is to add views to some regions when the UseCase becomes active, and remove them again when it becomes inactive. Instead of having to put region.Add(view) and matching region.Remove(view) calls in the activate and deactivate methods, I figured it would be handy to create one registration method that takes care of this.

Using the ViewToRegionBinder to add and remove views to and from regions

Adding and removing views from a region whenever something becomes active or inactive can be quite tedious. Whenever a view or usecase (or anything else that implements IActiveAware) becomes active, you probably have to find the right regions, add the views to the regions. And the same if the usecase is deactivated (but then remove the views)

To make this a bit easier, I have created the ViewToRegionBinder. This object can monitor an IActiveAware object (such as a IActiveAwareUseCaseController or a view or a ViewModel). You can register a list of objects (views or viewmodels) against names of regions so that, when the object you are monitoring becomes active, the views will be added to the right regions and removed when the object becomes inactive.

   1: // Make sure the Toolbar and the mainregion get displayed when this use case is activated


   2: // and removed again when it becomes inactive. 


   3: this.ViewToRegionBinder.Add("ToolbarRegion", this.emailToolBarViewModel);


   4: this.ViewToRegionBinder.Add("MainRegion", this.emailViewModel);




Using the ObjectFactory to delay creation of your views until you actually need them



Another interesting challenge I ran into was:I wanted to shows a list of buttons for each usecase in my system. But only when I click one of these buttons do I want my views to be created and initialized. You can imagine, if you have 20 of these ‘main’ usecases and each creates a bunch of views when the application starts, application load time would be dramatic.



This was one of the motivations for creating the UseCaseControllers in the first place. They are very lightweight and I could create the views I needed in the Activate() method. But then I faced an other issue. I really like to create my views and my viewmodels using Constructor Injection. However, since the objects injected the constructor would be created before the usecase would be created, not after, i had to put in some level of indirection.



This is what the ObjectFactory<Type> solves. You can express the dependency on an object in the constructor, and create the object when you need it, by calling the ObjectFactory.CreateInstance() method. You can access the instance created by the objectfactory using the ObjectFactory.Value property.



Now I didn’t want to create member variables for the ObjectFactories, because that would mean that every time I needed to access the view, i would have to go through the ObjectFactory.Value property. I really wanted to have variables of the type of the views. So I created the AddInitializationMethod, where you can add a lambda expression that will set the member variable for the views. The ActiveAwareUseCaseController will call these lambda’s just before the UseCase becomes active for the first time.



The following code snippet shows how this works in the MainEmailUseCase





   1: // The references to these viewmodels will be filled when the app is initialized for the first time. 


   2: private EmailMainViewModel emailViewModel;


   3: private EmailToolBarViewModel emailToolBarViewModel;


   4:  


   5: public EmailMainUseCase(


   6:     // Get the ViewToRegionBinder that the baseclass needs


   7:     IViewToRegionBinder viewtoToRegionBinder


   8:     // Get the factories that can create the viewmodels


   9:     , ObjectFactory<EmailMainViewModel> emailViewFactory


  10:     , ObjectFactory<EmailToolBarViewModel> emailToolBarFactory) : base(viewtoToRegionBinder)


  11: {


  12:     // Just before the view is initialized for the first time


  13:     this.AddInitializationMethods(


  14:             // Create the emailViewModel and assign it to this variable


  15:         () => this.emailViewModel = emailViewFactory.CreateInstance()


  16:             // Create the toolbarViewModel and assign it to this variable


  17:         , () => this.emailToolBarViewModel = emailToolBarFactory.CreateInstance());


  18: }




Why do I like Constructor Injection? It expresses very clearly what the dependencies of my object are, in a single place. Take the previous code snippet. You can clearly see that the EmailUseCase has a dependency on the IViewToRegionBinder, the EmailMainViewModel and the EmailToolBarViewModel. Those are the only objects this class interacts with. Sure, in some cases it might seem easier to get a reference to the service locator and resolve types when you need them, but that makes it very unclear what other types your class depends on and also makes unit testing a lot harder.





Bootstrapper



As usual, the bootstrapper is the glue for all the individual pieces. In the bootstrapper, all the infrastructure pieces are registered in the right way, so the application can use them. You can see that I’m registering the types i need to the container and some of the default regionbehaviors to the RegionBehaviorFactory





What’s next?



In the Outlook style application, i’m also showing a bunch of other things:




  • How to do ViewModel First development in an elegant way.


  • How to use RegionContext in a bit easier way


  • How to show views in a popup in a robust way. Note, in the current version, this is still work in progress.



Since I’ve already spent way to much time on this blogpost, i’m going to address these things in future posts.



As always, I really appreciate your feedback. Any comments or questions are more than welcome.



Happy coding!



_Erwin



[Update: Also read part 2 of this post. ]

"

No comments:

Post a Comment