问题
I am trying to write a method that goes through a list of items - for each one adds it to a form, then waits for the user to input some data and click a button. However I have no idea of what I should/could be using to create this.
foreach (string s in List)
{
txtName.Text = s;
//wait for button click...
// When the user is ready, they click the button to continue the loop.
}
So far all I have found is the EventWaitHandle
class, which seems to only apply for threads.
How can I achieve this?
回答1:
There are ways of doing this using signalling if you're also using async/await - you could await a task which is completed by the button being clicked for example - but in general you should think about user interfaces in a more event-driven way.
Instead of having your foreach
loop here, keep an index into the collection for which item is being displayed, and advance it each time the button is clicked. (Remember to check whether or not there are more items to display, of course.)
回答2:
While I agree in general with Jon Skeet, there was an interesting presentation by Mads Torgensen demonstrating how async/await
can be used to simplify such scenarios (using the techniques mentioned by Jon). After all, isn't that the same as with enumerators - we can write own enumerator class using state like index etc., but we almost never do that and use iterator blocks instead.
Anyway, here is the async/await
technique we were talking about.
First, the reusable part:
public static class Utils
{
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
button.Click -= onClick;
tcs.TrySetResult(null);
};
button.Click += onClick;
return tcs.Task;
}
}
and your code using it (note that you need to mark your method as async
)
foreach (string s in List)
{
txtName.Text = s;
await yourButton.WhenClicked();
}
Sample test putting it all together:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Samples
{
static class Test
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var txtName = new TextBox { Parent = form, Top = 8, Left = 8 };
var buttonNext = new Button { Parent = form, Top = txtName.Bottom + 8, Left = 8, Text = "Next" };
form.Load += async (sender, e) =>
{
var List = new List<string> { "A", "B", "C", "D " };
foreach (string s in List)
{
txtName.Text = s;
await buttonNext.WhenClicked();
}
txtName.Text = "";
buttonNext.Enabled = false;
};
Application.Run(form);
}
}
public static class Utils
{
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
button.Click -= onClick;
tcs.TrySetResult(null);
};
button.Click += onClick;
return tcs.Task;
}
}
}
回答3:
I am with Jon Skeet on this one, trying to force a UI framework into a way of working that is not normal for it will often lead to problems. Even if it works, you will have code that is hard for anyone else to understand, as it will work in an unusual way.
Some web frameworks in Lisp worked in such a way as to make page web page displays look like method calls. I have not seen anything in .NET that does so.
My first thought is for you to call Application.DoEvents() within a loop checking for a flag that your button call-back sets. However your application would then use 100% of the CPU waiting while it is doing nothing. See http://blog.codinghorror.com/is-doevents-evil/
Your application is a finite state machine that responds to events from the user and moves into a new state whenever a required event arrives to match a state transition from the current state. There have been many attempts over the years to model this in code, none I have seen have come out with anything that is better than just using standard event processing.
来源:https://stackoverflow.com/questions/32836563/force-loop-to-wait-for-an-event