Tuesday, February 24, 2009

Lack of AttachNewRegion method in Prism 2.0

Today I have switched to Composite WPF and Silverlight 2.0 (known as Prism 2.0) in one of my current projects. The transition was quite smooth (I used a guide which I found here), although I found one issue - the IRegionManager interface does not contain the AttachNewRegion method :( This made me very unhappy because I used this method in the implementation of a IMenuService which is responsible for managing the main menu control within my application by exposing a friendly interface to all modules. After small source code investigation I realized that indeed there's no sign of the mentioned method. After reading interesting post here, I decided to write one myself, which was a trivial task ;) Here's the code:



using System;
using System.Windows;
using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.Composite.Regions;

namespace Mammoth.Presentation.Infrastructure.Common
{
public static class RegionManagerExtensions
{
public static void AttachNewRegion(this IRegionManager regionManager, object regionTarget, string regionName)
{
if (regionManager.Regions.ContainsRegionWithName(regionName))
throw new ArgumentException("Region already exists.", regionName);

RegionManager.SetRegionManager((DependencyObject) regionTarget, regionManager);
RegionManager.SetRegionName((DependencyObject) regionTarget, regionName);
}
}
}


As you can see, this is a very simple extension method that extends the IRegionManger interface. Inside, I simply set the region manager and the region name on the desired UI element.

This solution is very simple, still it works fine (at least for me). If you have any troubles with it, let me know.

Wednesday, February 18, 2009

WPF Wizard Control - Part II

In my previous post about rolling your own WPF wizard control, I've described how one can easily create simple, styleable wizard in WPF. Generally, I blogged that the wizard consists of two things, namely
  • Wizard class - representing the wizard with its buttons (Next, Previous, Finish, etc.) and simple behavior concerned around managing which wizard page should be displayed,
  • WizardPage class - representing a container for a single wizard page.
The first class inherited from the Control class whereas the second inherited from the ContentControl class. Because the Wizard class wasn't derived from any control that can have content, it had to have a bindable collection of wizard's pages. Thus, I brought WizardPagesCollection class into play, which was defined as follows:

    1 
    2     /// 
    3     /// Wizard pages collection.
    4     /// 
    5     public class WizardPagesCollection : ObservableCollection<WizardPage>
    6     {
    7 
    8     }

Besides the collection of pages, the Wizard had two properties, namely
  • ActivePageIndex - the index of the current page in the collection being displayed,
  • ActivePage - the active page itself.
Of course, both properties depends on each other, i.e. if ActivePageIndex changes, ActivePage has to be updated accordingly. This was done using dependency property callbacks. Moreover, I used coerce callbacks to validate the values.
After coding this solution I realized there's a problem with it - the only wizard page that is the part of the wizard's logical tree is the page being displayed! This meant that DataContext property wasn't set correctly, and binding to controls across wizard pages was not possible.

A better Solution

Fortunately, it is very easy to overcome these problems, even without modifying wizard's template! My first assumption about wizard's base class was mistaken, because wizard is indeed a control that should have a content - its own pages :) And because there can be more than one page, the choice is obvious - ItemsControl.

Inheriting from ItemsControl reduced the following code from the Wizard class:

    1 
    2 private WizardPagesCollection m_WizardPages;
    3 
    4 /// 
    5 /// Returns a collection of wizard's pages.
    6 /// 
    7 public WizardPagesCollection WizardPages
    8 {
    9     get { return m_WizardPages; }
   10     set
   11     {
   12         m_WizardPages = value;
   13         m_WizardPages.CollectionChanged += OnWizardPagesChanged;
   14     }
   15 }
   16 
   17 private void OnWizardPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
   18 {
   19     // This code glues all wizard's pages to wizard's DataContext.
   20     // This is done due to the fact that when pages are switched, the
   21     // page that is hidden looses its data context.
   22     foreach (var page in WizardPages)
   23     {
   24         var binding = new Binding("DataContext") { Source = this };
   25         BindingOperations.SetBinding(page, DataContextProperty, binding);
   26     }
   27 }

This was actually the ugly code that attached the value of Wizard's DataContext property to each wizard's page DataContext which eliminated the problem no. 1. And because now wizard contains all the pages within its Items collection (which can be databound via ItemsSource property), all pages appear in the wizard's logical tree. Thus, problems described earlier in this post no more exist :)

Because now I used ItemsControl, I could virtually put anything in the wizard and it will compile. But the wizard expects to contain instances of WizardPage class, so I needed to tell the wizard that it need to wrap any content that is not a WizardPage into an instance of WizardPage. This is done using the following code:

    1 protected override DependencyObject GetContainerForItemOverride()
    2 {
    3     return new WizardPage();
    4 }
    5 
    6 protected override bool IsItemItsOwnContainerOverride(object item)
    7 {
    8     return item is WizardPage;
    9 }

Note, however, that in this solution, both ActivePageIndex and ActivePage properties still play vital role. Also note that there is not a single change in the Wizard's template, which is very simple. The one problem with it is that it defines the "view" for the wizard and for the wizard's page. This will be fixed in the third release :)

I will blog about better approach to implementing custom WPF wizard which fixes the complexity of using the two mentioned properties and uses separate templates for the wizard and its pages in my next post, so stay tuned ;)

The code for this post can be downloaded from here. Note that it actually contains three Wizard's implementations. The one described in this post is contained within WpfWizard2 project. WpfWizard3 will be subject of my next post.