SailingEase WinForm Framework WinForm开发框架开发手册:http://docs.shengxunwei.com/Home/Browser/sewinformfw/

本系列文章将详细阐述客户端应用程序的设计理念,实现方法。

本系列文章以  SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客户端应用程序的设计与开发。

目录:

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

SailingEase WinForm Framework

其实这是从 IDE 项目中提取出来的一个纯开发框架,它没有用户管理、权限管理之类的现成功能,而是提供纯开发角度的开发框架,概括来说提供了以下几方面的功能:

a.宿主程序(壳)与功能模块(插件)的加载、调度、通信等实现;

b.不同插件之间在完全接耦合的基础上,同步/异步调用、状态响应等机制的实现;

c.插件之间在代码层面完全没有互相引用关系,可以实现在缺少任意插件的情况下启动应用,即使他们在UI层有交集;

d.支持模块间的依存关系定义;

d.事件聚合器,用于在完全解耦的条件下,发布及订阅事件;

d.宿主程序提供了统一的主菜单及右键菜单的注册/吊销/状态控制机制;

e.宿主程序提供了统一的窗口调度/加载/销毁功能;

f.宿主程序提供了统一的日志记录、异常捕获,Web页面互操作等功能;

g.基于 GDI+ 自行实现的控件包,提供了高度的可扩展性;

h.基于zip格式的文件包管理器(基于zip的自定义文件格式,读取或写入指定的流);

i.对http、xml、磁盘io、反射、加解密等操作的增强与封装;

j.其它……

第三章节:实现菜单/工具栏按钮的解耦及状态控制

我们回顾一下上一章节中的客户端程序结构图:

可以留意到,主菜单主主菜单下面的子菜单,以及工具栏按钮,是允许模块在其中注册新项的,我们将在本章节中详细讲解这一细节的实现方法。

前文提到,一些入门级的模块化程序,通常直接将工具栏或菜单的视图,定义并公开,加载到宿主程序中,这样做有几具问题:

1.性能较差,由模块直接负责相关视图的切换或隐藏/显示,对于复杂视图性能极低;

2.可靠性低,同上,模块负责操作宿主的视图,非常容易破坏宿主的应用程序结构,并导致宿主无法handle其中的异常;

3.无法解耦菜单/工具栏的程序功能和视图表现,例如将传统菜单工具栏表现形式升级为Office 2016形式,此时必须全部模块重写相关视图加宿主大改;

那么如何在应用程序的宿主中,向插件提供统一的菜单,工具栏注册,更新,销毁机制呢?以及如何做到UI无关的彻底解耦合?

这里的主要思路是:模块不能直接定义菜单/工具栏的视图,而是能过其它方式,定义它们的功能和意义,由宿主程序解析并呈现。

看两个例子:

基于 Winform 的插件式应用程序:

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

这是一个基于 Winform 的 IDE 程序,与 Visual Stuido 非常类似,这是一个典型的例子, Visual Studio 功能繁多,菜单和工具栏的切换非常频繁。

主菜单及工具栏根据加载的模块,以及当前激活的窗体有所不同,菜单及工具栏按钮的状态则根据当前激活窗体内的数据或行为的不同而有所不同。

图中黄色背景的工具栏部分为窗体设计器所特有,也类似于在新版的 Word 中选中图形或表格时出现的特定菜单项目。每当在窗体设计器中进行不同的操作时,工具栏中的项目将呈现不同的状态。

基于 WPF 的插件式应用程序:

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

这个是一个基于 WPF 开发的普通桌面应用程序,根据当前加载的模块不同,上方主菜单显示的项目有所不同。这个例子比较简单,虽然主菜单是根据插件而加载的,但是加载之后不会有状态变化。

上面的两个例子,虽然一个是 Winform,一个是 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 环境。

在后续的章节中,我将继续阐述如何基于 SailingEase Winform Framework 进行模块化的客户端应用程序设计。

欢迎加我QQ交流探讨,共同学习:279060597,另外我在南京,有南京的朋友吗?

