其实开始接触标签式用户界面已经是几年前的事了,当时为了设计一个可用性更好的用户界面,我们参考了不少业界巨头,最后还是比较喜欢Siemens和GE的某些医学影像产品里的标签式风格。用软件的术语,就是去掉繁琐的菜单,而用标签这种清晰简单的交互方式,让用户在几个主要的业务模块之间进行导航。在程序的实现上,为了实现不同业务模块的解偶和并行开发,当时我们采用了一些复杂的方式。尤其是如何让一个界面既能在自己的进程中独立运行,又能嵌入到标签页中与来自其他业务模块的界面一起在主进程中运行,当时调用了其他一些组件,代码写出来,编译通过,我也懒得深究了。现在有时间来回顾一下,才发现原来换一种方式,也可以如此的简单。
Application.Run(frm);
而通常我们印象中能嵌入标签页控件TabControl中的,又必须是个Control,比如:
TextBox text = new TextBox();
page.Controls.Add( text );
this .tabControl1.TabPages.Add( page );
虽然Form派生于Control,但直接把一个Form的对象Add到一个TabPage上却是不行的,要想Add成功,就要把Form指定为一个子窗口,即设定一下TopLevel和Parent这两个属性。
Form2 frm = new Form2();
frm.FormBorderStyle = FormBorderStyle.None;
frm.Dock = DockStyle.Fill;
frm.TopLevel = false ;
frm.Visible = true ;
frm.Parent = page;
page.Controls.Add( frm );
this .tabControl1.TabPages.Add( page );
这里我们发现,把FormBorderStyle设置为None以后,窗口上的菜单就消失了。但如果没有把它设置为None,Form2嵌入到TabControl来以后还会带着它的标题栏,这就很难看了。不过可以考虑采用以前MDI时代的做法,当主窗口Form1发现当前嵌入的子窗口有菜单的时候,就自动把这个菜单显示到主窗口上。另外,如果要做一个商业软件,直接用.NET自带的TabControl来做标签式界面是难以想象的,因为这个控件实在太丑了,而且你也休想通过重载它的一些成员来让它变得更漂亮些。最好还是重新写一个TabControl吧,其实很容易的。尤其是应该多用一些像Panel这样简洁明了的控件,然后就像在网页上显示大张图片时常采用的那种拼贴艺术一样把它们拼贴起来就好了,运气好的话,甚至只需要设置一下Align、Anchor或者Dock属性,不需要直接动用任何一个GDI/GDI+的API。这就是WinForm的威力。最后,不管如何自定义控件,把窗口嵌入控件的方式永远都是这么的简单。
前面我们讲O/X Mapping和O/R Mapping的故事中,都提到了插件机制。其实如果想让插件中包含用户界面的话,除了把托管的插件模块(不管是EXE还是DLL)用Assembly.Load()转载上来以外,还需要用上面的方法把他们嵌入到主界面上。当然,在详细设计的时候,还是要有定义一些公共的接口、所有插件都实现这些接口之类的考虑,但这些都是基本的设计常识,不再详述了。总之,这样一来,这些模块真的就既能独立运行,独立开发,独立调试,又能嵌入宿主进程中,与大家一起运行了。天哪,与大家一起运行?这可没那么容易。对了,我们还需要一个模块之间相互通信的机制;如果系统足够复杂,还需要一个工作流引擎来确保这些通信的有序的,可管理的。好的,这又是个大话题,且听下回分解吧。