如何在WinForms中异步显示启动画面,同时在后台加载主窗体(C#)?

问题描述 投票:0回答:1

我正在开发一个包含 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();
            }
        }
    }
}

c# wpf multithreading winforms async-await
1个回答
1
投票

所以你的代码失败的原因很简单。 在

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());
}
© www.soinside.com 2019 - 2024. All rights reserved.