设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Prism动态模块和WPF工具包的流行UI

2016-4-5 22:30| 发布者: joejoe0332| 查看: 2340| 评论: 0|原作者: 雪落无痕 xdj|来自: oschina

摘要: 前段时间我的一个同事和我讨论了一个他要解决的问题。一个客户要求他开发一套桌面软件,这个软件在企业的不同部门使用它时展现出不同的功能模块。一方面,我想起了我曾经做过的解决方案中的实现。另一方面有一个能构 ...

介绍

前段时间我的一个同事和我讨论了一个他要解决的问题。一个客户要求他开发一套桌面软件,这个软件在企业的不同部门使用它时展现出不同的功能模块。一方面,我想起了我曾经做过的解决方案中的实现。另一方面有一个能构建出有现代展现风格的WPF应用程序的开源程序,这个开源程序我跟了好几年了,因为我觉得它确实很棒。

我在想是否能把Prism库和那个开源的MUI界面库结合起来做一个插件式架构,于是我就做出了接下来要展现的原型方案。

动态模块(Dynamic Modules)是一个WPF模块化应用的原型样例,基于Prism库和WPF控件库Modern UI(MUI)。这是一个以插件式架构创建metro风格的、WPF界面应用程序的概念的实例。

背景和需求

这篇文章要求读者至少要有基本的WPF、Prism库和Unity依赖注入容器的背景知识。工程需要使用Visual Studio 2015进行编译。


架构

本文提出的插件架构的核心思想是:

  • 把所需的工程模块都放到一个目录中(或者把所有模块都放到一个目录中,在加载时进行过滤)。

  • 动态的从模块文件夹中加载工程模块。

  • 每个模块暴露出一个入口点作为主菜单的选项。

  • 根据加载的模块动态构建主菜单。

  • 主菜单中的第一个选项是固定的,且对于每个用户都是一样的。

  • 一个包含共享服务、存储库、DTO和数据模型定义等信息的核心模块是静态加载的,从解决方案的任何工程都能引用到。

把动态模块拷贝到一个目录中是生成后事件中的一步。这个模块都没有被启动工程引用,并且是通过检查目录中的程序集来动态发现的。这些模块工程的生成后事件如下所示,以保证把生成的程序集拷贝到那个目录中。

xcopy "$(TargetDir)$(TargetFileName)" "$(TargetDir)modules\" /y

解决方案生成到 "..\bin\"目录。

理解代码

如果你检出了 MUI 示例工程中的 MainWindow.xaml 文件,你可以看到主菜单是如何静态的生成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<mui:ModernWindow.MenuLinkGroups>
    <mui:LinkGroup DisplayName="Welcome">
        <mui:LinkGroup.Links>
            <mui:Link DisplayName="Introduction" Source="/Pages/Introduction.xaml" />
        </mui:LinkGroup.Links>
    </mui:LinkGroup>
    <mui:LinkGroup DisplayName="Layout">
        <mui:LinkGroup.Links>
            <mui:Link DisplayName="Wireframe" Source="/Pages/LayoutWireframe.xaml" />
            <mui:Link DisplayName="Basic" Source="/Pages/LayoutBasic.xaml" />
            <mui:Link DisplayName="Split" Source="/Pages/LayoutSplit.xaml" />
            <mui:Link DisplayName="List" Source="/Pages/LayoutList.xaml"  />
            <mui:Link DisplayName="Tab" Source="/Pages/LayoutTab.xaml" />
        </mui:LinkGroup.Links>
    </mui:LinkGroup>
    <mui:LinkGroup DisplayName="Controls">
        <mui:LinkGroup.Links>
            <mui:Link DisplayName="Styles" Source="/Pages/ControlsStyles.xaml" />
            <mui:Link DisplayName="Modern controls" Source="/Pages/ControlsModern.xaml" />
        </mui:LinkGroup.Links>
    </mui:LinkGroup>
    ...
    ...
    ...
</mui:ModernWindow.MenuLinkGroups>
</mui:ModernWindow>

主窗口的主菜单是 ModernWindow 类的一个依赖属性,属性名是MenuLinkGroups。它返回一个LinkGroupCollection 类的实例,这个类继承自ObservableCollection<LinkGroup>。也就是说,主菜单是若干link group的集合。每个 LinkGroup 代表着菜单中的一个入口。所以,如果每个动态模块都可以导出一个LinkGroup的实例,我们所需要做的就是将其加入到Link group的集合中。 导出方式以接口契约的形式公布出来。

1
2
3
4
public interface ILinkGroupService
{
    LinkGroup GetLinkGroup();
}

