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, DoWorkEventArg开发者_StackOverflow中文版s e)
{
TreeView tv = new TreeView();
}
How can I create objects in background thread?
I use WPF application
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...
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);
}
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.
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.
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
}
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);
};
}
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.
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!
精彩评论