在插件式应用程序中,实现对菜单,工具栏按钮的 完全解耦及状态控制

之前承诺会对 Winform IDE,WPF 客服程序的开发进行进一步的分解记录,很抱歉一直没有太多时间认真梳理。

本篇博客抽取了这两个应用程序的一个共通功能的实现方法进行说明,即在插件式应用程序中,对菜单及工具栏的控制。

对于复杂的应用程序开发,我们可能会将程序的功能进行分解,模块化,插件化;那么如何在应用程序的宿主中,向插件提供统一的菜单,工具栏注册,更新,销毁机制呢?以及如何做到UI无关的彻底解耦合?

看两个例子:

基于 Winform 的插件式应用程序: http://www.cnblogs.com/sheng_chao/p/4387249.html

这是一个基于 Winform 的 IDE 程序,主菜单及工具栏根据加载的模块,以及当前激活的窗体有所不同,菜单及工具栏按钮的状态则根据当前激活窗体内的数据或行为的不同而有所不同。

图中黄色背景的工具栏部分为窗体设计器所特有,类似于在新版的 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 即可进行相关操作。

在此抛砖引玉,欢迎批评指正。   :)

一点小感想:许多开发工作,看起来简单,想要做好却不容易,例如本篇中所阐述的这个问题,我所经历过的一个大型软件项目,在各个插件的 Ribbon 菜单控制上,反反复复,产生了许多的问题,浪费了很多人力及时间。严重的时候一半以上的时间在和菜单较劲(CAD软件,功能繁杂,界面复杂)。但是这些问题本是可以轻易避免的,开发团队本身对问题的认识不够深刻,领导的不重视亦是很大因素,对于软件开发工作不够了解,思想停留在拖控件,画界面上,不愿对团队投入更多的人力,资金支持,导致团队成员疲于奔命,不断的延期。 却找不到症结所在,我想如果团队对于开发中的细节问题予以重视,只要找个会议室关起门来找办法,有许多的弯路是不必走的。所以遇到反复的问题,最好的办法是停下来,重新审视。

小广告

博主正在留意南京的相关高级职位

江苏电信10000号前技术经理,现任某外资企业Team Leader

简历:http://121.40.198.87:8010/

使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序:

http://www.cnblogs.com/sheng_chao/p/4387249.html

使用 WPF+ ASP.NET MVC 开发 在线客服系统 (一):

http://www.cnblogs.com/sheng_chao/p/4548146.html

时间: 2024-10-09 21:22:08

在插件式应用程序中,实现对菜单,工具栏按钮的 完全解耦及状态控制的相关文章

使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)

对于一些较小的项目,具备一定经验的开发人员应该能够设计和构建出便于进行维护和扩展的应用程序.但是,随着功能模块数量(以及开发维护这些部件的人员)的不断增加,对项目实施控制的难度开始呈指数级增长. SailingEase WinForm 框架为您提供了针对此问题提出的解决方案.在本文中,将对基于SailingEase WinForm 框架的复合应用程序的定义进行解释,并简要说明如何才能构建一个基于 SailingEase WinForm 框架功能的复合应用程序. 传统的单一应用程序 传统的单一应用

MAF+WPF实现插件式应用程序框架

关于maf和wpf大家感兴趣的话可以去百度学习一下,下面展示一下成果: 登录界面 主界面:默认的是我的应用,表示已经下载到本地的应用. 辅助应用类似appstore功能,指示未下载或者需要升级的程序列表,实现在线下载更新应用程序插件 这是打开应用的界面,每个应用都会新建一个tab页寄托在主程序中. QQ:94-15-97-411

c# winfrom程序中 enter键关联button按钮

1,关联按钮上的Key事件        在按钮上的keypress,keydown,keyup事件必须要获得焦点,键盘上的键才能有效.      private void btnEnter_KeyDown(object sender, KeyEventArgs e)         {                        if (e.KeyCode == Keys.Enter)//如果输入的是回车键             {                 this.btnEnte

java桌面程序中使用联动菜单遇到与解决的问题

最近在做一个小小的系统,想实现联动菜单,故尝试了几种方法. 1.想通过一个线程来监控下拉菜单的变化,从而控制从菜单. 遇到的问题: ①:开始,我是用一个外部线程类,不过我要通过外部线程类来改变主窗口的下拉菜单, ②:所以,就需要一个传递一个类参数才能引用, ③:我在主窗口中调用线程类. ④:由于我需要把从数据中得来的数据在线程类中使用,所以就需要在线程类中使用数据库连接 当然不是在run方法中使用,而是在最开始定义数据时就初始化赋值好. ⑤:我以为,在最开始使用数据库连接,就连一次,应该没事.

企业信息化解决方案——插件式平台开发框架

0.三板斧 作为职业Programmer或是优秀Team,拥有一套成熟.稳定的开发框架,无疑是行走IT江湖.纵横IT市场的必备功底. 无图无真相,作为一个讲究实效的ITer,先来几道的甜点凉菜,后续会上更多的硬菜啦~o(∩_∩)o ~ 0.1 平台登录界面 0.1.平台登录界面 平台登录模块的设计兼顾了安全性和易用性.只有合法授权且状态正常的用户才能登录到平台.同时为方便用户使用,在确保电脑使用者相对唯一的情况下,可以选择保存登录信息,系统会自动对用户的相关登录信息采取加密手段后进行存储. 0.

基于Python的插件式系统结构试验

由于Python支持运行时动态载入,设计一个插件式结构是比较简单的.如果使用PyQt的话,可以轻松地创建出一个插件式的UI结构.不过,在很多时候,主程序使用C++/STL编写,通过Python来实现插件扩展.这里主要探讨"纯Python"实现的插件结构.C++Python的模式后面再说(可参考,C++嵌入Python: http://www.vckbase.com/index.php/wv/1258,C++嵌入Python要点:http://blog.chinaunix.net/uid

在小程序中使用Echart图表

在小程序中使用Echart图表 Echart UI构建(柱状图) Echart 假数据 Echart 动态设置数据 柱状图UI示例 // Echart config,包括init data 和style及数据类型 var option = { animation: false,//提高页面加载速度,关闭echart的动画 grid: [ //grid section UI ... ], xAxis: [ //xAxis section UI ... ], yAxis: [ //yAxis sec

实验6 在应用程序中播放音频和视屏

实验报告 课程名称 基于Android平台移动互联网开发 实验日期 4.15 实验项目名称 在应用程序中播放音频和视屏 实验地点 S3010 实验类型 □验证型    √设计型    □综合型 学  时 1学时 一.实验目的及要求(本实验所涉及并要求掌握的知识点) 实现在应用程序中处理音频和视频 实现播放音频,音频播放控制 实现播放视屏,视屏播放控制 使用service服务播放项目原文件的音乐 二.实验环境(本实验所使用的硬件设备和相关软件) (1)PC机 (2)操作系统:Windows XP

插件式的80后程序员是怎样在夹缝中求生存的

我们先说80后.网上到处流传80后是最苦逼的.在夹缝中求生存的. 我个人部分同意,为啥说80后在夹缝中呢?(这里我们先摒除一些80后成功人士,我可以很负责的说,这是少数.如果你就是这少数中的一员,那么请直接跳过本章) 我个人总结了有那么几点: 1.现在大部分财富都掌握在60-70后手中. 这条估计已经是一个不争的事实.话说时事造就英雄,60.70经历了房地产.下海经商.互联网.软件发展的爆发期,而这个时期为他们积累资金和财富提供了基础.而此时,80后还在读书. 2.  即将发生的财富转移 话说9