多功能消息对话框






4.10/5 (9投票s)
一个用于多功能 Windows 消息对话框的 C# 类

引言
本文将介绍一个用于多功能 Windows 消息对话框的 C# 类,该类具有自定义按钮、文本框、复选框和进度条指示器的功能。 .NET 提供的 MessageBox
对于一般用途来说相当灵活,但有时需要“继续”或“跳过”之类的按钮,而不是为每种类型制作自定义对话框,因此开发了这个类。随着时间的推移,该类还添加了非模态进度条指示器(可选择取消或继续),以及定义和获取复选框和文本框输入的能力。控件会自动定位,并在需要更多空间时扩展窗口,并且可以定义按钮、复选框和文本框的默认值。
Using the Code
类 CMsgDlg
派生自 Windows Form
。一些成员变量可能需要一些解释。回想一下,Windows Form MessageBox
用于使用通常的“确定”、“取消”、“是”、“否”按钮,并且点击的按钮结果返回到 MessageBoxButtons
对象。由于这个新类具有可自定义的按钮,因此必须使用替代方法来返回哪个按钮被点击。添加到窗体的按钮由 string
定义,被点击的按钮作为该 string
返回。对话框可以定义多个按钮,可以通过将按钮文本的第一个字符作为转义字符(‘\xd
’)来设置默认按钮。使用复选框或文本框调用的对话框返回为 List<string>
。要将复选框设置为选中状态(默认不选中),可以在定义复选框时使用转义字符‘\xc
’。这里使用的转义值没有什么特别之处,但它们必须是按钮文本中不太可能使用的内容,这样才能被识别并从 string
中剥离,然后在窗体中使用。
private List<Button> _buttons = new List<Button>(); // Buttons list
private string _btnResult; // Button clicked
private List<string> _lResults = new List<string>(); // Return list
public const char escDef = '\xd'; // Default button
public const char escChk = '\xc'; // Default check
自定义按钮对话框
要显示带有自定义按钮的对话框,可以调用此类成员
public string ShowDialog(string text, string caption, string[] btnText) {...
其中 text
是对话框中显示的文本,caption
是窗体的标题,而 btnText
字符串数组定义了按钮标签。
例如,一个带有三个按钮“筛选”、“跳过”和“取消”的对话框可以如下调用。每个按钮将从左到右并排放在窗体上,顺序与调用时一致。请注意,被点击的按钮仅作为 string
返回。所以,例如,如果点击了“取消”按钮,则返回的 string
是“取消”。

string res = new CMsgDlg().ShowDialog("Warning - the chosen filter is non-standard.",
"Filter Warning", new string[] { "Filter", "Skip", "Cancel" });
if (res == "Cancel")
break; // Exit loop
else if (res == "Skip") continue; // Don’t filter and continue to next iteration
// ...else just Filter it
文本框对话框
要显示 textbox
对话框,可以调用此方法
public List<string> ShowDialogTextBox(string caption, string[] lbl_txtboxText,
string[] btnText = null) {...
lbl_txtboxText
是一个 string
数组,其中提示 string
是 lbl_txtboxText
的第一个元素(下面示例中的“输入单位:”),第二个元素是 textbox
的默认值(在此例中为空 string
)。第二个元素还用于定义 textbox
的宽度以及行数(通过使用‘\n
’)。

示例文本框对话框可以这样调用
List<string> astr = new CMsgDlg().ShowDialogTextBox("Units",
new string[] { "Enter units:", " " });
if (astr[0] == "Cancel") return;
string unit = astr[1].Trim(); // Entered string
返回的 List<string>
astr
的第一个元素(astr[0]
)是被点击的按钮,第二个元素(astr[1]
)是用户输入的 string
。请注意,lbl_txtboxText
是一个 string
数组,因此可以在窗体上显示多个文本框,数组的值在文本框标签和默认 string
之间交替出现,即文本框标签、默认字符串、文本框标签、默认字符串,依此类推。btnText
是可选的,如果未提供,则按钮默认为“确定”和“取消”,如上例所示。
通过在 textbox
默认值(第二个元素)中包含换行符(每增加一行就有一个‘\n
’)将生成一个多行 textbox
。下面显示的示例代码将生成一个两行 textbox
,默认文本为 defText
。添加了额外的空格以生成更宽的 textbox
。
List<string> lstr = new CMsgDlg().ShowDialogTextBox("Chart Edit", "Title:", defText + " \n ");
复选框对话框
checkbox
对话框的定义是
public List<string> ShowDialogCheckBox(string lblText, string caption, string[] chkBoxText,
string[] btnText = null) {...

带有 checkbox
的对话框可以这样调用
string chk_distr = (_bDistr ? CMsgDlg.escChk.ToString() : "") + "Distribution statement";
string chk_Sign = (_bSign ? CMsgDlg.escChk.ToString() : "") + "Sign document";
string chk_Show = (_bShow ? CMsgDlg.escChk.ToString() : "") + "Show report updates";
List<string> astr = new CMsgDlg().ShowDialogCheckBox(“Check all that apply.”, "Report Options",
new string[] { chk_distr, chk_Sign, chk_Show }, new string[] { "\xdOK", "Cancel" });
if (astr[0] == "Cancel") return;
_bDistr = (astr[1][0] == CMsgDlg.escChk); // Distr. statement
_bSign = (astr[2][0] == CMsgDlg.escChk); // Sign
_bShow = (astr[3][0] == CMsgDlg.escChk); // Show updates
前面的三个语句定义了 checkbox
标签以及它们是否应默认设置为“选中”状态。例如,第一个语句表示如果 bool
_bDistr
为 true
,则 escChk
(上面类中定义的转义字符)将作为第一个字符插入到文本标签“分发声明”中。成员函数会检查这一点,以了解它是否应将 checkbox
状态设置为“checked
”。如果不存在转义字符,则 checkbox
默认为未选中。返回的 List<string>
astr
的第一个元素 astr[0]
是被点击的按钮。如果不是“取消”按钮,则从这些剩余的 astr
元素的第一个字符中检索 checkbox
状态(转义字符);例如 astr[1][0]
、astr[2][0]
等。所以,查看上面的最后三个语句:如果 astr[1]
的第一个字符是 escChk
字符,则 _bDistr
为 true
,如果 astr[2]
的第一个字符是 escChk
字符,则 _bSign
为 true
,依此类推。字符串按照传递给方法的顺序返回。
进度条对话框
进度条对话框方法的定义是
public CMsgDlg ShowProgress(Form parent, string text, string caption, string btnText = "Cancel") {...

这将初始化并显示一个非模态对话框,用于在循环中使用,其中进度条会在循环的每次迭代中更新。请注意,父 Form
已传递给此函数。这纯粹是为了定位对话框,使其默认位于父窗体的中心,因为非模态对话框不会自动居中。btnText
是可选的,默认为仅“取消”按钮。
调用此方法的示例
CMsgDlg dlg = new CMsgDlg().ShowProgress(f1, "Running the Full Report script...", "Script");
int i = 0;
foreach (TreeNode node in chkNodes) { // For each checked node
if (dlg.Result((double)++i / chkNodes.Count) == "Cancel") goto Exit;
...
}
If (dlg != null) dlg.Close();
在调用 CMsgDlg().ShowProgress
实例化后,会保留 dlg
对象,以便在 foreach
循环中使用,以便访问类的 Result
方法来检查用户是否点击了“取消”按钮,并在循环的每次迭代中更新进度条。
Result
方法如下所示
//--------------------------------------------------------------------------
// Returns button result (for non-modal use) and updates progress by perc %
//--------------------------------------------------------------------------
public string Result(double perc, string text = "") {
try {
if (_prgBar != null) {
if (perc <= 1.0) perc *= 100; // Convert to percentage
_prgBar.Value = (int)(perc + 0.5);
}
if (text != "") _lbl.Text = text;
}
catch (Exception) { // Keep from crashing if passed invalid value
}
return _btnResult;
}
请注意,可以向其中传递一个可选的文本 string
,以将文本从初始文本(从 ShowProgress
调用时)更改为例如“% 完成”。perc
值在小于 1 时会被转换为百分比,并用于显示进度条的完成百分比。在上面的 foreach
循环中,此 perc
值作为计数器“i
”除以总迭代次数 chkNodes.Count
传递。计数器“i
”在每次循环通过时递增。try
块和“null
”的捕获可确保即使调用对话框出现错误并抛出异常,也不会中断调用者的循环。请注意,调用函数负责关闭对话框,例如在调用循环结束后。
即使这是一个非模态对话框,对话框也可以响应按钮点击。如果用户点击取消按钮,private
成员变量 _btnResult
将在 btn_Click
方法中设置。btn_Click
方法随后会调用 Cancel
方法,生成一个模态对话框,提示用户取消或继续。

Cancel
方法(如下所示)可以被调用来关闭对话框,或者(如果 bAsk = true
)在关闭之前提示。在提示模式之后,它首先使非模态对话框不可见,这有效地暂停了应用程序。它调用模态 ShowDialog
,添加一个“继续”按钮并更改文本消息,询问用户是否要取消操作。如果选择了“继续”,则从窗体中移除“继续”按钮,恢复原始文本消息,并再次使非模态对话框可见,从而允许应用程序继续并恢复循环。如果选择了“取消”,则“取消”将返回到 _btnResult
,该值由 Result
方法返回给循环中的调用者,然后循环退出。
请注意,在将窗体的可见属性设置为 false
并调用 ShowDialog
之后,模态对话框的外观与之前不可见的进度条对话框相同(它有“取消”按钮和进度条),因此只需要添加“继续”按钮。由于类尚未重新实例化,“this
”仍然引用当前窗体,即那个已设置为不可见的窗体。因此,它有效地表现得好像非模态对话框已更改为模态对话框。当窗体再次可见时,这将使其恢复为原来的非模态对话框。
//--------------------------------------------------------------------------
// Cancel handler for non-modal
//--------------------------------------------------------------------------
public void Cancel(bool bAsk = false) {
if (bAsk) { // Ask 1st
this.Visible = false; // Disables non-modal dlg
string lbl = _lbl.Text; // Save in case continue
if (ShowDialog("Cancel the current operation?", this.Text, "Continue")
== "Continue") { // Already has Cancel btn
_lbl.Text = lbl; // Restore
_buttons.Last().Dispose(); // Continue btn
_buttons.Remove(_buttons.Last());
this.Visible = true; // Enable non-modal again
Form1.TheForm().MsgDlg = this; // Restore for form - Close() nulls
return;
}
}
_lbl.Text = "Closing...";
_btnResult = "Cancel"; // Set to close on next Result() check
}
当窗体是非模态的时,它不一定总是有焦点,所以取决于循环中进行的计算量,它可能不会立即响应。该类还有一个 KeyDown
处理程序,用于查找 Esc 键,这通常比点击“取消”按钮响应更快。主应用程序也可以有一个 KeyDown
事件来查找 Esc 键,因此 Cancel
方法被声明为 public
,以便主应用程序的键处理程序也可以调用 Cancel
。主应用程序(Form1
)需要访问 CMsgDlg
对象(以调用 Cancel
),因此当 ShowProgress
首次调用时,Form1
中的一个 public global
变量被赋值为“this
”对象,这样它就可以访问 Cancel
方法。这个 Form1
全局变量在此 Cancel
方法中也被恢复,因为关闭模态对话框(在按钮点击之后)会将其设置为 null
。注意:如果未使用 Form1
代码,则当然可以将其注释掉或删除。
该类还包含一个 ShowDialog
方法(如下面声明所示),该方法可以使用同一窗体上的复选框和文本框,尽管目前它只设计用于将 checkbox
放在 textbox
之上。
public List<string> ShowDialog(string text, string caption, string[] btnText,
string[] chkBoxText = null, string[] txtboxText = null) {
将类与预先设计的窗体结合使用
尽管该类是为了独立处理大多数简单用户对话框而开发的,但通常仍需要设计更复杂的输入窗体,可能具有动态要求(即,在设计时未知)。例如,曾经需要一个对话框,其中文本框是动态添加的,这取决于在 Word 模板文档中找到的书签数量。因此,一个窗体是通过设计器定义的(而不是由该类创建),并且 AddTextBox
方法被设为 public
,以便可以多次从外部调用它,为设计窗体上的每个书签添加一个 textbox
。在这种情况下,由于未使用类的默认窗体,AddTextBox
需要知道要将 textbox
添加到哪个窗体上,因此有一个类的替代构造函数声明为:public
CMsgDlg(Control form)
。因此,为了更通用的使用,可以将 AddTextBox
和 AddCheckBox
方法定义为 public
,这样就可以使用带有窗体参数的替代构造函数调用这些方法(这样控件就可以从外部预先设计的窗口窗体添加),并且控件的定位仍然由 CMsgDlg
类处理。
结论
讨论了一个多功能消息对话框类,该类可以具有多个自定义标签按钮、复选框、文本框或进度条。该类的某些方法可用于将文本框或复选框添加到外部对话框,以便这些组件可以动态添加(即,在运行时)。该类绝非详尽无遗,无法满足所有消息对话框的功能,但希望它能为开发人员提供一个功能更强大的通用消息对话框的良好起点。