views:

71

answers:

3

In a WPF application I'm writing, I have a TransformedBitmap property which is bound to an Image object on the UI. Whenever I change this property, the Image is updated (and thus the image being displayed to the screen is updated). In order to prevent the UI from freezing or becoming unresponsive whilst I retrieve the next image, I'm attempting to the snapshot retrieval with a BackgroundWorker like this:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
  e.Result = this.snapshotHelper.GetSnapshot(ImageFormat.Bmp);
}

then, in my RunWorkerCompleted method, I have the following:

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  this.CurrentImage = (TransformedBitmap)e.Result;
  ....
}

This seems to work okay until on the NotifyPropertyChanged method used to tell the Image object to update when I update the CurrentImage property; I get a cross-thread error.

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
  if (PropertyChanged != null)
  {
  //The following causes a "the calling thread cannot access this object because a different thread owns it" error!
    PropertyChanged(this, new PropertyChangedEventArgs(info));      
  }
}

I really don't know how to change things around or what to do differently to get around this error. I've been reading about BackgroundWorkers for the past couple of hours and it seems to me that I should be able to set CurrentImage fine in the RunWorkerCompleted method; at least from what I can tell. Any help on this would be greatly appreciated! Thanks!

A: 

Only dispatcher is allowed to interact with the UI. Have a look here:

http://www.jeff.wilcox.name/2010/04/propertychangedbase-crossthread/

Aliostad
+2  A: 

A control (Image) can only be changed on the Thread which created it. So essentially what happens is your background thread changes the property on your object, which in turn fires the PropertyChanged event, which is then consumed by WPF, which then attempts to modify the Image control (remember we're still on the BackgroundThread throughout this chain of events).

Luckily, the fix is pretty simple:

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  myWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
     {
        this.CurrentImage = (TransformedBitmap)e.Result;
        ....
     });
}

You will need a reference to your Window or Control for this to work, but this essentially queues up the delegate to run on the UI thread instead of the background thread.

Coding Gorilla
This actually caused a "Exception has been thrown by the target of an invocation" error...
JToland
That's probably because it's wrapped by WPF somewhere, but I would guess an InnerException would contain the cross-thread violation exception.
Coding Gorilla
The Dispatcher.Invoke() didn't work??
Coding Gorilla
I just found it; you're right, it did...so the fix didn't really work for this case at all then? Doing what you mentioned above, with the Dispatcher.Invoke(), led to the "Exception has been thrown by the target of an invocation" and it's InnerException was the same cross-thread error. So I'm really at a loss as to what to do now; am I just missing something basic here?
JToland
I think you might be missing something, if you want to email me (mark at warpool . org) I'll be happy to review your code and see if I can help further.
Coding Gorilla
+2  A: 
Dispatcher.Invoke((Action<TransformedBitmap>) (obj => this.CurrentImage = obj), e.Result as TransformedBitmap);

This should work...

Update

In your case you are using freezable object, and the problem is in the bitmap that you have created need to be freezed before sending it in UI thread. So you DoWork will be as follows:

void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            var bmp = snapshotHelper.GetSnapshot(ImageFormat.Bmp);
            bmp.Freeze();
            e.Result = bmp;            
        }

Then in RunWorkerCompleted you update the property as I wrote above.

Eugene Cheverda
This also caused the "Exception has been thrown by the target of an invocation" which in turn still contained the same cross-thread error.
JToland
This works perfectly now! Thank you SOO much!!
JToland
You are welcome!
Eugene Cheverda