核心模块中定义ILinkGroupService的接口。这表明如果一个模块要在主菜单中加入一个选项,该模块只需实现返回LinkGroup实例的 GetLinkGroup()方法即可。ILinkGroupService接口的实现以及theGetLinkGroup()方法示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LinkGroupService : ILinkGroupService
{
    public LinkGroup GetLinkGroup()
    {
        LinkGroup linkGroup = new LinkGroup
        {
            DisplayName = "Module One"
        };
 
        linkGroup.Links.Add(new Link
        {
            DisplayName = "Module One",
            Source = new Uri($"/DM.ModuleOne;component/Views/{nameof(MainView)}.xaml", UriKind.Relative)
        });
 
        return linkGroup;
    }
}

现在我们就可以动态加载这些模块了,为每个模块生成ILinkGroupService 接口的实例,并且在主菜单中插入导出的选项。 下面是Bootstrapper类中ConfigureModuleCatalog()方法的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protected override IModuleCatalog CreateModuleCatalog()
{
    return new DirectoryModuleCatalog() { ModulePath = MODULES_PATH };
}
 
protected override void ConfigureModuleCatalog()
{
    var directoryCatalog = (DirectoryModuleCatalog)ModuleCatalog;
    directoryCatalog.Initialize();
 
    linkGroupCollection = new LinkGroupCollection();
    var typeFilter = new TypeFilter(InterfaceFilter);
 
    foreach (var module in directoryCatalog.Items)
    {
        var mi = (ModuleInfo)module;
        var asm = Assembly.LoadFrom(mi.Ref);
 
        foreach (Type t in asm.GetTypes())
        {
            var myInterfaces = t.FindInterfaces(typeFilter, typeof(ILinkGroupService).ToString());
 
            if (myInterfaces.Length > 0)
            {
                // We found the type that implements the ILinkGroupService interface
                var linkGroupService = (ILinkGroupService)asm.CreateInstance(t.FullName);
                var linkGroup = linkGroupService.GetLinkGroup();
                linkGroupCollection.Add(linkGroup);
            }
        }
    }
 
    var moduleCatalog = (ModuleCatalog)ModuleCatalog;
    moduleCatalog.AddModule(typeof(Core.CoreModule));
}

我们首先创建 Bootstrapper.ModuleCatalog作为一个 DirectoryModuleCatalog ,并且初始化模块目录。 然后迭代各动态发现的模块,对于每个模块,查找实现了ILinkGroupService接口的类型。如果这样一个类型找到了,则创建该类的实例并调用它的GetLinkGroup()方法。返回的LinkGroup实例被插入到框架程序(Shell)中的菜单项集合里:

1
2
3
4
5
6
7
8
9
10
11
protected override DependencyObject CreateShell()
{
    Shell shell = Container.Resolve<Shell>();
 
    if (linkGroupCollection != null)
    {
        shell.AddLinkGroups(linkGroupCollection);
    }
 
    return shell;
}

框架的AddLinkGroups()方法定义如下:

1
2
3
4
5
6
7
8
9
public void AddLinkGroups(LinkGroupCollection linkGroupCollection)
{
    CreateMenuLinkGroup();
 
    foreach (LinkGroup linkGroup in linkGroupCollection)
    {
        this.MenuLinkGroups.Add(linkGroup);
    }
}

CreateMenuLinkGroup()方法创建了主菜单中静态公共的菜单组,每个循环中又动态创建了一个菜单组。这样就大功告成了,伙计。如果某个模块,比如模块1ModuleOne从模块目录中被移除了,主菜单将变成这样:

Module one is removed

相应的,如果模块2ModuleTwo被移除了,主菜单将变成这样:

Module two is removed

显而易见的如果在模块目录中没有模块,则只会显示出静态公共菜单项。

Both modules are removed

结论

Prism库提供一组丰富的资源来创建模块化的 WPF应用程序。  WPF 库Modern UI(MUI)提供了丰富资源来创建界面美观的WPF应用程序。这篇文章展示了使用一个两全其美的办法来创建一个插件式架构。另一个相关主题是认证授权,不在此原型系统的讨论范围之内,即根据用户名或其角色来动态加载模块,以限制用户只能访问应用程序中已授权的功能模块。

我相信肯定有其他的甚至更好的方式来做这样的融合,请随时评论并留下的你想法、建议和意见。我们欢迎你评论。

感兴趣的链接

你会在下列链接中找到补充信息:

目前已不需要实现在视图的后端代码中实现IView 接口。






酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部