Click here to Skip to main content
15,892,674 members
Articles / Desktop Programming / WPF
Article

UIExtensionSites for PRISM

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
4 Aug 2010CPOL3 min read 29.3K   262   8   4
Classes to add a UIExtensionSites-like mechanism to PRISM.

Introduction

PRISM is a great library to develop WPF and Silverlight applications with. Yet, coming from the CAB (Microsoft Composite UI Application Blocks) library, I was missing the UIExtensionSites functionality. As it wasn't provided by PRISM and I needed it, I decided to write it myself (and subsequently share it).

The source code

The solution is a VS2010 solution. Note: to compile the code, you'll have to install the PRISM library from CodePlex first.

The code consists of four classes:

  • UIExtensionSiteManager: this is the main class, and contains the code to add the attached properties, and keeps track of the UIExtensionSites.
  • IUIExtensionSiteAdapter: classes that implement this interface 'adapt' a control to a UIExtensionSite.
  • UIExtensionSiteMappings: contains the mappings between the control types and the IUIExtensionSiteAdapter.
  • IUIExtensionSite: classes that implement this interface allow controls to be added to the UIExtensionSite.

UIExtensionSiteManager

The UIExtensionSiteManager defines the 'UIExtensionSite' attached property:

C#
public static readonly DependencyProperty UIExtensionSiteProperty = 
  DependencyProperty.RegisterAttached(
    "UIExtensionSite",
    typeof(string),
    typeof(UIExtensionSiteManager),
    new PropertyMetadata(OnSetUIExtensionSiteCallback));

public static void SetUIExtensionSite(DependencyObject siteTarget, string siteName)
{
    siteTarget.SetValue(UIExtensionSiteProperty, siteName);
}

public static string GetUIExtensionSite(DependencyObject siteTarget)
{
    return siteTarget.GetValue(UIExtensionSiteProperty) as string;
}

private static void OnSetUIExtensionSiteCallback(DependencyObject element, 
               DependencyPropertyChangedEventArgs args)
{
    if (!IsInDesignMode(element))
    {
        IServiceLocator locator = ServiceLocator.Current;
        UIExtensionSiteManager manager = 
          locator.GetInstance<UIExtensionSiteManager>();

        UIExtensionSiteMappings mappings = 
          locator.GetInstance<UIExtensionSiteMappings>();
        IUIExtensionSiteAdapter adapter = mappings.GetMapping(element.GetType());
        IUIExtensionSite site = adapter.Adapt(element);
        manager.AddUIExtensionSite(
          (string)element.GetValue(UIExtensionSiteProperty), site);
    }
}

All controls in WPF/Silverlight can use the UIExtensionSiteManager.UIExtensionSite property to define an extension site.

The second role of the manager is to keep track of the extension sites. The method 'GetUIExtensionSite' allows you to retrieve an extension site.

The UIExtensionSiteManager must be registered in the Unity container at the start of the application. A good place is the ConfigureContainer method in the Unity bootstrapper:

C#
protected override void ConfigureContainer()
{
    Container.RegisterType<UIExtensionSiteManager>(
        new Microsoft.Practices.Unity.ContainerControlledLifetimeManager(), 
        new Microsoft.Practices.Unity.InjectionMember[] {});            
    base.ConfigureContainer();
}

IUIExtensionSiteAdapter

The IUIExtensionSiteAdapter is an interface that defines what operations the adapter must implement. There's only one method: Adapt. This method accepts a DependencyObject and returns an object that implements IUIExtensionSite. The Adapt method must check if the type of the given DependencyObject matches the type that can be adapted.

A matching IUIExtensionSiteAdapter must be defined for each control that must be declared as a UIExtensionSite.

E.g., if you want to define a ComboBox as a UIExtensionSite, you'll have to provide a IUIExtensionSiteAdapter that 'adapts' a ComboBox to a fitting IUIExtensionSite object.

C#
public class ComboBoxUIExtensionSiteAdapter : IUIExtensionSiteAdapter
{
    public IUIExtensionSite Adapt(DependencyObject element)
    {
        if (element is ComboBox)
            return new ComboBoxUIExtensionSite(element as ComboBox);
        else
            throw new ArgumentException("ComboBoxUIExtensionSiteAdapter " + 
                                        "can only adapt ComboBox objects !");
    }
}

