在构建复杂的WPF/Silverlight应用程序时,经常需要一种机制来动态地添加或修改UI组件。这种机制通常被称为UI扩展点。本文将介绍如何在PRISM框架中实现UI扩展点管理,以及如何将其应用于DevExpress组件。
UI扩展点允许开发者在运行时动态地向UI中添加或替换组件。这在开发模块化应用程序时非常有用,因为不同的模块可能需要向UI中添加自己的组件。
UIExtensionSiteManager是管理UI扩展点的主要类。它定义了一个附加属性UIExtensionSite,用于标识UI扩展点。此外,它还负责跟踪所有已注册的UI扩展点。
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();
        UIExtensionSiteMappings mappings = locator.GetInstance();
        IUIExtensionSiteAdapter adapter = mappings.GetMapping(element.GetType());
        IUIExtensionSite site = adapter.Adapt(element);
        manager.AddUIExtensionSite((string)element.GetValue(UIExtensionSiteProperty), site);
    }
}
      
IUIExtensionSiteAdapter定义了适配器必须实现的操作。适配器的作用是将UI组件适配为UI扩展点。
public interface IUIExtensionSiteAdapter
{
    IUIExtensionSite Adapt(DependencyObject element);
}
    
UIExtensionSiteMappings是一个包含类型及其匹配的IUIExtensionSiteAdapter的集合。每个IUIExtensionSiteAdapter都需要一个映射。
public class UIExtensionSiteMappings
{
    private Dictionary _mappings = new Dictionary();
    public void RegisterMapping(Type type, IUIExtensionSiteAdapter adapter)
    {
        _mappings[type] = adapter;
    }
    public IUIExtensionSiteAdapter GetMapping(Type type)
    {
        return _mappings[type];
    }
}
      
IUIExtensionSite定义了扩展点必须实现的操作。它定义了一个包装DependencyObject的“包装器”,并定义了一个Add方法,用于将给定的FrameworkElement添加到扩展点。
public interface IUIExtensionSite
{
    void Add(FrameworkElement element);
    DependencyObject Target { get; }
}
    
可以通过向控件添加附加属性来使用UI扩展点。例如,如果想将ComboBox定义为UI扩展点:
<ComboBox uiext:UIExtensionSiteManager.UIExtensionSite="ProjectCombo">
</ComboBox>
    
在模块中,只需要检索UI扩展点的引用,并向其添加项目。例如,向之前定义的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" });
    
由于使用了DevExpress组件,为DevExpress工具栏创建了一个UI扩展点。但认为以下代码可以很容易地适应其他第三方组件。
首先,创建了一个DXBarUIExtensionSite和一个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);
    }
}
    
接下来,在Shell中定义了一个工具栏作为UI扩展点(在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>
    
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);