Polaris
Polaris

Reputation: 3793

How can I create WPF controls in a background thread?

I have method which create background thread to make some action. In this background thread I create object. But this object while creating in runtime give me an exception :

The calling thread must be STA, because many UI components require this.

I know that I must use Dispatcher to make reflect something to UI. But in this case I just create an object and dont iteract with UI. This is my code:

    public void SomeMethod()
      {
         BackgroundWorker worker = new BackgroundWorker();
         worker.DoWork += new DoWorkEventHandler(Background_Method);
         worker.RunWorkerAsync();
      }

   void Background_Method(object sender, DoWorkEventArgs e)
      {
         TreeView tv = new TreeView();
      }

How can I create objects in background thread?

I use WPF application

Upvotes: 11

Views: 8130

Answers (8)

user6996876
user6996876

Reputation:

No one is discussing the case of a separate STA thread in details (even though the concept is exactly the same).

So let's imagine a simple tab control added at a button click

    private void button_Click(object sender, RoutedEventArgs e)
    {
        TabItem newTab = new TabItem() { Header = "New Tab" };
        tabMain.Items.Add(newTab);
    }

If we move it to another STA thread

    private void button_Click(object sender, RoutedEventArgs e)
    {
        Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
        newThread.SetApartmentState(ApartmentState.STA);
        newThread.IsBackground = true;
        newThread.Start();
    }
    private void ThreadStartingPoint()
    {
        TabItem newTab = new TabItem() { Header = "New Tab" };
        tabMain.Items.Add(newTab);
    }

of course we get a System.InvalidOperationException

Now, what happens if we add the control

    private void AddToParent(string header)
    {
        TabItem newTab = new TabItem() { Header = header };
        tabMain.Items.Add(newTab);
    }

using a delegate method?

    public void DelegateMethod(string header)
    {
        tabMain.Dispatcher.BeginInvoke(
                new Action(() => {
                    this.AddToParent(header);
                }), null);
    }

it does work if you call it

    private void button_Click(object sender, RoutedEventArgs e)
    {
        Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
        newThread.SetApartmentState(ApartmentState.STA);
        newThread.IsBackground = true;
        newThread.Start();
    }
    private void ThreadStartingPoint()
    {
        DelegateMethod("new tab");
    }

because of course now we keep the visual tree in the same original thread.

Upvotes: 2

Arcturus
Arcturus

Reputation: 27055

See the answer on this question: How to run something in the STA thread?

When you define your thread, set the ApartmentState to STA:

thread.SetApartmentState(ApartmentState.STA);

This should do the trick!

Upvotes: 0

Polaris
Polaris

Reputation: 3793

I solved my problem. I just used e.Result property of RunWorkerCompleted method. I get data in background thread and then use this data when thread completed. Thank every body for useful methods. Special thank to Veer to give a recommendation about e.Result property.

Upvotes: 0

Amsakanna
Amsakanna

Reputation: 12934

void Background_Method(object sender, DoWorkEventArgs e) 
{ 
    TreeView tv = new TreeView(); 
    // Generate your TreeView here
    UIDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
    { 
        someContainer.Children.Add(tv);
    }; 
}

Upvotes: 0

Andrew
Andrew

Reputation: 1102

HTH:

    void Background_Method(object sender, DoWorkEventArgs e)
    {
        // Time Consuming operations without using UI elements
        // Result of timeconsuming operations
        var result = new object();
        App.Current.Dispatcher.Invoke(new Action<object>((res) =>
            {
                // Working with UI
                TreeView tv = new TreeView();
            }), result);
    }

Upvotes: 3

Dean Harding
Dean Harding

Reputation: 72658

TreeView is a UI control. You can only create and manipulate UI controls on a UI thread, so what you're trying to do is not possible.

What you want to do is do all of the time-consuming work on the background thread, and then "call back" to the UI thread to manipulate the UI. This is actually quite easy:

void Background_Method(object sender, DoWorkEventArgs e)
{
    // ... time consuming stuff...

    // call back to the window to do the UI-manipulation
    this.BeginInvoke(new MethodInvoker(delegate {
        TreeView tv = new TreeView();
        // etc, manipulate
    }));
}

I may have got the syntax wrong for BeginInvoke (it's off the top of my head), but there you go anyway...

Upvotes: 7

Archie
Archie

Reputation: 2579

Try following Code:

public void SomeMethod() 
{ 

System.ComponentModel.BackgroundWorker myWorker = new  System.ComponentModel.BackgroundWorker();

myWorker.DoWork += myWorker_DoWork;

myWorker.RunWorkerAsync();

}

private void myWorker_DoWork(object sender,
   System.ComponentModel.DoWorkEventArgs e)
{
   // Do time-consuming work here
}

Upvotes: 0

wpfwannabe
wpfwannabe

Reputation: 14867

To make your code simply work, you must join a STA COM apartment by calling Thread.SetApartmentState(ApartmentState.STA). Since BackgroundWorker is probably using some shared thread pool, joining a particular apartment may affect other users of this thread pool or may even fail if it has already been set to e.g. MTA before. Even if it all worked out, your newly created TreeView would be locked to this worker thread. You wouldn't be able to use it in your main UI thread.

If you explained in a bit more detail about your true intentions, you would surely get better help.

Upvotes: 0

Related Questions