Winform中,Dialog对话框是独立于主窗体的和用户交互的窗体,对话框分为模态和非模态两种形式,Winform内置了几个常用的系统对话框,开发者可以直接调用,此外我们也可以自定义对话框。这篇笔记我们介绍Winform中对话框的使用。
在上一章节中我们介绍窗体时,显示窗体就有模态和非模态两种形式,对话框也是一种窗体,因此这里的概念也是完全一致的。
模态对话框:使用ShowDialog()
方法显示,当用户与该对话框交互时,主窗体代码将被阻塞在ShowDialog()
方法上,用户必须先关闭对话框才能返回主程序界面,模态对话框可以通过设置DialogResult
枚举值返回用户在模态对话框上使用了确定、取消或是其它行为。
非模态对话框:使用Show()
方法显示,用户可以与主程序界面和对话框同时交互。
Winform内置了几个对话框,使用相关功能时我们可以优先使用这些内置对话框,不必自己重复设计这些界面:
ColorDialog
:颜色选择对话框,选择的颜色通过Color
属性暴露出来,它是System.Drawing.Color
类型的FolderBrowserDialog
:允许用户选择文件夹,选择的文件夹以字符串类型的SelectedPath
属性暴露FontDialog
:字体选择对话框,选择的字体以Font
属性暴露,它是System.Drawing.Font
类型的OpenFileDialog
和SaveFileDialog
:用于打开文件和保存文件的对话框,选择的文件名称以FileName
属性暴露PageSetupDialog
、PrintDialog
和PrintPreviewDialog
:打印相关的对话框,将在后续章节介绍在设计器中,我们可以直接将对话框控件拖拽到主窗体上,这样设计器会自动在InitializeComponent
方法中帮我们实例化Dialog对象,我们使用this.xxxDialog.ShowDialog(this)
即可展示该对话框;如果不使用设计器,我们手动使用new
创建对话框实例也是可以的。下面例子代码中,我们在按钮点击时显示颜色选择对话框,当用户点击对话框的确定按钮时,输出用户选择的颜色。
private void btnShowDialog_Click(object sender, EventArgs e)
{
DialogResult dialogResult = colorDialog.ShowDialog(this);
if (dialogResult == DialogResult.OK)
{
MessageBox.Show("You selected color: " + colorDialog.Color.ToString());
}
}
ShowDialog()
方法会创建模态对话框,当用户操作对话框时,代码会阻塞在该方法上,直到用户点击确定或将对话框关闭,用户操作的结果由ShowDialog()
方法返回,而其它属性通常由对话框对象本身暴露出来,例如ColorDialog
中用户选择的颜色就可以通过colorDialog.Color
属性访问,这也是模态对话框的最佳使用方法。
自定义对话框其实和创建新的窗体没有任何区别,只是在模态对话框中,我们需要返回DialogResult
枚举值来告知主窗体对话框的关闭行为。下面例子中,我们使用Visual Studio设计器创建了一个窗体MyDialog
,它包含了一个登录界面,我们将以模态对话框的形式使用这个窗体。
登录界面的代码如下,其中我们定义了两个属性Username
和Password
,它们可被调用对话框的主窗体访问。btnOk_Click()
和btnCancel_Click()
方法分别处理用户点击确定和取消时的对话框行为,前一节中我们知道调用Close()
方法可以关闭窗体,但模态对话框不需要显式的被关闭,我们设置this.DialogResult
属性,模态对话框会自动关闭,这个属性的默认值是DialogResult.None
,当用户点击确定时我们将其设置为DialogResult.OK
,点击取消时设置为DialogResult.Cancel
。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DemoWinform
{
public partial class MyDialog : Form
{
public string Username
{
get
{
return tbUsername.Text;
}
}
public string Password
{
get
{
return tbPassword.Text;
}
}
public MyDialog()
{
InitializeComponent();
}
private void btnOk_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
}
private void btnCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
}
}
}
在主窗体中,我们使用如下代码调用这个模态对话框。
private void btnShowMyDialog_Click(object sender, EventArgs e)
{
MyDialog myDialog = new MyDialog();
DialogResult dialogResult = myDialog.ShowDialog(this);
if (dialogResult == DialogResult.OK)
{
MessageBox.Show("Your Input: " + myDialog.Username + " " + myDialog.Password);
}
}
我们先实例化了窗体对象,然后使用ShowDialog()
方法以模态形式显示对话框,此时代码将阻塞并等待用户操作结束,当用户关闭对话框时dialogResult
将被返回,如果这个值是DialogResult.OK
那么我们认为用户点击了确定按钮,我们通过访问对话框实例的公开属性获取用户输入的内容。
至于非模态对话框,主窗体也通常采用访问对话框暴露的公开属性来读取用户的输入,但非模态对话框中我们就不需要使用DialogResult
了,关闭非模态对话框时,我们仍需要使用Close()
方法。
Dialog对话框常用于表单的填写,这就涉及到了表单字段的验证问题。比较简单粗暴的表单验证方式是用户点击确定按钮时判断输入是否符合要求,如果不符合要求就不关闭对话框并给出提示,不过Winform其实内置了一套表单验证机制,能够更细粒度的控制验证字段和界面行为。
在输入框上有一个CausesValidation
属性,它被默认置为true
,这意味着这个输入框会在失去焦点时触发表单验证。此外我们可以看到输入框有Validating
和Validated
两个事件,当输入框失去焦点时Validating
事件将被触发检查输入内容,如果检查通过Validated
将被触发进行一些后处理逻辑。下面例子代码中我们在tbUsername
这个输入框控件上绑定了Validating
事件实现表单校验,如果输入内容为空,表单校验不通过,我们将弹出MessageBox
给出提示。
private void tbUsername_Validating(object sender, CancelEventArgs e)
{
if (string.IsNullOrEmpty(tbUsername.Text))
{
MessageBox.Show("请输入用户名");
e.Cancel = true;
}
}
当表单校验不通过时,我们设置了CancelEventArgs
的Cancel
属性为true
,这一操作会阻止界面上的焦点离开输入框,也就是说此时用户必须在这个输入框里填写内容,否则输入焦点无法离开。
前面例子中,对于字段校验时,我们直接弹出了一个MessageBox
,这其实是很不友好的,如果界面上表单的字段很多,MessageBox
并不与表单字段直接相关联,用户很可能搞不清楚究竟是哪个字段填错了。实际开发中,对于表单校验建议使用ErrorProvider
。在Visual Studio的界面设计器内,我们可以将ErrorProvider
拖放到界面上。
当tbUsername
输入框触发Validating
事件时,我们可以调用errorProvider.SetError()
方法,它的第1个参数是展示错误状态的控件,第2个参数是错误信息,通过第1个参数错误状态可以和输入控件关联在一起,将错误状态展示在对应的输入控件附近。
private void tbUsername_Validating(object sender, CancelEventArgs e)
{
string error = null;
if (string.IsNullOrEmpty(tbUsername.Text))
{
error = "请输入用户名";
e.Cancel = true;
}
errorProvider.SetError(tbUsername, error);
}
代码中,CancelEventArgs
的Cancel
属性被设置为true
意味着将取消当前控件的验证过程,防止焦点从该控件移开。此时当我们的输入不符合要求时,错误提示界面如下。
前面的表单字段验证中,我们的输入框验证是在失去焦点时触发的,但失去焦点触发就意味着输入框必须先获得焦点,如果用户根本没有触碰这个控件,那么表单验证永远不会触发。因此在对话框中用户点击确定按钮时,我们通常还需要手动触发对话框内所有输入框的验证。在Form对象内,我们可以调用ValidateChildren()
方法,它可以调用当前窗体的所有子控件并触发表单校验。
private void btnOk_Click(object sender, EventArgs e)
{
if (ValidateChildren())
{
DialogResult = DialogResult.OK;
}
}
当btnOk_Click()
被按钮点击触发时,当前对话框窗体的所有输入控件都会被触发Validating
事件(包括嵌套的子控件),任何一个输入控件的e.Cancel
被设置为true
都意味着表单验证失败,ValidateChildren()
方法将返回false
。ValidateChildren()
来自Control
类,这意味着几乎所有的控件都支持ValidateChildren()
方法,如果我们想要单独验证一组控件,我们可以将其加入容器控件,表单验证时,调用容器控件的ValidateChildren()
即可单独对这一组输入控件进行表单验证。
注:触发整体验证时,一个推荐的做法是将对话框Form
对象的AutoValidate
属性设置为EnableAllowFocusChange
,允许焦点离开验证不通过的输入框。默认情况下这个属性值是EnablePreventFocusChange
,它会阻止焦点离开验证不通过的输入框,在整体验证中,这让整个表单焦点转移的逻辑显得奇怪。