1
votes

I have read a lot posts about this error but I dont understand how it could be solved in my solution(( I have a progressbar dialog with some logic in it- which is called from MainFrame by ButtonClick

void OnBtnClick(object sender, RoutedEventArgs e)
{  
    ProgressDialog dlg = new ProgressDialog("");
    dlg.Closing += new CancelEventHandler(dlg_Closing);
    dlg.Closed += new EventHandler(dlg_Closed);
    //dlg.AutoIncrementInterval = 0;
    LibWrap lwrap = new LibWrap();
    DoWorkEventHandler handler = delegate
    {
        BitmapFrame bf = wrap.engine(BitmapFrame.Create(FXPhotoStudio.App
                                                            .draggedImage),
                                     this.fxPSEditorView); 
    };
    dlg.CurrentLibWrap = lwrap;
    dlg.AutoIncrementInterval = 100;
    dlg.IsCancellingEnabled = true;
    dlg.Owner = Application.Current.MainWindow;
    dlg.RunWorkerThread(0, handler); 
}

There is also a handler in the same class(MainFrame) on closed event for this progress bar dialog

void dlg_Closed(object sender, EventArgs e)
{
    try
    { 
        mainFrameView.CurrentImage = effectedImage;//!error here!
    }
}

the effectedImage is a field of MainFrame. It is seted by my ProgressDialog. I make the following in ProgressDialog.cs:

(this.Owner as MainFrame).effectedImage = currentLibVrap.GetEffectedImage;

currentLibVrap was set in OnBtnClick - see above Could anyone help me to solve this?

This is code that closing ProgressBarDialog:

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      if (!Dispatcher.CheckAccess())
      {
        //run on UI thread
        RunWorkerCompletedEventHandler handler = worker_RunWorkerCompleted;
        Dispatcher.Invoke(DispatcherPriority.SystemIdle, handler, new object[] {sender, e}, null);
        return;
      }

      if (e.Error != null)
      {
        error = e.Error;
      }
      else if (!e.Cancelled)
      {
        //assign result if there was neither exception nor cancel

          (this.Owner as MainWindow).effectedImage = currentLibVrap.GetEffectedImage;//! ok there

        result = e.Result;
      }

  //update UI in case closing the dialog takes a moment
 // progressTimer.Stop();
  progressBar.Value = progressBar.Maximum;
  btnCancel.IsEnabled = false;

  //set the dialog result, which closes the dialog
  DialogResult = error == null && !e.Cancelled;
}

And there is the working process:

/// Launches a worker thread which is intended to perform
/// work while progress is indicated, and displays the dialog
/// modally in order to block the calling thread.
/// </summary>
/// <param name="argument">A custom object which will be
/// submitted in the <see cref="DoWorkEventArgs.Argument"/>
/// property <paramref name="workHandler"/> callback method.</param>
/// <param name="workHandler">A callback method which is
/// being invoked on a background thread in order to perform
/// the work to be performed.</param>


   public bool RunWorkerThread(object argument, DoWorkEventHandler workHandler)
    {
      if (autoIncrementInterval.HasValue)
      {
        //run timer to increment progress bar
        progressTimer.Interval = TimeSpan.FromMilliseconds(autoIncrementInterval.Value);
        progressTimer.Start();
         // LibWrap lwrap = new LibWrap();
         // BitmapFrame bf = lwrap.engine(BitmapFrame.Create(FXPhotoStudio.App.draggedImage));//(aa.Image);

      }

      //store the UI culture
      uiCulture = CultureInfo.CurrentUICulture;

      //store reference to callback handler and launch worker thread
      workerCallback = workHandler;
      worker.RunWorkerAsync(argument);

      //display modal dialog (blocks caller)
      return ShowDialog() ?? false;
    }

/// <summary>
/// Worker method that gets called from a worker thread.
/// Synchronously calls event listeners that may handle
/// the work load.
/// </summary>
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  try
  {
    //make sure the UI culture is properly set on the worker thread
    Thread.CurrentThread.CurrentUICulture = uiCulture;
    //invoke the callback method with the designated argument
    workerCallback(sender, e);
  }
  catch (Exception)
  {
    //disable cancelling and rethrow the exception
    //Dispatcher.BeginInvoke(DispatcherPriority.Normal,
     //                      (SendOrPostCallback) delegate { btnCancel.SetValue(Button.IsEnabledProperty, false); },
    //                       null);
    return;
    //throw;
  }
}
2
You need to call Invoke() to call a function within your UI thread. - Lloyd Powell

2 Answers

1
votes

You can use either Dispather.Invoke or Dispatcher.BeginInvoke. They both will marshall the call to the UI thread (that's what your error's about), BeginInvoke is designed for running heavy operations in a background thread, while Invoke is just a marshaller, so for your type of task I'd stick with the last one.

Here's how you do it (assuming that mainFrameView.CurrentImage is of Image type, otherwise just change in to whatever it is):

C#

Update 1 Use a unique name for the argument to avoid corssing with the existing variable names.

mainFrameView.Dispatcher.Invoke(new Action<object>((myImage2012) => 
{ mainFrameView.CurrentImage = (Image)myImage2012; }), 
new object[1] { effectedImage });
1
votes

You need do dispatch it to UI thread using Dispatcher.BeginInvoke (dispatches and returns immediatly) or Dispatcher.Invoke (blocks until action was dispatched)

That's because, by default, threading model for an application is single-threaded apartment (STA). That means, only thread which created UI element, is able to interact with it, other threads, which want to do something with UI element, must dispatch their actions on that elements to UI thread

void dlg_Closed(object sender, EventArgs e)
{
    try
    { 
        mainFrameView.CurrentImage = effectedImage;//!error here!
    }
}

Are you sure UI thread send this event? Have you tried to dispatch setting CurrentImage? Could you please also simplify your code and leave only relevant methods?

UPD: I mean, have you tried to dispatch CurrentImage setting?

    Application.Current.MainWindow.Dispatcher.BeginInvoke(new Action(()=>
                                          {
                                              mainFrameView.CurrentImage = effectedImage;
                                          }));