9

I realize what I'm doing is probably pretty silly, but I'm in the middle of learning WPF and would like to know how to do this.

I have a window with a listbox on it. The listbox is being used to deliver status messages about the program while it's running. For example "Server started" "New connection at IP #" etc. I wanted this to be constantly updating in the background, so I spawned a new thread for handling updating this, but when I made the call to add an item I get the error message "The calling thread cannot access this object because a different thread owns it."

Any idea how I can update the listbox from another thread? Or in the background, etc.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
cost
  • 4,420
  • 8
  • 48
  • 80

3 Answers3

25


UPDATE

If you are using C# 5 and .NET 4.5 or above you can avoid getting on another thread in the first place using async and await, e.g.:

private async Task<string> SimLongRunningProcessAsync()
{
    await Task.Delay(2000);
    return "Success";
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    button.Content = "Running...";
    var result = await SimLongRunningProcessAsync();
    button.Content = result;
}

Easy:

Dispatcher.BeginInvoke(new Action(delegate() 
  {
     myListBox.Items.Add("new item"));
  }));

If you are in code-behind. Otherwise you can access the Dispatcher (which is on every UIElement) using:

Application.Current.MainWindow.Dispatcher.BeginInvoke(...

Ok thats a lot in one line let me go over it:

When you want to update a UI control you, as the message says, have to do it from the UI thread. There is built in way to pass a delegate (a method) to the UI thread: the Dispatcher. Once you have the Dispatcher you can either Invoke() of BeginInvoke() passing a delegate to be run on the UI thread. The only difference is Invoke() will only return once the delegate has been run (i.e. in your case the ListBox's new item has been added) whereas BeginInvoke() will return immediately so your other thread you are calling from can continue (the Dispatcher will run your delegate soon as it can which will probably be straight away anyway).

I passed an anonymous delegate above:

delegate() {myListBox.Items.Add("new item");}

The bit between the {} is the method block. This is called anonymous because only one is created and it doesnt have a name (usually you can do this using a lambda expression but in this case C# cannot resolve the BeginInvoke() method to call). Or I could have instantiated a delegate:

Action myDelegate = new Action(UpdateListMethod);

void UpdateListMethod() 
{
  myListBox.Items.Add("new item");
}

Then passed that:

Dispatcher.Invoke(myDelegate);

I also used the Action class which is a built in delegate but you could have created your own - you can read up more about delegates on MSDN as this is going a bit off topic..

markmnl
  • 11,116
  • 8
  • 73
  • 109
  • Hey thanks, I really appreciate detailed explanation! I'm a programming student, but everyone at my school has a dislike of C#. I like your longer explanation since it's helping me to understand what's going on. C# and WPF is a fairly huge language, and there's only so much I can get straight out of a book. – cost Nov 16 '10 at 05:34
  • My pleasure (it is v. similar to another answer I have given). Pls mark it as the answer if it is :) – markmnl Nov 16 '10 at 06:14
  • 3
    I like how, looking back on this three years later, I actually know what a dispatcher, invoking, delegates, and all this other stuff is (and actually know how to use it!) :) – cost Dec 02 '13 at 19:48
5

You can also use the Action delegate with anonymous methods to update the main thread from the worker thread. For more information on the Action class you could look here :

http://msdn.microsoft.com/en-us/library/018hxwa8.aspx

If you would like to update the listbox from multiple points I suggest explicitally setting the delegate, however if you just wish to update the thread at a single point in a method with a single call it could be done as follows :

        listbox.Dispatcher.BeginInvoke(new Action(delegate()
        {
            listbox.Items.Add(item); //where item is the item to be added and listbox is the control being updated.
        }));

Note that I use the Action class as it takes encapsulates a method that has a single parameter (in this case a listItem).

Elixir
  • 357
  • 1
  • 3
  • 13
  • 1
    I think this does it. The other answer that was given gave me a compiler error about how it couldn't convert the lamba calculus to a delegate. I did some reading on the msdn site and googled how to work with the dispatcher; I modified the code he gave me and it turned out looking exactly like what you've got there. Answered 13 minutes ago, I should've just hit refresh, haha. – cost Nov 16 '10 at 05:26
2

You'll want to use Dispatcher.BeginInvoke. For example, if the listbox is named listbox

  // On worker thread
  listbox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
  new Action(delegate() { listbox.Items.Add("Server started") });

This will queue up the delegate to be executed on the UI thread that listbox belongs to.

Logan Capaldo
  • 39,555
  • 5
  • 63
  • 78
  • Hey thanks, you pointed in the right direction, but the complier didn't like what you gave here. Gave an error about how it can't convert lamba calculus to a delegate. I was able to change it around so it works, any idea what was off about yours, or were you just using notation I don't understand – cost Nov 16 '10 at 05:28
  • No I was just too lazy to check myself with an actual program. – Logan Capaldo Nov 16 '10 at 12:53