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 UIExtensionSite
s.
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:
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:
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.
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.
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
.
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
:
<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
:
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
:
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):
<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:
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.