I know there are a few answers on this topic on SO, but I can not get any of the solutions working for me. I am trying to open a new window, from an ICommand fired from within a datatemplate. Both of the following give the aforementioned error when the new window is instantiated (within "new MessageWindowP"):
Using TPL/FromCurrentSynchronizationContext Update: works
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(new Action<object>(CreateMessageWindow), user,CancellationToken.None, TaskCreationOptions.None,scheduler);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
Using ThreadStart: Update: not recommended, see Jon's answer
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var t = new ParameterizedThreadStart(CreateMessageWindow);
var thread = new Thread(t);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(sender);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
Thanks
EDIT. Based on the responses so far, I'd like to point out that I have also tried BeginInvoke on the current dispatcher, as well as executing the code in the original method (that's how the code started). See below:
BeginInvoke Update: not recommended see Jon's answer
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
Dispatcher.CurrentDispatcher.BeginInvoke(new Action<object>(CreateMessageWindow), sender);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
In same thread Update: works if you are on UI thread already
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
}
BeginInvoke, using reference to dispatcher of first/main window Update: works
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
GeneralManager.MainDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
}
}
where GeneralManager.MainDispatcher is a reference to the Dispatcher of the first window I create:
[somewhere far far away]
mainP = new MainP();
MainDispatcher = mainP.View.Dispatcher;
I'm at a loss.
The calling thread must not only be STA, but it must also have a message loop. There's only one thread in your application that already has a message loop, and that's your main thread. So you should use
Dispatcher.BeginInvoke
to open your window from your main thread.E.g. if you have a reference to your main application window (
MainWindow
), you can doUpdate: Be careful: you cannot blindly call
Dispatcher.CurrentDispatcher
because it doesn't do what you think it does. The documentation says thatCurrentDispatcher
:That's why you must use the
Dispatcher
associated with an already-existing UI control (like your main window as in the example above).With TPL you can use the StaTaskScheduler from the TPL Extras
It will run tasks on STA threads.
Only used it for COM. Never tried to run several UI threads.
You are trying to create a window from a background thread. You cannot do it due to a variety of reasons. Typically you need to create the window in the main application thread.
For your case, a simple idea would be just do it immediately (just call
CreateMessageWindow
insideExecute
) instead of allocating aTask
, because your command will definitely fire from the main (UI) thread. If you are unsure about the thread where yourExecute
runs, you can marshal it to the UI thread usingDispatcher.BeginInvoke()
.There are really few cases when you would want your new window to run in a non-main thread. If this is really really needed in your case, you should add
Dispatcher.Run();
aftermessageP.View.Show();
(using the second variant of the code). This starts the message loop in the new thread.You shouldn't try to run window in TPL's thread, because these threads are typically threadpool threads, and therefore out of your control. For example, you cannot ensure they are STA (typically, they are MTA).
Edit:
from the error in your updated code, it's clear that the
Execute
runs in some non-UI thread. Try usingApplication.Current.Dispatcher
instead ofDispatcher.CurrentDispatcher
. (CurrentDispatcher
means the dispatcher of the current thread, which may be wrong if the current thread is not the main one.)