谈基于.net平台windows开发中的模式窗体
发布日期: 2006-04-23 | 更新日期: 2006-05-05
作者:郑佐
适用于: Windows 操作系统
.NET Framework 1.x,2.0运行时环境
.NET Windows开发Visual Studio 2005
摘要:本文阐述了在基于.NET平台的Windows程序开发中使用模式窗体的诸多方面,部分内容延伸到一般窗体的应用。
单击此处下载本文的示例代码。
内容索引
概述
何谓模式窗体?简单的可以理解为窗体对话框,用户必须在完成该窗体上的操作或关闭窗体后才能返回打开此窗体的窗体。本文不对模式窗体的定义、特征、功能做具体讨论,主要把重点放在如何在.net窗体应用程序中有效的使用模式窗体,解决使用模式窗体中碰到的常见问题。
模式窗体的属性设置
在.net中一个System.Windows.Forms.Form类就表示一个窗体,通过visual studio 2005设计器能够直接添加窗体,切换到设计模式,在属性窗口中会显示属于该窗体的属性和事件。参照标准的模式窗体,以visual studio 2005程序的菜单工具->选项打开的那个选项对话框为例,对于设计器初始化的窗体还是需要进行一番设置才能达到专业化。令人高兴的是这些设置都可以在设计器模式中通过属性设置实现,笔者将通过代码来实现相应功能,下面对其进行详细描述。
Form.StartPosition属性,确定窗体第一次出现时的位置。这里设置为在父窗体的中间显示。
this.StartPosition = FormStartPosition.CenterParent;
Form.HelpButton属性,确定窗体的标题栏上是否有“帮助”按钮。设置显示,看上去更人性化,但实际不一定会对帮助功能进行实现。
this.HelpButton = true;
Form.MaximizeBox属性,确定窗体标题栏的右上角是否有最大化框。设置不让她显示。
this.MaximizeBox = false;
Form.MinimizeBox属性,确定窗体标题栏的右上角是否有最小化框。设置不让他显示。
this.MinimizeBox = false;
Form.ShowIcon属性,指示是否在窗体的标题栏中显示图标。设置不显示。
this.ShowIcon = false;
Form.ShowInTaskbar属性,确定窗体是否出现在Windows任务栏中。这个当然要节省任务栏的宝贵空间。
this.ShowInTaskbar = false;
Form.FormBorderStyle属性,指示窗体的边框和标题栏的外观和行为。设置这个属性将不允许拖动调整窗体的大小,同时Icon属性将失效,并不显示窗体标题栏图片。
this.FormBorderStyle = FormBorderStyle.FixedDialog;
Form.ControlBox属性,确定窗体是否有“控件/系统”菜单框。通过该设置可以隐藏标题栏的控制按钮。在有些时候还是有必要设置为False,标题栏就不会再有控制按钮。
this.ControlBox = false;
通过对以上属性的设置,基本实现模式窗体的静态功能。对于是否允许调整窗体的大小可根据实际情况而定。
模式窗体中的按钮
模式窗体中(比如visual studio 2005中的“选项”对话框)一般会有两个基本按钮,一个[确定]按钮用来提交,另一个[取消]按钮用来撤销提交,有时候会增加一个[应用]按钮,不过像“帮助”菜单中的“关于”模式窗体可能就只有一个[确定]按钮。Windows窗体为用户操作友好性提供了比较好的支持。我们可以在Form设计界面的属性设置中找到AcceptButton和CancelButton两个属性,默认值为空即显示(无)。在属性中可以通过选择窗体上的按钮来设置值。属性修改生成的代码如下。
先定义两个Button,
private System.Windows.Forms.Button buttonOK;
private System.Windows.Forms.Button buttonCancel;
窗体的“接受”按钮。如果设置了此按钮,则用户每次按“Enter”键都相当于“单击”了该按钮。
this.AcceptButton = this.buttonOK;
窗体的“取消”按钮。如果设置了此按钮,则用户每次按“Esc”键都相当于“单击”了该按钮。
this.CancelButton = this.buttonCancel;
可见可以通过快捷键来方便的访问特定按钮,但这个有一些例外,比如窗体焦点刚好在buttonCancel上,当按{Enter}时实际按下的键会是buttonCancel而不是buttonOK,如果焦点停在第三个按钮上,那{Enter}按下相当于点击了该按钮。另一个细节是通过鼠标点击按钮和快捷键操作按钮的表现行为不一样,快捷键操作Button不会显示按钮被按下的显示效果,看上去什么都没有发生。
模式窗体的打开与关闭
谈到模式窗体的打开,一般通过Form.ShowDialog ()方法或她的一个重载Form.ShowDialog (IWin32Window)来实现,其中后一个方法将窗体显示为具有指定所有者的模式对话框。如下代码所示,
OptionForm form = new OptionForm();
//form.ShowDialog();
form.ShowDialog(this);
对于指定所有者方式打开的模式窗体可以在模式窗体内部获取主窗体的引用,
//在模式窗体内部访问所属窗体
MainForm form = this.Owner as MainForm;
注意,如果以Form.ShowDialog ()方式打开,那Form.Owner属性会是空引用。
谈到模式窗体的关闭,先来看一下模式窗体关闭后的返回值。无论是调用Form.ShowDialog ()方法还是Form.ShowDialog (IWin32Window)方法,都会在模式窗体关闭时返回System.Windows.Forms.DialogResult枚举值。参考MSDN,该枚举包含的值如下,
DialogResult.Abort,对话框的返回值是 Abort(通常从标签为“中止”的按钮发送)。
DialogResult.Cancel,对话框的返回值是 Cancel(通常从标签为“取消”的按钮发送)。
DialogResult.Ignore,对话框的返回值是 Ignore(通常从标签为“忽略”的按钮发送)。
DialogResult.No,对话框的返回值是 No(通常从标签为“否”的按钮发送)。
DialogResult.None,从对话框返回了 Nothing。这表明有模式对话框继续运行。
DialogResult.OK,对话框的返回值是 OK(通常从标签为“确定”的按钮发送)。
DialogResult.Retry,对话框的返回值是 Retry(通常从标签为“重试”的按钮发送)。
DialogResult.Yes,对话框的返回值是 Yes(通常从标签为“是”的按钮发送)。
由于某些原因在实际用户操作中比如选项数据无法保存,输入的设置数据有问题,点击[确定]按钮需要阻止窗体的关闭以对输入的设置进行调整。对于一些开发者在技术社区贴的阻止模式窗体关闭的代码,我认为不是很好的实现。以下用代码来描述该实现,注意其中用到了三个事件。
//注册窗体关闭事件
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OptionForm_FormClosing);
//注册确定按钮事件
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//注册取消按钮事件
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
三个事件对应的事件处理程序如下,
//确定按钮处理程序
private void buttonOK_Click(object sender, EventArgs e)
{
//假设textBoxPath用来记录目录路径,如果不存在要求用户重新设置。
if (this.textBoxPath.Text.Trim().Length == 0)
{
MessageBox.Show("输入路径信息不对!");
this.textBoxPath.Focus();
}
else
{
this.DialogResult = DialogResult.OK;
}
}
//取消按钮处理程序
private void buttonCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
//窗体关闭处理程序,在关闭窗体时发生。
private void OptionForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (this.DialogResult != DialogResult.Cancel && this.DialogResult != DialogResult.OK)
e.Cancel = true;
}
上面的代码都正常,就是事件写多了,对上面代码进行修改,去掉[取消]按钮事件和窗体关闭事件以及相关的事件处理程序。首先需要在窗体构造函数中通过设置按钮的DialogResult属性来实现返回特定的DialogResult。
this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
注册确定按钮事件,
//注册确定按钮事件
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//确定按钮处理程序
private void buttonOK_Click(object sender, EventArgs e)
{
if (this.textBoxPath.Text.Trim().Length == 0)
{
MessageBox.Show("输入的路径信息不对!");
this.textBoxPath.Focus();
//设置文本框焦点
this.DialogResult = DialogResult.None;
}
}
可见,新的实现方式代码减少了一半。
窗体的参数传递
对于窗体间的数据传递,是刚开始从事.Net窗体应用程序开发人员碰到的一个常见问题,在此讲几个常见的实现方式。此节内容适用于模式窗体或非模式窗体,部分方式延伸到一般类的操作。
(1)构造函数参数传递
通过构造函数传递参数应该是比较基本的参数传递方式,重载构造函数,通过带参数的构造函数来实例化窗体。
在窗体类内部定义参数变量,
private object myParams;
实现构造函数,
public OptionForm(object parameters)
{
InitializeComponent();
this.myParams = parameters;//设置参数引用
}
实例化窗体,
OptionForm form = new OptionForm( myParams );
在实际使用过程中,需要注意传入的是引用类型还是值类型,处理方式会有所不同。
(2)使用窗体的属性
说起属性关联,上面已经提到过Form.Owner属性,下面笔者根据MSDN文档来比较完整的讲一下,大部分的文字来自MSDN文档,为保证其完整性,对其中一些属性描述进行了扩展。
Form.Owner 属性。获取或设置拥有此窗体的窗体。
语法,public Form Owner { get; set; }
若要使某窗体归另一个窗体所有,可为其 Owner 属性分配一个对将成为所有者的窗体的引用。当一个窗体归另一窗体所有时,它便随着所有者窗体最小化和关闭。例如,如果 Form2 归窗体 Form1 所有,则关闭或最小化 Form1 时,Form2 也会关闭或最小化。并且附属窗体从不显示在其所有者窗体后面。可以将附属窗体用于查找和替换窗口之类的窗口,当选定所有者窗体时,这些窗口不应消失。
Form.OwnedForms 属性。获取 Form 对象的数组,这些对象表示此窗体拥有的所有窗体。
语法,public Form[] OwnedForms { get; }
此属性返回包含此窗体拥有的所有窗体的数组。要使某窗体归另一个窗体所有,可调用 AddOwnedForm 方法。分配给所有者窗体的窗体将保持被拥有状态,直到调用了 RemoveOwnedForm 方法。如果窗体是多文档界面 (MDI) 父窗体,则除了当前打开的所有 MDI 子窗体外,此属性将返回所有显示的窗体。
Form.MdiChildren 属性。获取窗体的数组,这些窗体表示以此窗体作为父级的多文档界面 (MDI) 子窗体。
语法,public Form[] MdiChildren { get; }
此属性使您得以获取对当前在某 MDI 父窗体中打开的所有 MDI 子窗体的引用。若要创建 MDI 子窗体,请将要成为 MDI 父窗体的 Form 分配给该子窗体的 MdiParent 属性。可以使用此属性依次通过所有 MDI 子窗体,以执行一些操作,如当 MDI 父窗体关闭时将数据保存到数据库中,或者根据应用程序中执行的操作更新子窗体上的字段。
Form.MdiParent 属性。获取或设置此窗体的当前多文档界面 (MDI) 父窗体。
语法,public Form MdiParent { get; set; }
若要创建 MDI 子窗体,请将要成为 MDI 父窗体的 Form 分配给该子窗体的 MdiParent 属性。可以从某 MDI 子窗体使用此属性来获取所有子窗体都需要的全局信息或者调用对所有子窗体执行操作的方法。
Form.ActiveForm 静态属性。获取此应用程序的当前活动窗体。
语法,public static Form ActiveForm { get; }
表示当前活动窗体,或者如果没有活动窗体,则为空引用。可以使用此方法获得对当前活动窗体的引用,以在该窗体或其控件上执行操作。
Form.ActiveMdiChild 属性。获取当前活动的多文档界面 (MDI) 子窗口。
语法,public Form ActiveMdiChild { get; }
返回表示当前活动的 MDI 子窗口的 Form,或者如果当前没有子窗口,则返回 空引用。可使用此方法确定 MDI 应用程序中是否有任何打开的 MDI 子窗体。也可使用此方法从 MDI 子窗口的 MDI 父窗体或者从应用程序中显示的其他窗体对该 MDI 子窗口执行操作。
ContainerControl.ParentForm 属性。获取将容器控件分配给的窗体。
语法,public Form ParentForm { get; }
将容器控件分配给的 Form。
以上属性MSDN提供相应的代码事例,可直接拿来调试使用。
(3)使用公共属性
使用公共属性也是一种比较常用的方式,通过窗体设计器添加的控件默认访问修饰符为private级别,可以设置成public或Internal(在程序集内部可见)来对外公开。比如对窗体中的Button进行公开,那就可以访问Button的相关属性,同时也可以注册事件或撤销事件注册。如,
OptionForm form = new OptionForm();
form.buttonOK.Click += new EventHandler(buttonOK_Click);
form.ShowDialog();
对于只允许读取访问或修改访问的控件或变量可以通过属性来控制。对(1)方式进行修改,去除重载构造函数,增加属性也可以实现同样的效果。
public object MyParams
{
get { return this.myParams; }
set { this.myParams = value; }
}
(4)使用公共方法
使用公共方法类似于属性,对上面的同等实现如下,
//获取参数
public object GetParams()
{
return this.myParams;
}
//设置参数
public void SetParams(object myParams )
{
this.myParams = myParams;
}
(5)使用静态类该方式可以简单的理解为静态变量全局共享,通过下面代码能够比较清楚的理解,先来定义静态类,
public static class ParameterSettings
{
//公共静态变量
public static string Username = "Zhengzuo";
//私有静态变量
private static string userRole = "Administrators";
//私有静态变量
private static string password = "http://blog.csdn.net/zhzuo";
//内部属性
internal static string UserRole
{
get { return userRole; }
}
//公共属性
public static string Password
{
get { return password; }
private set { password = value; }
}
}
在需要访问的地方通过以下方式进行,
string username = ParameterSettings.Username;
string password = ParameterSettings.Password;
string userRole = ParameterSettings.UserRole;
ParameterSettings.Username = "郑佐";//修改成新用户名
(6)窗体实现Singleton模式
Singleton模式是我们开发过程中最常用的模式之一。在技术社区经常看到有人谈及对主窗体实现Singleton,但个人认为这不是一种妥当的做法,因为没有这个必要。这里通过另一个自定义类来进行演示。假设UserLoginInfo类用来保存登录系统后的用户凭据。
/*==============================================
程序 郑佐 2006-4-23 http://blog.csdn.net/zhzuo
==============================================*/
public class UserLoginInfo
{
//实现Singleton模式,线程安全。
private readonly static UserLoginInfo currentUserInfo = new UserLoginInfo();
//提供全局访问点
public static UserLoginInfo CurrentUserInfo
{
get { return currentUserInfo; }
}
//阻止显式实例化,但不能阻止反射方式调用。
private UserLoginInfo()
{
}
//公共变量
public string Username;
//私有变量
private static string userRole;
//私有变量
private static string password;
//内部属性
internal string UserRole
{
get { return userRole; }
set { userRole = value; }
}
//公共属性
public string Password
{
get { return password; }
internal set { password = value; }
}
}
在其他代码中进行访问,
UserLoginInfo.CurrentUserInfo.Username ="郑佐";
UserLoginInfo.CurrentUserInfo.UserRole = "dotnetlover";
UserLoginInfo.CurrentUserInfo.Password = "http://blog.csdn.net/zhzuo";
对于Singleton模式的实现方式有很多,编写时需要考虑是否需要保证实例访问的线程安全问题,以免引发不可预料的情况,为了提高性能可以考虑惰性实例化。关于Singleton模式的更多信息可以参考另一篇文章。
(7)发布事件进行订阅
通过事件来传递参数应该说是一种推的实现方式,在产生事件时进行被动的获取相关数据。这里将通过一个自定义事件来演示数据的传输。
在自定义事件时,标准的做法都会先定义一个事件参数类,要么直接使用基类EventArgs,或者从EventArgs继承实现自己的参数类,假设自定义基类取名为OptionSettingEventArgs,
//选项设置事件参数类
public class OptionSettingEventArgs : EventArgs
{
private string changedPath;
//构造函数
public OptionSettingEventArgs(string changedPath)
{
this.changedPath = changedPath;
}
//读取参数
public string ChangedPath
{
get { return this.changedPath; }
}
}
以上参数类只包含一个修改后的路径参数。接下去我们要对原先的OptionForm窗体增加事件定义,这里使用.net 2.0中提供的泛型类来实现。
//定义事件
public event EventHandler<OptionSettingEventArgs>
OptionSettingChanged;
编写事件引发程序如下,
//引发OptionSettingChanged事件
protected virtual void OnOptionSettingChanged(OptionSettingEventArgs e)
{
if (OptionSettingChanged != null)
{
OptionSettingChanged(this, e);
}
}
对文件目录选择按钮事件处理程序进行修改来实现事件激发,并没有考虑直接从文本框直接数据输入方式。
//通过目录对话框设置新的路径
private void buttonBrowser_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new FolderBrowserDialog();
DialogResult result = dialog.ShowDialog(this);
if (result == DialogResult.OK)
{
if(this.textBoxPath.Text != dialog.SelectedPath)
{
this.textBoxPath.Text = dialog.SelectedPath;
OptionSettingEventArgs args = new OptionSettingEventArgs(dialog.SelectedPath);
OnOptionSettingChanged(args);
}
}
}
好了,一切准备工作完成,调用代码如下,
OptionForm form = new OptionForm();
//注册事件
form.OptionSettingChanged += new EventHandler
(form_OptionSettingChanged);
form.ShowDialog();
通过以下事件处理程序来验证其正确性,
private void form_OptionSettingChanged(object sender, OptionSettingEventArgs e)
{
string newPath = e.ChangedPath;
MessageBox.Show(this, String.Format("新路径为“{0}”。", newPath), "提示");
}
在实际开发过程中,合理的处理方式可能是以上几种方式的组合。对于窗体间的参数传递,我在另一篇文章中也有比较多的基础实例讲解。
.Net Framework提供的模式窗体
.net Framework为我们提供了一些比较常用的对话框,在开发过程中省了不少事,以下对其进行介绍。
MessageBox。显示可包含文本、按钮和符号(通知并指示用户)的消息框。通过MessageBox.Show 静态方法来打开模式对话框。
public static DialogResult Show ( string text );
该方法包含多个重载版本。复杂的一个方法如下,
public static DialogResult Show ( IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, string helpFilePath, HelpNavigator navigator, Object param ) ;
根据不同的参数可以定制对话框的行为。
另外一些对话框提供了特定功能。
OpenFileDialog。打开文件对话框,从FileDialog类继承,提示用户打开文件,无法继承此类。对于文件的打开操作属于比较常见的。
SaveFileDialog。保存文件对话框,从FileDialog类继承,提示用户选择文件的保存位置。无法继承此类。
FolderBrowserDialog。目录浏览对话框,从CommonDialog类继承,提示用户选择文件夹。无法继承此类。 FontDialog。字体设置对话框,从CommonDialog类继承,提示用户从本地计算机上安装的字体中选择一种字体。可继承该类。
ColorDialog。颜色设置对话框,从CommonDialog类继承,表示一个通用对话框,该对话框显示可用的颜色以及允许用户定义自定义颜色的控件。可继承该类。
PageSetupDialog。打印页面设置对话框,从CommonDialog类继承,允许用户更改与页面相关的打印设置,包括边距和纸张方向。无法继承此类。
PrintDialog。打印对话框,从CommonDialog类继承,允许用户选择一台打印机并选择文档中要打印的部分。无法继承此类。
PrintPreviewDialog。打印预览对话框,从Form类继承,表示包含 PrintPreviewControl 的对话框窗体。可继承该类。由于该类从Form类继承,所以除了通过
PrintPreviewDialog.ShowDialog ();
PrintPreviewDialog.ShowDialog (IWin32Window);
方法以模式方式打开窗体外,还可以通过PrintPreviewDialog.Show ();或其重载PrintPreviewDialog.Show (IWin32Window);方法按正常非模式方式打开。
上面列举的文件对话框抽象基类FileDialog是从CommonDialog抽象类继承,因此所有从该类继承的对话框都可以通过CommonDialog.ShowDialog ();或其重载CommonDialog.ShowDialog (IWin32Window);方法以模式方式打开窗体。
更多资源
对于windowsforms的开发,这里推介几本大师写的书,
Charles Petzold著的《Programming Windows with C# (Core Reference)》,中文翻译版为《Microsoft c#Windows程序设计(上下册)》。
Chris Sells著的《Windows Forms Programming in C#》,中文翻译版为《Windows Forms程序设计》。在amazon.com上看到该书好像已经出第二版了。