多文档界面(MDI)系统框架-C#完整版(一)[vjsdn]
/******************************************************/
多文档界面(MDI)系统框架-C#完整版
Programming by:Jonny Sun(易学原创)
www.vjsdn.net all righrights reserved.
/******************************************************/
说明:
Windows应用程序的用户界面主要有两种形式:单文档界面(Single Document Interface,SDI )
和多文档界面( Multiple Document Interface,MDI )。
单文档界面并不是指只有一个窗体的界面,而是指应用程序的各窗体是相互独立的,用户只能操作当前
活动的窗体,不能同时操作其它窗体,这是单文档界面程序主要特性。单文档界面如安全卫士360。
多文档界面由多个窗体组成,但这些窗体不是独立的。其中有一个窗体称为MDI父窗体,其他窗体称为
MDI子窗体。可以把MDI父窗体理解为一个容器,子窗体的活动范围限制在MDI父窗体这个容量内,
永远不能将其移动到MDI窗体之外。MDI程序遍布在各个角落,如:Microsoft Word程序。
多文档界面(MDI)系统框架运行如下:
[图一]
MDI程序主要特点是由父窗体可以控制所有子窗体的操作。我们在设计上需要抽象出一个公共接口来规范子窗体的操作。但是不能限制和固化子窗体的功能。有些子窗体具有独特的功能而其它子窗体没有。接口设计只关心子窗体的共性。
那我们如何实现那些独特的功能呢? 我们用过很多MDI软件,如Ms.Word,VisualStudio,当切换子窗体时主窗体上的按钮也随之改变。通过观察不难看出,按钮状态是与子窗体紧密相关的。所以得出一个简单的设计理论:当子窗体获得焦点时更新主窗体的按钮.先看一段代码:
//子窗体获取焦点时注册子窗体的按钮。
//通过Form.Active我们可以看到主窗体的ToolBar动态变化。
private void frmBaseChild_Activated(object sender, EventArgs e)
{
this.RegisterToolBar(this.ToolbarRegister);
this.NotifyObserver(); //通过其它观察者
}
//再看RegisterToolBar方法。
public virtual void RegisterToolBar(IToolbarRegister toolBarRegister)
{
//this.Buttons是当前窗体的按钮数组。
toolBarRegister.RegisterButton(this.Buttons);
}
Form.Active-->this.RegisterToolBar(this.ToolbarRegister)-->
toolBarRegister.RegisterButton(this.Buttons);这段代码更新了主窗体的按钮。
MDI有很多子窗体而子窗体有很多共同之处,所以我们需要设计一个窗体基类frmBaseChild。通过基类可以
分享相同的功能。如通过上面的RegisterToolBar()方法完成按钮注册动作,这样所有继承frmBaseChild基类的窗体都享受这个功能。
//通知观察者进行更新
private void NotifyObserver()
{
foreach (IObserver o in _observers) o.Notify();
}
这里用到观察者模式(Observer Pattern),主窗体的StatusBar有2个需要动态更新的观察对象[打开的窗体个数]和[当前窗体名字]。这两个观察对象随子窗体的打开/关闭/切换动作随之更新。
通过上面的介绍我们了解到窗体切换时更新按钮的原理。那么父窗体与子窗体是如何运作的呢? 说来也相当简单。首先看[图一],父窗体包含了几个控件,如主菜单,Toolbar,StatusBar。主菜单负责打开窗体或执行其它工作,Toolbar负责控制当前子窗体的操作,StatusBar跟踪程序动态,如显示状态信息,登录用户,登录时间,当前窗体名字,子窗体个数,开发团队等等。主菜单的功能相当简单,不作细谈。看下面代码。
private void menuChild2_Click(object sender, EventArgs e)
{
//调用MdiTools.OpenChildForm()方法打开子窗体。
MdiTools.OpenChildForm(this, typeof(frmChildPrint));
}
private void menuAboutMDI_Click(object sender, EventArgs e)
{
//打开模式窗体。
new frmAbout().ShowDialog();
}
Toolbar工具栏是重要实现部分。我当初写这个案例时考虑到用户喜好, 所以实现了两种完全不同的工具栏。
分别是.Net自带的非常漂亮的ToolStrip控件ToolStripRegister和用户自定义控件MyToolbarRegister。
在IDE设计状态MDI主窗体不包含任何Toolbar控件,由程序运行时动态加载.那如何动态加载呢?听我慢慢道来.
因需求上考虑用户喜好所以系统必需支持两种以上不同的Toolbar,由此看来似乎增加设计复杂度,其实不然,
我就是要这个效果,这是一个经典的接口应用! 设计动态加载Toolbar程序之前我想到一个好听的名字:
工具栏注册器.(ToolbarRegister).该注册器负责注册2种甚至n种工具栏.
工具栏注册器(IToolbarRegister)接口定义:
/// <summary>
/// 工具栏控件接口
/// </summary>
public interface IToolbarRegister : IDisposable
{
void RegisterButton(IList buttons); //注册按钮
void Dispose(); //重置工具栏按钮,销毁控件.
IButtonInfo CreateSeperator(); //创建分隔按钮,如"|"
/// <summary>
/// 创建工具栏上的按钮.
/// </summary>
/// <param name="name">按钮名称:如btnSave,btnClose</param>
/// <param name="caption">按钮名称</param>
/// <param name="image">按钮图片</param>
/// <param name="size">按钮大小</param>
/// <param name="clickEvent">按钮的Click事件</param>
/// <returns></returns>
IButtonInfo CreateButton(string name, string caption, Bitmap image,
Size size, OnButtonClick clickEvent);
}
IToolbarRegister接口主要功能是创建按钮,要创建什么样的按钮呢? 当然是万能按钮,这个按钮同样需要接口来抽象.命名为IButtonInfo.
/// <summary>
/// 定义按钮接口
/// </summary>
public interface IButtonInfo
{
string Caption { get;set;} //按钮标题
Image Image { get;set;} //按钮图片
int Index { get;set;} //显示顺序
object Button { get;} //按钮对象
bool Enable { get;set;} //禁止/可用
}
/// <summary>
/// 点击按钮触发Click事件,Click事件委托
/// </summary>
public delegate void OnButtonClick(IButtonInfo sender);
好了,接口定义完毕! 这样是不是可以工作了呢?当然不能,接口只是业务抽象和规范动作.最重要的是需要类来完成.下面是实现IToolbarRegister和IButtonInfo接口的两个类.
实现IButtonInfo接口,创建.Net自带控件ToolStripButton.
实现IToolbarRegister接口,创建.Net自带控件ToolStrip
/// <summary>
/// .Net自带的ToolStripButton按钮
/// </summary>
internal class ToolStripButtonInfo : IButtonInfo
{
private ToolStripButton _btn = null;
private OnButtonClick _clickEvent = null;
public ToolStripButtonInfo(string name, string caption, Bitmap image,
Size size, OnButtonClick clickEvent)
{
_btn = new ToolStripButton();
_btn.Name = name;
_btn.Image = image;
_btn.Text = caption;
_btn.Size = size;
_btn.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
_btn.ImageTransparentColor = System.Drawing.Color.Magenta;
_clickEvent = clickEvent;
_btn.Click += new EventHandler(OnClick);
}
private void OnClick(object sender, EventArgs e)
{
if (_clickEvent != null) _clickEvent(this);
}
public string Caption { get { return _btn.Text; }
set { _btn.Text = value; } }
public Image Image { get { return _btn.Image; }
set { _btn.Image = value; } }
public object Button { get { return _btn; } }
private int _Index = -1;
public int Index { get { return _Index; } set { _Index = value; } }
public bool Enable { get { return _btn.Enabled; }
set { _btn.Enabled = value; } }
}
/// <summary>
/// .Net控件ToolStrip注册器.
/// </summary>
public class ToolStripRegister : IToolbarRegister
{
private ToolStrip _toolStrip = null;
private Form _Owner = null;
public ToolStripRegister(Form owner)
{
_Owner = owner;
_toolStrip = new ToolStrip();
owner.Controls.Add(_toolStrip);
owner.Controls.SetChildIndex(_toolStrip,1);
}
public void RegisterButton(IList buttons)
{
_toolStrip.SuspendLayout();
_toolStrip.Items.Clear();
foreach (IButtonInfo bi in buttons)
{
ToolStripButton btn = bi.Button as ToolStripButton;
_toolStrip.Items.Add(btn);
}
_toolStrip.ResumeLayout();
}
public void Dispose()
{
_Owner.Controls.Remove(_toolStrip);
}
public IButtonInfo CreateSeperator()
{
return new ToolStripButtonSeperator();
}
public IButtonInfo CreateButton(string name, string caption, Bitmap image, Size size, OnButtonClick clickEvent)
{
return new ToolStripButtonInfo(name, caption, image, size, clickEvent);
}
}
好了,有了工具栏注册器和按钮的类只需要在MDI主窗体内实例化对象就可以工作了.
.......
//主窗体上的按钮工具栏。ToolBar
private IToolbarRegister _MdiToolbar = null;
public frmMDI()
{
InitializeComponent();
}
private void frmMDI_Load(object sender, EventArgs e)
{
this._MdiToolbar = new ToolStripRegister(this);
this.RegisterMdiButtons(); //创建主窗体上的按钮
}
/// <summary>
/// 注册MDI主窗体功能的按钮
/// </summary>
public void RegisterMdiButtons()
{
IList btns = new ArrayList();
btns.Add(this.MdiToolbar.CreateButton("btnHelp", "帮助",
global::VJSDN.Tech.MDI.Properties.Resources._24_Help, new Size(57, 28),
this.DoHelp));
btns.Add(this.MdiToolbar.CreateButton("btnClose", "关闭",
global::VJSDN.Tech.MDI.Properties.Resources._24_Exit, new Size(57, 28),
this.DoClose));
//注意:注册两款自定义按钮
btns.Add(this.MdiToolbar.CreateButton("btnOpenDataForm", "打开数据窗体",
global::VJSDN.Tech.MDI.Properties.Resources._24_Clone, new Size(57, 28),
this.DoOpenDataForm));
btns.Add(this.MdiToolbar.CreateButton("btnOpenReport", "打开报表窗体",
global::VJSDN.Tech.MDI.Properties.Resources._24_CreateDoc, new Size(57, 28), this.DoOpenReportForm));
this.MdiToolbar.RegisterButton(btns);
}
我们定义好了接口和实现接口的类. 万事具备只欠东风,按F5运行:
[图二]
后续....
==============================
多文档界面(MDI)系统框架已完成。链接地址:
系统介绍及接口设计
多文档界面(MDI)系统框架-C#完整版(一)
父窗体与子窗体之间互动关系及Toolbar实现
多文档界面(MDI)系统框架-C#完整版(二)
系统框架UML图
多文档界面(MDI)系统框架-C#完整版(三)(UML图)