UIExtensionSiteMappings

This class is a collection of types and their matching IUIExtensionSiteAdapter. A mapping should be provided for each IUIExtensionSiteAdapter.

E.g., if you want to define a ComboBox as a UIExtensionSite, you'll have to add a mapping for the ComboBox and its matching IUIExtensionSiteAdapter. This is done in the UnityBootstrapper of your PRISM application.

C#
protected override void ConfigureContainer()
{
    Container.RegisterType<UIExtensionSiteManager>(
        new Microsoft.Practices.Unity.ContainerControlledLifetimeManager(), 
        new Microsoft.Practices.Unity.InjectionMember[] {});
    ConfigureUIExtensionSiteAdapterMappings();
            
    base.ConfigureContainer();
}

protected void ConfigureUIExtensionSiteAdapterMappings()
{
    UIExtensionSiteMappings mappings = new UIExtensionSiteMappings();
    Container.RegisterInstance(mappings, 
      new Microsoft.Practices.Unity.ContainerControlledLifetimeManager());
    mappings.RegisterMapping(typeof(System.Windows.Controls.ComboBox), 
      this.Container.Resolve<ComboBoxUIExtensionSiteAdapter>());
}

IUIExtensionSite

An extension site must implement this interface. It defines a 'wrapper' around the DependencyObject that is the extension site, and defines one method: Add. The code in this method should add the given FrameworkElement to the extension site.

E.g., if you want to define a ComboBox as a UIExtensionSite, you'll have to create a class that implements the IUIExtensionSite interface and provides a wrapper for the Items.Add() method of the ComboBox.

C#
public class ComboBoxUIExtensionSite : IUIExtensionSite
{
    private ComboBox _combo;

    public ComboBoxUIExtensionSite(ComboBox combo)
    {
        this._combo = combo;
    }

    public void Add(FrameworkElement element)
    {
        _combo.Items.Add(element);
    }

    public DependencyObject Target
    {
        get { return _combo; }
    }
}

Using the UIExtensionSites

You can use the UIExtensionSite by adding an attached property to you controls. E.g., if you want to define a ComboBox as a UIExtensionSite:

XML
<ComboBox uiext:UIExtensionSiteManager.UIExtensionSite="ProjectCombo"></ComboBox>

In your modules, all you have to do is to retrieve a reference to the UIExtensionSite and add your items to it. E.g., to add items to the previously defined ComboBox:

C#
UIExtensionSiteManager manager = container.Resolve<UIExtensionSiteManager>();
manager.GetUIExtensionSite("ProjectCombo").Add(new ComboBoxItem() { Content = "Project A" });
manager.GetUIExtensionSite("ProjectCombo").Add(new ComboBoxItem() { Content = "Project B" });
manager.GetUIExtensionSite("ProjectCombo").Add(new ComboBoxItem() { Content = "Project C" });

A sample using the DevExpress Silverlight toolbar

As I'm using the DevExpress components, I've created a UIExtensionSite for the DevExpress toolbar. But I think the following code might quite easily be adapted to fit other third-party components.

I started by creating a DXBarUIExtensionSite and a DXBarUIExtensionSiteAdapter:

C#
public class DXBarUIExtensionSite : IUIExtensionSite
{
    private DevExpress.Xpf.Bars.Bar _bar;

    public DXBarUIExtensionSite(DevExpress.Xpf.Bars.Bar bar)
    {
        if (bar == null)
            throw new ArgumentException("DXBarUIExtensionSite must " + 
                              "be initialized with a valid Bar object !");
        _bar = bar;
    }

    public void Add(FrameworkElement element)
    {
        if (!(element is DevExpress.Xpf.Bars.BarItem))
            throw new ArgumentException("element should be of type BarItem !");

        DevExpress.Xpf.Bars.BarItem item = element as DevExpress.Xpf.Bars.BarItem;

        DevExpress.Xpf.Bars.BarManager barmanager = _bar.Manager;
        if (!barmanager.Items.Contains(item))
            barmanager.Items.Add(item);
        _bar.ItemLinks.Add(item);
    }

    public DependencyObject Target
    {
        get { return _bar; }
    }
}

