0

I have a problem setting a Property on the main thread from another thread.

This is what i do at the moment:

XAML:

<TabControl Name="tab"/>

Code Behind:

TabItem tabItem = new TabItem();

Binding myBinding = new Binding("ReportView") { Source = ViewModel, Mode =    BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
BindingOperations.SetBinding(tabItem, TabItem.ContentProperty, myBinding);

tab.Items.Add(tabItem);

Thread t = new Thread(DoWork);
t.SetApartmentState(ApartmentState.STA);
t.Priority = ThreadPriority.Normal;
t.Start();

DoWork Method:

When i create the ReportPreviewView inside the body of the Dispatcher.BeginInvoke, i don't get an exception:

    public void DoWork(object sender)
        {
            //I create the relavant ViewModel
            ReportViewModel reportViewModel = new ReportViewModel(SessionContext, Mediator, Parent.ReportRequest, false);

            Dispatcher.BeginInvoke((Action)(() =>
                        {
                            //Create the relevant View
                            ReportPreviewView reportPreviewView = new ReportPreviewView(reportViewModel) { ReportName = Parent.ReportRequest.Report.ReportName };
                            ViewModel.ReportView = reportPreviewView;
                        }));
        }

But this is not correct. It freezes up my UI thread. The creation of the ReportPreviewView takes quit a long time.

So then i move the creation of the ReportPreviewView outside the Dispatcher.BeginInvoke:

public void DoWork(object sender)
    {
        ReportViewModel reportViewModel = new ReportViewModel(SessionContext, Mediator, Parent.ReportRequest, false);

        ReportPreviewView reportPreviewView = new ReportPreviewView(reportViewModel) { ReportName = Parent.ReportRequest.Report.ReportName };

        Dispatcher.BeginInvoke((Action)(() =>
                    {
                        reportLoaderViewModel.ReportView = reportPreviewView;
                    }));
    }

As soon as PropertyChanged fires i get the following exception:

The calling thread cannot access this object because a different thread owns it

Any idea how i can get around this?

H.B.
  • 166,899
  • 29
  • 327
  • 400
Willem
  • 9,166
  • 17
  • 68
  • 92
  • Well, not sure it helps, but if your `ReportPreviewView` copy constructor is lightweight, you might just want to make a copy of that object inside `beginInvoke` and use it to set `ReportView`. But I'm almost sure that won't work. Try it, hovewer, it's just one line of code :) – J0HN Sep 27 '11 at 08:22
  • 1
    The prequel: http://stackoverflow.com/questions/7553884/instantiating-an-object-in-a-background-worker-causes-the-ui-to-freeze-up – H H Sep 27 '11 at 08:56

1 Answers1

2

You have to create UI elements on UI dispatcher thread. There is no escape. So when you say

The creation of the ReportPreviewView takes quit a long time.

Did you anaylyse why is that? Does it have drawings, animations, heavy styles, non-virtualized panels (such as canvas)? Does it have some code in its constructor that takes time to complete?

If so, please reconsider your UI design.

In WPF the UI always freezes for heavy views. Even if you deserialize views using XAMLReader/Writer, the creation and hosting of the view object has to take place on UI thread.

WPF-it
  • 19,625
  • 8
  • 55
  • 71
  • Thanks for the response. When i say the `The creation of the ReportPreviewView takes quit a long time.`, the constructor has some code that takes a long time... No if you say, `reconsider your UI design`, what should i change then? I am new to `multithreading`, so i learn on the go... – Willem Sep 27 '11 at 09:46
  • So can that code which takes time be put into `BackgroundWorker`? – WPF-it Sep 27 '11 at 10:15
  • Yes, i can do that... Would that be a viable solution then? All the lengthy operations in my constructors must be in their own `thread/task`? – Willem Sep 27 '11 at 10:42
  • 1
    Of course. Background worker is suitable as it provides dispatcher driven **`WorkCompleted`** event. It also provides **Progress Notification** event Also being a background thread it can close when main application closes. It is also handled by thread pool internally. – WPF-it Sep 27 '11 at 10:59
  • I tried putting the `heavy lifting` in a background worker, but still no luck. The `ReportPreviewView` creates a another view inside the constructor and inside the new view's constructor there are more `heavy lifting`. I tried putting all of `heavy lifting` in `tasks/background workers`, but then i get `The calling thread must be STA, because many UI components require this.`. I am stuck and don't know where to go from here... Do u maybe have any other advice i could try? – Willem Sep 27 '11 at 14:49