基于 SailingEase WinForm Framework 开发客户端程序(3:实现菜单/工具栏按钮的解耦及状态控制)的更多相关文章

  1. 基于 SailingEase WinForm Framework 开发优秀的客户端应用程序(目录)

    本系统文章将详细阐述客户端应用程序的设计理念,实现方法. 本系列文章以  SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客 ...

  2. 基于 SailingEase WinForm Framework 开发优秀的客户端应用程序(1:概述)

    本系统文章将详细阐述客户端应用程序的设计理念,实现方法. 本系列文章以  SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客 ...

  3. c++下基于windows socket的服务器客户端程序(基于UDP协议)

    前天写了一个基于tcp协议的服务器客户端程序,今天写了一个基于UDP协议的,由于在上一篇使用TCP协议的服务器中注释已经较为详细,且许多api的调用是相同的,故不再另外注释. 使用UDP协议需要注意几 ...

  4. 基于MINA框架快速开发网络应用程序

    1.MINA框架简介 MINA(Multipurpose Infrastructure for Network Applications)是用于开发高性能和高可用性的网络应用程序的基础框架.通过使用M ...

  5. [iPhone高级] 基于XMPP的IOS聊天客户端程序(IOS端一)

    介绍完了服务器,这篇我们就要介绍重点了,写我们自己的IOS客户端程序 先看一下我们完成的效果图 首先下载xmppframework这个框架,下载 点ZIP下载 接下来,用Xcode新建一个工程 将以下 ...

  6. 使用WinFrom + CefSharp 开发客户端程序

    今天使用CefSharp,加上本地资源文件嵌入了HTML.CSS.JS文件,做为Winform的UI:效果不错,漂亮可控,简简单单,半天时间搞定从开发到上线: 下面记录下相关的备忘: (窗口的效果) ...

  7. 基于XMPP的IOS聊天客户端程序

    简介:XMPP协议是一种基于Socket长连接.以XML格式进行基本信息交换.C/S S/S多种架构的聊天协议 XMPPServer 基于XMPP协议的服务端(例如eJabber.OpenFire) ...

  8. 〖Linux〗Qt+gsoap开发客户端程序,服务端地址设定的字符串转换处理

    之所以写出来,是由于经常因为这个问题屡屡丢面子.. 一般情况下,QString转换成(char*),我们一般直接使用: char *str = qstr->text().toLatin1().d ...

  9. electron vue 开发客户端程序

    文档知识点 https://electronjs.org/docs/tutorial/about (1)Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Ma ...

随机推荐

  1. Tomcat一个BUG造成CLOSE_WAIT

    之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...

  2. 札记:android手势识别,MotionEvent

    摘要 本文是手势识别输入事件处理的完整学习记录.内容包括输入事件InputEvent响应方式,触摸事件MotionEvent的概念和使用,触摸事件的动作分类.多点触摸.根据案例和API分析了触摸手势T ...

  3. 我为Net狂 ~ 社交平台系列小集合!

    微信平台: 我为Net狂(dotNetCrazy) 资源贴吧: http://tieba.baidu.com/f?kw=毒逆天 个人博客: http://dunitian.cnblogs.com/ h ...

  4. Python标准库--typing

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python 3.5 增加了一个有意思的库--typ ...

  5. MVC5 网站开发之九 网站设置

    网站配置一般用来保存网站的一些设置,写在配置文件中比写在数据库中要合适一下,因为配置文件本身带有缓存,随网站启动读入缓存中,速度更快,而保存在数据库中要单独为一条记录创建一个表,结构不够清晰,而且读写 ...

  6. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  7. 【Web动画】SVG 实现复杂线条动画

    在上一篇文章中,我们初步实现了一些利用基本图形就能完成的线条动画: [Web动画]SVG 线条动画入门 当然,事物都是朝着熵增焓减的方向发展的,复杂线条也肯定比有序线条要多. 很多时候,我们无法人工去 ...

  8. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  9. 设计模式之结构类模式大PK

                                      结构类模式大PK 结构类模式包括适配器模式.桥梁模式.组合模式.装饰模式.门面模式.享元模式和代理模式.之所以称其为结构类模式,是因 ...

  10. 走进缓存的世界(三) - Memcache

    系列文章 走进缓存的世界(一) - 开篇 走进缓存的世界(二) - 缓存设计 走进缓存的世界(三) - Memcache 简介 Memcache是一个高性能的分布式内存对象缓存系统,用于动态Web应用 ...