将 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),将其与问题的正确答案进行比较(已通过 TaskTaskAsyncState 属性 由 AsyncState 属性 提供给此方法=14=] 我创建的对象),并将 Task 的结果设置为 truefalse,具体取决于用户是否得到正确答案。

上面的棘手部分是“幕后”,第一种方法 实际上 returns 一旦完成第一个问题的文本填写并到达循环中的 await 语句。编译器重写了整个方法以促进这一点。

button2 被压入并设置 Task 的结果时,框架会知道继续执行它在 await 语句处中断的第一个方法,继续你的循环。

此顺序一直持续到用户回答完所有问题。

关于 UI 的一些最后说明:

  • 我到处都使用 TextBox 来进行用户输入和输出。当然,还有其他显示文本的方法。此外,TextBox 的默认状态是单行 read/write 文本。但是为了向用户显示,您可能会发现将 TextBoxReadOnly 属性 设置为 true 会更好(即防止用户不小心更改文本),and/or 您更喜欢将 Multiline 属性 设置为 true(即显示多于一行的文本)。
  • 以上还假定 button2 按钮的 Enabled 属性 的初始状态为 false。 IE。在上面的第一种方法在适当的时间明确启用该按钮之前,无法单击该按钮。