将 C# 代码从控制台迁移到 Windows 表单应用程序
Migrate C# code from Console to Windows Form Application
我一直在使用这本书 "Programming in the Key of C#..." 学习 C#,这本书很好地帮助我理解了这门语言,但只涉及控制台程序。我准备继续开发我过去的编码项目的版本作为 Windows 表单应用程序,但特别是一个程序让我很沮丧。我开发了一个简单的电影问答程序,它使用数组来保存问题、答案选择和正确答案。它通过在控制台上显示问题、可能的答案并等待用户通过使用 Console.Readline() 分配响应来提供响应(基本上是 A、B、C 或 D)来工作。
现在我希望能够让用户通过选择 4 个按钮(A 到 D)中的 1 个来输入答案。根据我的旧代码,我不确定如何让程序等待用户单击其中一个按钮。我想我需要改变循环的性质,但我不知道如何改变。任何帮助将不胜感激。
这是我的控制台代码片段:
while (iAsked < 5)
{
iLocation = rand.Next(0, astrQuestions.GetLength(0));
if (list.Contains(iLocation))
rand.Next(0, astrQuestions.GetLength(0));
else
{
iAsked++;
list.Add(iLocation);
Console.WriteLine("Question {0}", iAsked);
Console.WriteLine("------------");
Console.WriteLine(astrQuestions[(iLocation)]);
Console.WriteLine(astrChoices[(iLocation)]);
Console.Write("Answer:");
iResponse = Console.ReadLine();
if (iResponse == astrAnswers[(iLocation)])
{
Console.WriteLine("Correct\n");
iPoints += 5;
iCorrect++;
}
else
{
Console.WriteLine("Incorrect\n");
}
}
从像控制台程序这样的以提示为中心的环境转移到像 Winforms 这样的事件驱动环境,是的……这肯定至少需要对“循环的性质”进行一些改变。 :)
也就是说,最新版本的 C# 提供了一种基于 async
/await
的方法,可以最大限度地减少从控制台迁移到 G[=84 时可能带来的一些文化冲击=].编写和使用 async
方法本身并不简单,但恕我直言,更简单的场景并不难理解。更重要的是,因为它允许您以更直接命令式的方式构建代码,类似于在控制台程序中使用的方式,因此非常值得与 Winforms 一起学习。
在您的特定场景中,您需要处理两件不同的事情:提示用户和接收用户的输入。
由于事件驱动系统的工作方式,您需要将这些任务分开。但是 .NET 有一个 class、TaskCompletionSource
,我们可以用它来将两者粘合在一起,即使它们在不同的地方结束。
首先,当用户启动进程时会发生什么?据推测,您将拥有一个表单,该表单上有一个按钮(或可能是一个菜单项),当 clicked/selected 时,它会启动整个过程。这可能看起来像这样:
private TaskCompletionSource<bool> _completionSource;
private async void button1_Click(object sender, EventArgs e)
{
int[] questionIndexes = ShuffleQuestions();
for (int iAsked = 0; iAsked < 5; iAsked++)
{
textBoxQuestionNumber.Text = string.Format("Question {0}", iAsked);
textBoxQuestion.Text = astrQuestions[questionIndexes[iAsked]];
textBoxChoices.Text = astrChoices[questionIndexes[iAsked]];
_completionSource =
new TaskCompletionSource<bool>(astrAnswers[questionIndexes[iAsked]]);
button2.Enabled = true;
bool result = await _completionSource.Task;
MessageBox.Show(result ? "Correct" : "Incorrect");
if (result)
{
iPoints += 5;
iCorrect++;
}
button2.Enabled = false;
_completionSource = null;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (_completionSource != null)
{
_completionSource.SetResult(
textBoxUserAnswer.Text == (string)_completionsSource.Task.AsyncState);
}
}
(我已经将你上面的问题选择逻辑更改为更有效的东西,假设你有一个 ShuffleQuestions()
方法。有关如何实现该方法的详细信息,请参阅 Is using Random and OrderBy a good shuffle algorithm?)。
上面的代码所做的是,响应用户点击 button1
按钮(大概有“开始”之类的文本),执行一个与您在你的控制台程序。两个主要区别是:
- 在您的控制台程序中,您使用
Console.WriteLine()
向用户显示文本,这里我展示了在您的表单中使用 TextBox
控件来显示相同的文本。
- 在你的控制台程序中,你使用
Console.ReadLine()
来接收用户的输入,这个循环创建一个 TaskCompletionSource
对象以供完全不同的方法使用。使用 button2
按钮(大概有“检查答案”之类的文本)执行的该方法将读取用户在文本框中输入的文本(在这里,我给它起了名字 textBoxUserAnswer
),将其与问题的正确答案进行比较(已通过 Task
的 Task
的 AsyncState
属性 由 AsyncState
属性 提供给此方法=14=] 我创建的对象),并将 Task
的结果设置为 true
或 false
,具体取决于用户是否得到正确答案。
上面的棘手部分是“幕后”,第一种方法 实际上 returns 一旦完成第一个问题的文本填写并到达循环中的 await
语句。编译器重写了整个方法以促进这一点。
当 button2
被压入并设置 Task
的结果时,框架会知道继续执行它在 await
语句处中断的第一个方法,继续你的循环。
此顺序一直持续到用户回答完所有问题。
关于 UI 的一些最后说明:
- 我到处都使用
TextBox
来进行用户输入和输出。当然,还有其他显示文本的方法。此外,TextBox
的默认状态是单行 read/write 文本。但是为了向用户显示,您可能会发现将 TextBox
的 ReadOnly
属性 设置为 true 会更好(即防止用户不小心更改文本),and/or 您更喜欢将 Multiline
属性 设置为 true
(即显示多于一行的文本)。
- 以上还假定
button2
按钮的 Enabled
属性 的初始状态为 false
。 IE。在上面的第一种方法在适当的时间明确启用该按钮之前,无法单击该按钮。
我一直在使用这本书 "Programming in the Key of C#..." 学习 C#,这本书很好地帮助我理解了这门语言,但只涉及控制台程序。我准备继续开发我过去的编码项目的版本作为 Windows 表单应用程序,但特别是一个程序让我很沮丧。我开发了一个简单的电影问答程序,它使用数组来保存问题、答案选择和正确答案。它通过在控制台上显示问题、可能的答案并等待用户通过使用 Console.Readline() 分配响应来提供响应(基本上是 A、B、C 或 D)来工作。
现在我希望能够让用户通过选择 4 个按钮(A 到 D)中的 1 个来输入答案。根据我的旧代码,我不确定如何让程序等待用户单击其中一个按钮。我想我需要改变循环的性质,但我不知道如何改变。任何帮助将不胜感激。
这是我的控制台代码片段:
while (iAsked < 5)
{
iLocation = rand.Next(0, astrQuestions.GetLength(0));
if (list.Contains(iLocation))
rand.Next(0, astrQuestions.GetLength(0));
else
{
iAsked++;
list.Add(iLocation);
Console.WriteLine("Question {0}", iAsked);
Console.WriteLine("------------");
Console.WriteLine(astrQuestions[(iLocation)]);
Console.WriteLine(astrChoices[(iLocation)]);
Console.Write("Answer:");
iResponse = Console.ReadLine();
if (iResponse == astrAnswers[(iLocation)])
{
Console.WriteLine("Correct\n");
iPoints += 5;
iCorrect++;
}
else
{
Console.WriteLine("Incorrect\n");
}
}
从像控制台程序这样的以提示为中心的环境转移到像 Winforms 这样的事件驱动环境,是的……这肯定至少需要对“循环的性质”进行一些改变。 :)
也就是说,最新版本的 C# 提供了一种基于 async
/await
的方法,可以最大限度地减少从控制台迁移到 G[=84 时可能带来的一些文化冲击=].编写和使用 async
方法本身并不简单,但恕我直言,更简单的场景并不难理解。更重要的是,因为它允许您以更直接命令式的方式构建代码,类似于在控制台程序中使用的方式,因此非常值得与 Winforms 一起学习。
在您的特定场景中,您需要处理两件不同的事情:提示用户和接收用户的输入。
由于事件驱动系统的工作方式,您需要将这些任务分开。但是 .NET 有一个 class、TaskCompletionSource
,我们可以用它来将两者粘合在一起,即使它们在不同的地方结束。
首先,当用户启动进程时会发生什么?据推测,您将拥有一个表单,该表单上有一个按钮(或可能是一个菜单项),当 clicked/selected 时,它会启动整个过程。这可能看起来像这样:
private TaskCompletionSource<bool> _completionSource;
private async void button1_Click(object sender, EventArgs e)
{
int[] questionIndexes = ShuffleQuestions();
for (int iAsked = 0; iAsked < 5; iAsked++)
{
textBoxQuestionNumber.Text = string.Format("Question {0}", iAsked);
textBoxQuestion.Text = astrQuestions[questionIndexes[iAsked]];
textBoxChoices.Text = astrChoices[questionIndexes[iAsked]];
_completionSource =
new TaskCompletionSource<bool>(astrAnswers[questionIndexes[iAsked]]);
button2.Enabled = true;
bool result = await _completionSource.Task;
MessageBox.Show(result ? "Correct" : "Incorrect");
if (result)
{
iPoints += 5;
iCorrect++;
}
button2.Enabled = false;
_completionSource = null;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (_completionSource != null)
{
_completionSource.SetResult(
textBoxUserAnswer.Text == (string)_completionsSource.Task.AsyncState);
}
}
(我已经将你上面的问题选择逻辑更改为更有效的东西,假设你有一个 ShuffleQuestions()
方法。有关如何实现该方法的详细信息,请参阅 Is using Random and OrderBy a good shuffle algorithm?)。
上面的代码所做的是,响应用户点击 button1
按钮(大概有“开始”之类的文本),执行一个与您在你的控制台程序。两个主要区别是:
- 在您的控制台程序中,您使用
Console.WriteLine()
向用户显示文本,这里我展示了在您的表单中使用TextBox
控件来显示相同的文本。 - 在你的控制台程序中,你使用
Console.ReadLine()
来接收用户的输入,这个循环创建一个TaskCompletionSource
对象以供完全不同的方法使用。使用button2
按钮(大概有“检查答案”之类的文本)执行的该方法将读取用户在文本框中输入的文本(在这里,我给它起了名字textBoxUserAnswer
),将其与问题的正确答案进行比较(已通过Task
的Task
的AsyncState
属性 由AsyncState
属性 提供给此方法=14=] 我创建的对象),并将Task
的结果设置为true
或false
,具体取决于用户是否得到正确答案。
上面的棘手部分是“幕后”,第一种方法 实际上 returns 一旦完成第一个问题的文本填写并到达循环中的 await
语句。编译器重写了整个方法以促进这一点。
当 button2
被压入并设置 Task
的结果时,框架会知道继续执行它在 await
语句处中断的第一个方法,继续你的循环。
此顺序一直持续到用户回答完所有问题。
关于 UI 的一些最后说明:
- 我到处都使用
TextBox
来进行用户输入和输出。当然,还有其他显示文本的方法。此外,TextBox
的默认状态是单行 read/write 文本。但是为了向用户显示,您可能会发现将TextBox
的ReadOnly
属性 设置为 true 会更好(即防止用户不小心更改文本),and/or 您更喜欢将Multiline
属性 设置为true
(即显示多于一行的文本)。 - 以上还假定
button2
按钮的Enabled
属性 的初始状态为false
。 IE。在上面的第一种方法在适当的时间明确启用该按钮之前,无法单击该按钮。