使用对话框获取用户输入 - 第 1 部分






4.14/5 (9投票s)
一篇关于使用自定义对话框获取用户输入的文章。
引言
本文旨在揭开使用对话框收集用户输入的神秘面纱。它并非旨在详尽无遗,而是针对新程序员或初学者。第二部分正在编写中,它将处理“复杂”数据,即多条数据。
背景
最近,CP Lounge 的一位常客发帖称他在理解这个主题方面遇到了困难,并建议写一篇文章是个好主意。这是我尝试满足他的请求。
新程序员似乎感到困惑的一件事是如何通过对话框获取用户输入。一旦你理解了这个大秘密,这实际上非常简单。
有很多方法可以做到这一点,随附的代码示例只演示了其中一些。
示例解决方案包含三个项目。
所有这三个都处理“简单”用户输入,即只获取一条信息。
对话框表单
所有文件名以“Dialog.cs”结尾的表单和文件InputBox.cs至少具有以下属性和组件
通用组件
- 一个名为“btnOK”的按钮,其 DialogResult 属性设置为
DialogResult.OK
- 一个名为“btnCancel”的按钮,其 DialogResult 属性设置为
DialogResult.Cancel
表单属性
- FormBorderStyle 设置为
FormBorderStyle.FixedDialog
- StartPosition 设置为
FormStartPosition.CenterParent
(我更喜欢这种方式,尽管它不是必需的) - AcceptButton 设置为“btnOK”(这允许用户按回车键接受更改)
- CancelButton 设置为“btnCancel”(这允许用户按 Esc 键取消更改)
- ControlBox、MaximizeBox 和 MinimizeBox 都设置为
false
(可选) - 你还可以将 ShowInTaskbar 设置为
false
(顾名思义)
VeryVerySimpleDialog 示例
这个项目演示了我认为最简单但也最不“正确”的方法。
这是来自调用表单的代码,它使对话框显示并在对话框关闭时处理用户输入
private void btnEdit_Click(object sender, EventArgs e)
{
// Create an instance of the dialog
VeryVerySimpleInputDialog input = new VeryVerySimpleInputDialog();
// Show the dialog modally, testing the result.
// If the user cancelled, skip past this block.
if (input.ShowDialog() == DialogResult.OK)
{
// The user clicked OK or pressed Return Key
// so display their input in this form.
"ex1ErrorLine">this.txtInputOutput.Text = input.txtInput.Text;
}
// Check to see if the dialog is still hanging around
// and, if so, get rid of it.
if (input != null)
{
input.Dispose();
}
}
在注释的帮助下,代码应该是不言自明的。
这是对话框
正如你所看到的,它只有一个用于用户输入的控件,一个 TextBox。
如果用户按下 OK 键,则应该返回用户在此 TextBox 中键入的文本。
如果你只复制此对话框的视觉外观并对对话框不做任何其他操作以启用检索用户输入,那么当你尝试编译时,你将收到错误。
VeryVerySimpleDialog.VeryVerySimpleInputDialog.txtInput is inaccessible due to its protection level
错误行号将指向上面调用表单代码中的这一行
"#ex1errorLine">this.txtInputOutput.Text = input.txtInput.Text;
为了允许调用表单访问用户的文本(即实现秘密),最快的方法是,在对话框设计器中选择 TextBox,然后在属性窗口中向下滚动到 Design
类别,并将 Modifiers
属性更改为除 private
或 protected
以外的任何值。你现在会发现你的项目将编译并运行。但是,你可能会遇到其他人对这种方法的一些问题。你看,它至少部分地打破了面向对象的一条基本规则,“封装”。这不是深入解释的地方。请在 MSDN 中查找,或在 Google 上搜索以获取更多详细信息。但是,它确实有效,通过这样做,它证明了秘密是对话框问题的解决方案。
下一个示例解决了封装问题。
自己研究的事项
- 封装
VerySimpleDialog 示例
这个例子与上一个非常相似,对话框稍微复杂一些,因为它允许你向用户显示提示并建议一些输入作为默认值。为了实现这一点,主窗体也稍微复杂一些。此外,如上所述,它解决了封装问题。
主窗体代码与上一个示例基本相同,只是由于需要根据选中的样式单选按钮创建不同类型的对话框而变得复杂。我们稍后会讨论这个问题。
首先,这是带有所有选项的对话框
注意提示和默认输入。因此,如果用户现在单击“确定”,输入将是“Hello World!”。
对话框的代码解决了封装问题
public VerySimpleInputDialog()
{
InitializeComponent();
this.lblPrompt.Text = "";
}
public VerySimpleInputDialog(string prompt)
: this()
{
if (!string.IsNullOrEmpty(prompt))
{
this.lblPrompt.Text = prompt;
}
}
public VerySimpleInputDialog(string prompt, string defaultInput)
: this()
{
if (!string.IsNullOrEmpty(prompt))
{
this.lblPrompt.Text = prompt;
}
this.txtInput.Text = this.defaultValue = defaultInput;
}
请注意,有三个构造函数。标准构造函数,用于不需要提示或默认文本的情况。一个带有一个字符串参数的构造函数,用于只想要提示的情况。第三个构造函数带有两个字符串参数,用于需要提示和默认文本的情况。后两个构造函数调用标准构造函数(: this()
行)以执行标准组件初始化。
请注意
this.txtInput.Text = this.defaultValue = defaultInput;
这一行。不是因为它做了什么特别或巧妙的事情,而是因为它将默认文本的副本保存在私有成员 defaultValue
中,并将其复制到输入 TextBox 中。
defaultValue 声明
private string defaultValue = string.Empty;
当用户按下“取消”按钮或按下 Esc 键时,将使用 defaultValue
成员。这是代码
private void btnCancel_Click(object sender, EventArgs e)
{
// Don't bother if no default
if (!string.IsNullOrEmpty(this.defaultValue))
{
this.txtInput.Text = this.defaultValue;
}
}
如果用户出于任何原因决定取消,则会将存储的 defaultValue
(如果有)复制到输入 TextBox 中,以供调用表单使用。
这是解决封装困境的代码
public string UserInput
{
get
{
return this.txtInput.Text;
}
}
它是一个公共只读属性,在调用时返回输入 TextBox 的内容。这种方法是可行的,因为它只允许访问 TextBox 的内容,而且是只读访问。前面的示例允许访问整个 TextBox,并且有了对象的引用,你可以做任何你想做的事情,例如 Dispose()
它,这可能是灾难性的。
现在是主窗体
“提示”文本框允许你指定将出现在对话框中、输入文本框正上方的提示。
“默认值”文本框允许你指定一些文本,如果用户没有输入任何内容,则将使用该文本。
“调用样式”组框中的三个单选按钮允许你指定对话框的外观。如果选择“标准”选项,对话框将与上一个示例中的对话框非常相似。如果选择“提示”选项,对话框将包含主窗体的提示。如果选择“默认”选项,对话框将同时包含提示和默认文本。
这是来自调用表单的代码,它使对话框显示并在对话框关闭时处理用户输入
private void btnEdit_Click(object sender, EventArgs e)
{
// Show Dialog in the style for the checked RadioButton
if (rbtnStandard.Checked)
{
// Make instance of dialog
VerySimpleInputDialog input = new VerySimpleInputDialog();
// Show dialog modally. Checking return value.
if (input.ShowDialog() == DialogResult.OK)
{
// User - he/she say OK. Show results
this.txtResult.Text = input.UserInput;
}
// Trash the dialog, if it is still hanging around
if (input != null)
{
input.Dispose();
}
}
else if (rbtnPrompt.Checked)
{
// Make instance of dialog, with prompt
VerySimpleInputDialog input =
new VerySimpleInputDialog(this.txtPrompt.Text);
// Show dialog modally. Checking return value.
if (input.ShowDialog() == DialogResult.OK)
{
// User - he/she say OK. Show results
this.txtResult.Text = input.UserInput;
}
// Trash the dialog, if it is still hanging around
if (input != null)
{
input.Dispose();
}
}
else if (rbtnDefault.Checked)
{
// Make instance of dialog, with prompt and default text
VerySimpleInputDialog input =
new VerySimpleInputDialog(this.txtPrompt.Text, this.txtDefault.Text);
// No need to test for OK, because you are at the least
// getting the default result.
input.ShowDialog();
this.txtResult.Text = input.UserInput;
// Trash the dialog, if it is still hanging around
if (input != null)
{
input.Dispose();
}
}
}
请注意,对于每个选项,设置结果文本框内容的行都使用公共只读 UserInput
属性来获取数据。
自己研究的事项
- 属性
- 只读属性
SimpleDialog 示例
此示例与前两个示例的不同之处主要在于对话框的创建方式。你可能还记得,在这些示例中,是由调用窗体创建和控制对话框的显示。在此示例中,该职责被委托给对话框本身。
这是对话框(InputBox 的一个实例)
对话框本身的唯一真正区别是增加了设置对话框标题以及根据创建时指定的类型验证输入的能力。关于 InputBox 代码首先要注意的是,默认构造函数的访问权限已设置为 private。这阻止了任何人在不使用 ShowDialog() 方法之一的情况下创建实例,内置的 MessageBox 使用类似策略与 MessageBox.Show()
静态方法。ShowDialog()
方法有几个重载版本,以允许参数的不同组合。我没有包含所有可能组合的方法,那会让你抓狂,只包含了我在设计对话框时认为有用的那些。如果以后觉得有必要,很容易添加更多,但一旦对话框被任何应用程序使用,删除其中任何一个都可能是致命的。
这是其中两个方法的代码
public static DialogResult ShowDialog(out string input)
{
return InputBox.ShowDialog(null, null, null, out input, InputBoxResultType.Any);
}
public static DialogResult ShowDialog(string caption, string prompt, string defaultValue,
out string input, InputBoxResultType validationType)
{
// Create an instance of the InputBox class.
InputBox inputBox = new InputBox();
// Set the members of the new instance
// according to the value of the parameters
if (string.IsNullOrEmpty(caption))
{
inputBox.Text = Application.ProductName;
}
else
{
inputBox.Text = caption;
}
if (!string.IsNullOrEmpty(prompt))
{
inputBox.lblPrompt.Text = prompt;
}
if (!string.IsNullOrEmpty(defaultValue))
{
inputBox.defaultValue = inputBox.txtInput.Text = defaultValue;
}
// Calculate size required for prompt message and adjust
// Label and dialog size to fit.
Size promptSize = inputBox.lblPrompt.CreateGraphics().MeasureString(prompt,
inputBox.lblPrompt.Font,
inputBox.ClientRectangle.Width - 20).ToSize();
// a little wriggle room
if (promptSize.Height > inputBox.lblPrompt.Height)
{
promptSize.Width += 4;
promptSize.Height += 4;
}
inputBox.lblPrompt.Width = inputBox.ClientRectangle.Width - 20;
inputBox.lblPrompt.Height = Math.Max(inputBox.lblPrompt.Height, promptSize.Height);
int postLabelMargin = 2;
if ((inputBox.lblPrompt.Top + inputBox.lblPrompt.Height + postLabelMargin) >
inputBox.txtInput.Top)
{
inputBox.ClientSize = new Size(inputBox.ClientSize.Width,
inputBox.ClientSize.Height +
(inputBox.lblPrompt.Top + inputBox.lblPrompt.Height + postLabelMargin -
inputBox.txtInput.Top));
}
else if ((inputBox.lblPrompt.Top + inputBox.lblPrompt.Height + postLabelMargin) <
inputBox.txtInput.Top)
{
inputBox.ClientSize = new Size(inputBox.ClientSize.Width,
inputBox.ClientSize.Height -
(inputBox.lblPrompt.Top + inputBox.lblPrompt.Height + postLabelMargin -
inputBox.txtInput.Top));
}
// Ensure that the value of input is set
// There will be a compile error later if not
input = string.Empty;
// Declare a variable to hold the result to be
// returned on exitting the method
DialogResult result = DialogResult.None;
// Loop round until the user enters
// some valid data, or cancels.
while (result == DialogResult.None)
{
result = inputBox.ShowDialog();
if (result == DialogResult.OK)
{
// if user clicked OK, validate the entry
input = inputBox.txtInput.Text;
// Only test if specific type is required
if (validationType != InputBoxResultType.Any)
{
// If the test fails - Invalid input.
if (!inputBox.Validate(validationType))
{
// Set variables ready for another loop
input = string.Empty;
// result to 'None' to ensure while loop
// repeats
result = DialogResult.None;
// Let user know there is a problem
MessageBox.Show(inputBox, "The data entered is not a valid " +
validationType.ToString() + ".");
// Set the focus back to the TextBox
inputBox.txtInput.Select();
}
}
}
else
{
// User has cancelled.
// Use the defaultValue if there is one, or else
// an empty string.
if (string.IsNullOrEmpty(inputBox.defaultValue))
{
input = string.Empty;
}
else
{
input = inputBox.defaultValue;
}
}
}
// Trash the dialog if it is hanging around.
if (inputBox != null)
{
inputBox.Dispose();
}
// Send back the result.
return result;
}
上面方法中的第二个是唯一真正执行操作的方法。其他方法,例如上面的第一个,只是简单地调用最后一个方法,用合理的值替换其自己的参数列表中缺少的任何必需参数。所有 ShowDialog()
方法都声明为
public static DialogResult ShowDialog
使其工作的关键是 static
访问修饰符,它使这种方法得以工作。它的使用意味着你不需要创建类的实例即可使用它。声明为“static”的方法属于类型,而不属于类型的特定实例。因此,如果你创建一个名为 Banana
的类,它有一个名为 SoldOut
的公共静态方法,如下所示
public class Banana
{
public static bool SoldOut()
{
return true;
}
}
SoldOut
方法的调用方式如下
bool anyBananas = Banana.SoldOut();
而不是你习惯的方式
Banana newBanana = new Banana();
if (newBanana.SoldOut())
{
// go Aaaaaaaaaaaaaaaaaagh
}
这将导致编译错误。
在本文的代码示例中,你会看到许多
if (string.IsNullOrEmpty(aString))
{
etc.
}
IsNullOrEmpty
是 string
类型的一个 static
成员。它不是 string 的特定实例的成员。
此示例通过使用 out
参数实现了秘密。MSDN 中有很多这种类型的参数示例。这是主窗体用于显示对话框和使用输入的数据的代码
private void btnTest_Click(object sender, System.EventArgs e)
{
string userInput = string.Empty;
InputBox.ShowDialog(txtCaption.Text, txtPrompt.Text, txtdefault.Text,
out userInput, this.validationType);
// display results
if (string.IsNullOrEmpty(userInput))
{
this.txtResult.Text = "Input cancelled by user.";
}
else
{
this.txtResult.Text = userInput;
}
}
主窗体调用 InputBox.ShowDialog(.....)
后,它使用以下行利用用户输入
this.txtResult.Text = userInput;
你可以看到 userInput
在方法开始时声明,然后作为 ShowDialog()
方法的 input
参数传递。我已经将 userInput
初始化为空字符串,但这不是必需的。如果你使用 ref
而不是 out
,并且你可以这样做,那么初始化将是必需的。
我更喜欢在可能的情况下使用这种方法来处理对话框,因为它最接近 OOP 编程的理想。然而,有时用这种方式做某些事情会变得过于复杂,所以我回到了示例 2 中使用的方法。我尽量不使用示例 1 中的方法。
这个对话框最初是我在尝试学习 C# 时使用 VS2003 和 .NET 1.1 编写的。从那时起,我在某些方面对其进行了一些清理(例如,我已将验证代码移至单独的方法),最初它包含在静态 ShowDialog()
方法中,以便将来更容易替换它而不会破坏我使用此对话框的任何应用程序。当时我写它的时候,我不知道 ErrorProvider 组件或 TextBox 可用的 Validating 事件,但这按原样工作,所以我没有过多干预。也许有一天会!
自己研究的事项
- static 访问修饰符
- out 关键字
- ErrorProvider 组件
- Validating 事件
Using the Code
示例中的代码应该适用于所有 C# 版本。(对于 VS2008 之前的版本,你需要剪切和粘贴代码。)其中大部分直接来自我使用 VS2003 和 .NET 1.1 编写的项目,尽管随附的解决方案和项目文件是用 VS2008 和 .NET 3.5 生成的。对于所有包含的项目,编译并运行后,初始窗体都有一个相当居中的按钮。单击此按钮会弹出一个对话框,可以在其中输入或修改数据。单击对话框上的“确定”或“取消”按钮会关闭对话框并将新信息传递回主窗体。
历史
这是本文的第一个发布版本。