1

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?

Ben
  • 2,433
  • 5
  • 39
  • 69

3 Answers3

4

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.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

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;
        }
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • There may be a race condition that leads to lost button clicks. The risk is that there is a click between the event getting removed and the new event getting added. It will depend on how the implantation of await interacts with the windows message queue. – Ian Ringrose Sep 29 '15 at 09:45
  • @IanRingrose Agreed. But that might not be a problem for a program like this - simulating console type of processing like asking a question and wait for response. – Ivan Stoev Sep 29 '15 at 09:52
0

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.

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317