SailingEase WinForm Framework WinForm开发框架开发手册:http://docs.shengxunwei.com/Home/Browser/sewinformfw/html
以前承诺会对 Winform IDE,WPF 客服程序的开发进行进一步的分解记录,很抱歉一直没有太多时间认真梳理。框架
本篇博客抽取了这两个应用程序的一个共通功能的实现方法进行说明,即在插件式应用程序中,对菜单及工具栏的控制。模块化
对于复杂的应用程序开发,咱们可能会将程序的功能进行分解,模块化,插件化;那么如何在应用程序的宿主中,向插件提供统一的菜单,工具栏注册,更新,销毁机制呢?以及如何作到UI无关的完全解耦合?工具
看两个例子:ui
基于 Winform 的插件式应用程序: http://www.cnblogs.com/sheng_chao/p/4387249.htmlthis
这是一个基于 Winform 的 IDE 程序,主菜单及工具栏根据加载的模块,以及当前激活的窗体有所不一样,菜单及工具栏按钮的状态则根据当前激活窗体内的数据或行为的不一样而有所不一样。spa
图中黄色背景的工具栏部分为窗体设计器所特有,相似于在新版的 Word 中选中图形或表格时出现的特定菜单项目。每当在窗体设计器中进行不一样的操做时,工具栏中的项目将呈现不一样的状态。插件
基于 WPF 的插件式应用程序: http://www.cnblogs.com/sheng_chao/p/4548146.html设计
这个是一个基于 WPF 开发的普通桌面应用程序,根据当前加载的模块不一样,上方主菜单显示的项目有所不一样。这个例子比较简单,虽然主菜单是根据插件而加载的,可是加载以后不会有状态变化。日志
通常来讲,宿主程序在加载插件时,会根据某种预先配置的插件信息(如配置文件),读取与插件相关的信息进行加载。
过去的许多应用程序,经过将菜单及工具栏的配置经过配置文件来向宿主进行声明,这种方式的优势是实现简单,开发容易,几乎没有难度,缺点是几乎只能以静态方式对菜单及工具栏进行配置,若是须要在程序运行时动态更新、吊销菜单或工具栏,按此思路实现起来已不是最优选择。
第二种方式也是我常常看到的,就是开发人员直接把菜单或工具栏从UI层抛给插件去实现,宿主只提供一个基本UI容器去承载插件所提供的UI对象,好比整个 UserControl。这种方式若是必定要说有什么优势,那就是开发实现比较简单,缺点则比第一种方式更多,首先宿主程序失去了对插件的绝对控制,插件程序能够经过提供本身形态万千的UI,使主程序的相关功能呈现,控制,再也不统一,其次使主程序变得很是脆弱,宿主程序没法有效的,彻底的 Handle 来自这些UI的异常,也没法监控,控制这些UI中的方法调用,例如对超时的方法调用显示等待UI,或强行停止,没法调度这些方法调用。当宿主程序因升级而修改了菜单和工具栏的呈现形态时,或须要支持换肤功能时,插件提供的UI彻底不受控。此外这种方式可能带来大量的重复劳动,浪费开发人员生产性,由于大多数的菜单,工具栏项目的呈现,都是类似的,有必定规律的,能够经过自动化的方式来处理。
第三种方式的思路是由宿主程序提供接口,供插件进行调用,从而使插件可以对菜单及工具栏进行动态控制,这样作的好处一是不存在上述方法二中的问题,二是解决了方法一中,静态加载所不能实现的动态控制。
实现的方式有许多,过去咱们见到过提供一系列方法来供插件调用的状况,这样作有一个显著缺点,就是复杂,会使代码复杂化,逻辑复杂化。须要提供一系列的注册,更新,吊销方法,以及许多不一样的参数重载以实现相应的功能。当开发中存在新需求时,如对菜单及工具栏项绑定权限 Key,就须要一系列的接口修改或参数修改。
我在上面两个例子中,将菜单和工具栏资源化,经过一种 相似URI,统一资源标识符的方式 来控制,最大程度的将插件开发的工做量降到最低,最容易,使实习生水平的开发人员,经过10分钟的讲解,就能够从容掌握。
实现效果:
private void InitializeNavigation() { _navigationService.Register("MainMenu://Session[Text='会话']/Session/"); _navigationService.Register("MainMenu://Session/Session/Contact[Text='联系人']", new Action(() => { ContactView.ShowInstance(); })); _navigationService.Register("MainMenu://Setup[Text='设置']/Contact/"); _navigationService.Register("MainMenu://Setup/Contact/CustomerCategory[Text='业务类型',AuthorizeKey='ManageCustomerCategory']", new Action(() => { CustomerCategoryListView.ShowInstance(); })); _navigationService.Register("MainMenu://Setup/Contact/CustomerImportentLevel[Text='重要级别',AuthorizeKey='ManageCustomerImportentLevel']", new Action(() => { CustomerImportentLevelListView.ShowInstance(); })); }
相信稍具经验的开发人员,无需解释亦能明白这段代码的含义。
插件在获得宿主提供的 INavigationService (_navigationService)接口后,只需调用 Register 方法,传入 URI 及相关参数,便可实现对菜单或工具栏项目的动态注册。
INavigationService 接口的定义很是简单:
public interface INavigationService { void Register(string path);
void Register(string path, Action action); void Register(NavigationCodon codon); void Update(string path); }
从字面意思便可彻底理解,避免了传统的大段方法来提供相关的功能,核心就在于参数 path ,统一资源标识符。
协议部分根据宿所能提供的功能实现既可,如:
MainMenu:主菜单;Toolbar:工具栏:QuickStart:快速启动工具等等
以 MainMenu 为例:
路径路分即指明当前目标菜单的“层级”,在这个例子中,路径的第一部分 Setup,在上文 Winform 应用的例子中,实现为顶层菜单,而对于第二个 WPF 例子,采用了 Ribbon 式的菜单,则实现为 Tab 页;路径第二部分的 Contact 实现为二级菜单,或忽略,在 Ribbon 式菜单中,实现为 Tab 页下的 Group;第三部分 CustomerCategory 则指明了具体的菜单项目“业务类型”。
路径的第三部分 CustomerCategory 仅指定了该菜单项的 Name,其它属性均经过以中括号括起的属性语法来指定,即:Text='业务类型',AuthorizeKey='ManageCustomerCategory'。
在具体实现中,属性语法中的可用属性,通过特别处理,容许框架无关,UI无关,容许动态扩展。对于属性语法中的可用属性进行扩展,很是容易。与 INavigationService 自己的实现,是彻底解耦的,无关的。
意味着随着应用程序开发的深刻,需求的变化,出现新功能须要对应时,只需在特定位置指明新的属性名及实现其功能便可,与框架,与INavigationService 皆无关。
全部的新属性对应,甚至是原有属性的去除,均可以不影响现有任何代码,新属性实现不影响原有代码,而原有代码中属性的属性若是须要取消,取消相关对应便可,INavigationService 在解析时找不到对应的实现,可在记录日志后直接忽略,例如1.0版本的宿主支持指定菜单的颜色,到了2.0不支持了,原有在1.0下工做的代码,彻底不会受影响,仅仅是该指定到了2.0变为无效,从而实现良好的向下兼容性。
INavigationService 还提供了 Update 方法用于更新菜单或工具栏项目的状态,同时,直接在 path 中使用属性语法便可,如:
_navigationService.Update("MainMenu://Setup/Contact/CustomerCategory[Enable='False']",
此外,INavigationService 接口支持一个更复杂的参数对象 NavigationCodon
public class NavigationCodon { public NavigationPath Path { get; private set; }
public Action Action { get; set; } public Func<bool> IsEnableFunc { get; set; } public Func<Visibility> VisibilityFunc { get; set; } public NavigationCodon(string path) { this.Path = new NavigationPath(path); } }
可实如今更为复杂的场景下对菜单及工具栏项目的精细控制,如上文中的 Winform IDE 环境。
经过将菜单及工具栏项目资源化,不但实现了宿主与插件之间的彻底解耦合,也为插件自身提供了菜单工具栏解耦合的方法,插件在实现本身的业务时,亦无需获得对菜单及工具栏项目的强引用,经过 INavigationService 便可进行相关操做。
在此抛砖引玉,欢迎批评指正。 :)