65.9K
CodeProject 正在变化。 阅读更多。
Home

多功能消息对话框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.10/5 (9投票s)

2018 年 4 月 17 日

CPOL

9分钟阅读

viewsIcon

13306

downloadIcon

290

一个用于多功能 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 数组,其中提示 stringlbl_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 _bDistrtrue,则 escChk(上面类中定义的转义字符)将作为第一个字符插入到文本标签“分发声明”中。成员函数会检查这一点,以了解它是否应将 checkbox 状态设置为“checked”。如果不存在转义字符,则 checkbox 默认为未选中。返回的 List<string> astr 的第一个元素 astr[0] 是被点击的按钮。如果不是“取消”按钮,则从这些剩余的 astr 元素的第一个字符中检索 checkbox 状态(转义字符);例如 astr[1][0]astr[2][0] 等。所以,查看上面的最后三个语句:如果 astr[1] 的第一个字符是 escChk 字符,则 _bDistrtrue,如果 astr[2] 的第一个字符是 escChk 字符,则 _bSigntrue,依此类推。字符串按照传递给方法的顺序返回。

进度条对话框

进度条对话框方法的定义是

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)。因此,为了更通用的使用,可以将 AddTextBoxAddCheckBox 方法定义为 public,这样就可以使用带有窗体参数的替代构造函数调用这些方法(这样控件就可以从外部预先设计的窗口窗体添加),并且控件的定位仍然由 CMsgDlg 类处理。

结论

讨论了一个多功能消息对话框类,该类可以具有多个自定义标签按钮、复选框、文本框或进度条。该类的某些方法可用于将文本框或复选框添加到外部对话框,以便这些组件可以动态添加(即,在运行时)。该类绝非详尽无遗,无法满足所有消息对话框的功能,但希望它能为开发人员提供一个功能更强大的通用消息对话框的良好起点。

© . All rights reserved.