简体   繁体   中英

Update UI from a different thread in a different class

I have the main form class which contains a list box I want to change. The box is populated with items created in a time-consuming method. Right now it looks like this (inventing an example by hand, might not be valid C#):

List<string> strings = StaticClassHelper.GetStrings(inputString);
foreach(string s in strings)
{
    listBox1.Add(s);
}

//meanwhile, in a different class, in a different file...

public static List<string> GetStrings(inputString)
{
    List<string> result = new List<string>();
    foreach(string s in inputString.Split('c'))
    {
        result.Add(s.Reverse());
        Thread.Sleep(1000);
    }
    return result;
}

What I would like to do instead is regularly update the list box as new strings are found. The other answers I found work when the thread method is in the same class, so you can set up an event handler. What do I do here?

Here is how I like to do this, I create a method on the form like this:

public void AddItemToList(string Item)
{
   if(InvokeRequired)
      Invoke(new Action<string>(AddItemToList), Item);
   else
      listBox1.Add(Item);
}

I prefer invoke in this case to make sure the items are added synchronously, otherwise they can get out of order. If you don't care about the order then you can use BeginInvoke which will be a tad faster. Since this method is public, you can all it from any class in your application as long as you can get a reference to your form.

Another advantage of this is that you can call it from either your UI thread or a non-UI thread and it takes care of deciding whether or not it needs Invoke ing. This way your callers don't need to be aware of which thread they are running on.

UPDATE To address your comment about how to get a reference to the Form , typically in a Windows Forms app your Program.cs file looks something like this:

static class Program
{
   static void Main() 
   {
       MyForm form = new MyForm();
       Application.Run(form);  
   }

}

This is typically what I would do, particularly in the case of a "Single Form" application:

static class Program
{
   public static MyForm MainWindow;

   static void Main() 
   {
       mainWindow = new MyForm();
       Application.Run(form);  
   }

}

And then you can access it pretty much anywhere with:

Program.MainWindow.AddToList(...);

The class containing the ListBox needs to expose a method to add a string - since this method might be called on a different thread, it needs to use

listBox1.Invoke( ...)

to create a thread-safe calling mechanism

Would it be possible for you to rewrite GetStrings as an Iterator? Then in your UI you could start a background thread which iterates over the results of GetStrings, updating the listbox each time. Something like:

public static System.Collections.IEnumerable GetStrings(inputString)
{
    foreach(string s in inputString.Split('c'))
    {
        yield return s.Reverse();
        Thread.Sleep(1000);
    }
}

And in the UI (Assuming C# 4):

Task.Factory.StartNew(() =>
{
    foreach (string s in StaticClassHelper.GetStrings(inputString))
    {
        string toAdd = s;
        listBox1.Invoke(new Action(() => listBox1.Add(toAdd)));
    }
}

Probably cleaner ways to go about it, but this should get you what you're looking for.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM