Moving from a prompting-centric environment like a console program to an event-driven environment like Winforms, yes…that definitely will require at least some change in "the nature of the loops". :)
That said, the latest version of C# offers an async
/await
-based approach that can minimize some of the culture-shock that might come from moving from console to GUI. Writing and using async
method is itself non-trivial, but IMHO the simpler scenarios are not too hard to understand. More importantly, because it allows you to structure the code in a more directly-imperative way, similar to that which would be used in a console program, it's very much worth learning this along with Winforms generally.
In your particular scenario, you have two separate things you'll need to deal with: prompting the user, and receiving the user's input.
Because of the way an event-driven system works, you need to separate these tasks. But .NET has a class, TaskCompletionSource
, which we can use to keep the two glued together, even though they wind up in different places.
First, what happens when the user starts the process? Presumably, you'll have a form, where on that form is a button (or possible a menu item) which when clicked/selected, starts the whole thing. That might look something like this:
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);
}
}
(I have changed your question-selection logic above to something more efficient, by assuming that you have a ShuffleQuestions()
method. See Is using Random and OrderBy a good shuffle algorithm? for details on how to implement that).
What the above code does is, in response to the user clicking the button1
button (which presumably has text like "Start" or something), executes a loop that is very similar to what you had in your console program. The two main differences are:
- Where in your console program, you use
Console.WriteLine()
to display text to the user, here I have shown the use of TextBox
controls in your form which are used to display the same text.
- What in your console program, you use
Console.ReadLine()
to receive input from the user, this loop creates a TaskCompletionSource
object for a completely different method to use. That method, which is executed with your button2
button (which presumably has text like "Check Answer" or something) will read the text entered in a text box by the user (here, I've given it the name textBoxUserAnswer
), compare it to the correct answer for the question (which has been provided to this method by the other method via the AsyncState
property of the Task
created by the TaskCompletionSource
object I created), and set the Task
's result to true
or false
, depending on whether the user got the answer correct or not.
The tricky part above is that "under the hood", that first method actually returns as soon as it is done filling in the text for the first question and reaches the await
statement in the loop. The compiler rewrites the entire method to facilitate this.
When button2
is pushed, and sets the result of the Task
, the framework then knows to resume executing the first method where it left off at the await
statement, continuing your loop.
This sequence continues until the user has answered all of the question.
Some final notes about the UI:
- I have used
TextBox
's everywhere for user input and output. Of course, there are other ways to display text. Also, the default state for a TextBox
is a single-line, read/write text. But for displaying to the user, you may find that setting the ReadOnly
property of the TextBox
to true is better (i.e. to prevent the user from accidentally changing the text), and/or that you prefer setting the Multiline
property to true
(i.e. so that more than one line of text is displayed).
- The above also assumes that the initial state for the
button2
button's Enabled
property is false
. I.e. that button can't be clicked until the first method above explicitly enables the button at the appropriate time.