public class DXBarUIExtensionSiteAdapter : IUIExtensionSiteAdapter
{
    public IUIExtensionSite Adapt(DependencyObject element)
    {
        return new DXBarUIExtensionSite(element as DevExpress.Xpf.Bars.Bar);
    }
}

The next step was to define a toolbar in my Shell as a UIExtensionSite (in XAML):

XML
<dxb:BarManager Name="barManager" CreateStandardLayout="True">
    <dxb:BarManager.Items>
        </dxb:BarManager.Items>
        <dxb:BarManager.Bars>
                <dxb:Bar Caption="MainMenu" x:Name="MainMenu" 
                        IsMainMenu="True" UseWholeRow="True"
                        uiext:UIExtensionSiteManager.UIExtensionSite="MainMenu">
                    <dxb:Bar.DockInfo>
                        <dxb:BarDockInfo ContainerType="Top"/>
                    </dxb:Bar.DockInfo>
                </dxb:Bar>
        </dxb:BarManager.Bars>
</dxb:BarManager>

The final step was to add buttons to my UIExtensionSite. Note that this sample also includes a binding to a Command:

C#
DelegateCommand<string> openrepositorycmd = new DelegateCommand<string>(
    s => regionManager.RegisterViewWithRegion("MainTab", () => 
    container.Resolve<RepositoryListPresenter>().View));

Binding binding = new Binding();
binding.Source = openrepositorycmd;

System.Windows.Media.Imaging.BitmapImage image =
    new System.Windows.Media.Imaging.BitmapImage(
    new Uri("/RepositoryModule;component/Img/anchor16.png", UriKind.Relative));
DevExpress.Xpf.Bars.BarButtonItem button = new DevExpress.Xpf.Bars.BarButtonItem()
    {
        Content="Go !",
        Hint="This is a testbutton",
        Glyph=image   
    };
button.SetBinding(DevExpress.Xpf.Bars.BarButtonItem.CommandProperty, binding);

DevExpress.Xpf.Bars.BarSubItem repMenu = new DevExpress.Xpf.Bars.BarSubItem()
    {
        Content = "Repository"
    };
repMenu.ItemLinks.Add(button);

UIExtensionSiteManager manager = container.Resolve<UIExtensionSiteManager>();
manager.GetUIExtensionSite("MainMenu").Add(repMenu);

History

  • 2010-08-04: First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO TRI-S bvba, Cogenius bvba
Belgium Belgium
I'm working since 1999 in an IT environment: started developing in PROGRESS 4GL, then VB6 and am working since 2003 with C#. I'm currently transitioning to HTML5, CSS3 and JavaScript for the front-end development.
I started my own company (TRI-S) in 2007 and co-founded another one (Cogenius) in 2012.
Besides being a Microsoft Certified Professional Developer (MCPD) I'm also a Microsoft Certified Trainer (MCT) and am teaching .NET and JavaScript courses.

Comments and Discussions

 
GeneralIt's working! Pin
Chop_CZ4-Sep-10 5:48
Chop_CZ4-Sep-10 5:48 
QuestionNeeded? Pin
Mike Kenyon9-Aug-10 11:33
Mike Kenyon9-Aug-10 11:33 
AnswerRe: Needed? Pin
Xavier Spileers9-Aug-10 20:57
Xavier Spileers9-Aug-10 20:57 
GeneralRe: Needed? Pin
Mike Kenyon10-Aug-10 5:18
Mike Kenyon10-Aug-10 5:18 
Xavier,

I can understand that situation, I encountered it recently using Prism against some Infragistics Ribbon controls. Our solution was slightly different. There's an in-the-box mechanism for doing this. There's a defined stage at startup for updating the table of RegionAdapterMappings. This maps control types to instances of objects that know how to translate from a region to them.

The advantage of using this mechanism is that you get full support for the region functionality out of the box. This includes region behaviors and several other options. Readding UIExtensionSites seems to be generating a parallel structure to regions which would then need parallels of the behaviors in order to be "fully functional".

Still I can see the benefit of having the capability of being able to add FEs to an arbitrary FE. Maybe this would be better done as a highly flexible region adapter? They aren't very much fun to write, so this I think would be a large win for the community.

Mike

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.