在构建复杂的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);