我知道关于这个主题有一些答案,但我无法得到任何适合我的解决方案。我正在尝试从数据模板内触发的 ICommand 打开一个新窗口。以下两者都给出了上述错误当新窗口被实例化时(在“new MessageWindowP”内):
使用 TPL/FromCurrentSynchronizationContext 更新:有效
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();
}
}
使用ThreadStart:更新:不推荐,请参阅Jon的回答
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();
}
}
谢谢
编辑。根据到目前为止的响应,我想指出,我还尝试了当前调度程序上的 BeginInvoke,以及在原始方法中执行代码(这就是代码的启动方式)。见下图:
BeginInvoke更新:不建议参见乔恩的回答
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();
}
}
在同一线程中更新:如果您已经在 UI 线程上,则可以使用
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,使用对第一个/主窗口的调度程序的引用更新:有效
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
GeneralManager.MainDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
}
}
其中 GeneralManager.MainDispatcher 是对我创建的第一个窗口的调度程序的引用:
[somewhere far far away]
mainP = new MainP();
MainDispatcher = mainP.View.Dispatcher;
我很茫然。
调用线程不能仅是STA,但它还必须具有消息循环。您的应用程序中只有一个线程已经具有消息循环,这就是您的主线程。因此,您应该使用
Dispatcher.BeginInvoke
从主线程打开窗口。
例如如果您有主应用程序窗口的参考(
MainWindow
),您可以执行以下操作
MainWindow.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
更新:小心:你不能盲目地调用
Dispatcher.CurrentDispatcher
,因为它不会做你认为它会做的事情。 文档说CurrentDispatcher
:
获取当前正在执行的线程的Dispatcher并创建一个 如果尚未与线程关联,则创建新的调度程序。
这就是为什么您必须使用与已存在的 UI 控件(如上例中的主窗口)关联的
Dispatcher
。
您正在尝试从后台线程创建一个窗口。由于各种原因你无法做到这一点。通常,您需要在主应用程序线程中创建窗口。
对于您的情况,一个简单的想法是立即执行(只需在
CreateMessageWindow
内调用Execute
)而不是分配Task
,因为您的命令肯定会从主(UI)线程触发。如果您不确定 Execute
运行的线程,您可以使用 Dispatcher.BeginInvoke()
将其编组到 UI 线程。
在极少数情况下,您希望新窗口在非主线程中运行。如果您的情况确实需要这样做,您应该在
Dispatcher.Run();
之后添加 messageP.View.Show();
(使用代码的第二种变体)。这将在新线程中启动消息循环。
您不应该尝试在 TPL 的线程中运行窗口,因为这些线程通常是线程池线程,因此不受您的控制。例如,您无法确保它们是 STA(通常是 MTA)。
编辑:
从更新代码中的错误来看,很明显
Execute
在某些非 UI 线程中运行。尝试使用 Application.Current.Dispatcher
而不是 Dispatcher.CurrentDispatcher
。 (CurrentDispatcher
表示当前线程的调度程序,如果当前线程不是主线程,则可能会出错。)