我正在开发一个包含 WinForms 和 WPF 组件的 WinForms 应用程序。我想在后台异步加载主窗体(frmTP2D)时显示启动屏幕。启动屏幕应保持可见,直到主窗体完全初始化,然后启动屏幕应关闭,显示主窗体。
出现此问题的原因是主窗体(frmTP2D)同时包含 WinForms 和 WPF 元素。具体来说,我使用 ElementHost 嵌入 WPF UserControl (TP3DModelViewer)。初始化WPF组件时,出现以下错误:
System.InvalidOperationException: 'The calling thread must be STA, because many UI components require this.
我尝试过使用 async/await 来处理闪屏并在后台加载主窗体,但是当涉及 WPF 组件时就会出现问题,因为它们要求主线程处于 STA 模式。
我正在将 DevExpress 用于某些 UI 组件,包括启动屏幕,并且我在 Main 方法中设置了 [STAThread]。然而,我在将WPF组件集成到WinForms表单时仍然遇到这个线程问题。
有人在使用WPF和WinForms时遇到过类似的问题吗?关于如何正确异步初始化 WPF 组件同时保持启动屏幕可见的任何建议?
我尝试通过在后台加载主窗体(frmTP2D)时显示启动屏幕来在我的 WinForms 应用程序中实现异步初始化。我的目标是确保启动屏幕保持可见,直到主窗体的所有组件完全加载,包括使用 ElementHost 嵌入的 WPF 元素。表单准备好后,我希望关闭启动屏幕并显示主表单。
为了实现这一点,我使用 async/await 异步加载主窗体,直到到达涉及 WPF 元素的初始化部分为止,它工作得很好。当尝试在 WPF 需要的非 STA(单线程单元)上下文中初始化 WPF UserControl 时,会发生错误。
我原本希望能够异步加载表单,并让启动屏幕保持可见,直到表单完全初始化,但相反,我遇到了一个特别由于 WPF 组件而导致的线程问题。
这是我的 InitializeDefaultViews 方法,其中初始化了 WPF ElementHost:
private void InitializeDefaultViews()
{
_GridCanvas = new TPGridCanvas2D(glControl, _GridSettingsManager.GetSettings());
glControl.Invalidate();
dckPnl3DView.ControlContainer.Controls.Clear();
_3DModelViewer = new TP3DModelViewer(_GridSettingsManager.GetSettings());
var elementHost = new ElementHost
{
Dock = DockStyle.Fill,
Child = _3DModelViewer
};
dckPnl3DView.ControlContainer.Controls.Add(elementHost);
}
这是我的 Program.cs:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
namespace TPUI
{
static class Program
{
private static Dispatcher _splashScreenDispatcher;
private static frmTPSplashScreen _splashScreen;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
StartSplashScreen();
RunInitializationAndMainFormAsync().GetAwaiter().GetResult();
}
private static void StartSplashScreen()
{
var splashThread = new Thread(() =>
{
_splashScreenDispatcher = Dispatcher.CurrentDispatcher;
_splashScreen = new frmTPSplashScreen();
_splashScreen.Show();
Dispatcher.Run();
});
splashThread.SetApartmentState(ApartmentState.STA);
splashThread.Start();
}
private static async Task RunInitializationAndMainFormAsync()
{
await Task.Run(() =>
{
Thread.Sleep(3000);
});
CloseSplashScreen();
Application.Run(new frmTP2D());
}
private static void CloseSplashScreen()
{
if (_splashScreenDispatcher != null)
{
_splashScreenDispatcher.Invoke(() =>
{
_splashScreen?.Close();
});
_splashScreenDispatcher.InvokeShutdown();
}
}
}
}
所以你的代码失败的原因很简单。 在
RunInitializationAndMainFormAsync
中,您在设置同步上下文之前正在等待操作,因此延续在默认上下文中运行,这将是一个线程池线程,而不是 STA 线程(就像您在 RunInitializationAndMainFormAsync
中调用的线程一样) ).
解决方案是整个基本方法是有缺陷的。 不要创建多个 UI 线程。 您可以使用一个完美的 STA 线程来启动程序,该线程被设计为程序的one UI 线程。 使用它。
在主线程中,您可以为启动屏幕运行应用程序,然后当完成后,为“主”表单运行该应用程序。
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
frmTPSplashScreen splashScreen = new frmTPSplashScreen();
splashScreen.Shown += async (s, a) =>
{
await Task.Delay(TimeSpan.FromSeconds(3));
splashScreen.Close();
};
Application.Run(splashScreen);
Application.Run(new frmTP2D());
}