C# 中的多线程启动屏幕?

发布于 2024-07-05 01:08:04 字数 411 浏览 6 评论 0原文

我希望在加载应用程序时显示启动屏幕。 我有一个带有系统托盘控件的表单。 我希望在加载此表单时显示初始屏幕,这需要一些时间,因为它正在访问 Web 服务 API 来填充一些下拉列表。 我还想在加载之前对依赖项进行一些基本测试(即Web服务可用,配置文件可读)。 随着启动的每个阶段的进行,我想根据进度更新启动屏幕。

我已经阅读了很多有关线程的内容,但是我不知道应该从哪里控制它(main()方法?)。 我还缺少 Application.Run() 的工作原理,这是应该从中创建线程的地方吗? 现在,如果带有系统托盘控件的窗体是“活动”窗体,那么启动画面应该来自那里吗? 无论如何,在表单完成之前它不会加载吗?

我不是在寻找代码讲义,更多的是算法/方法,这样我就可以一劳永逸地解决这个问题:)

I want a splash screen to show while the application is loading. I have a form with a system tray control tied to it. I want the splash screen to display while this form loads, which takes a bit of time since it's accessing a web service API to populate some drop-downs. I also want to do some basic testing for dependencies before loading (that is, the web service is available, the configuration file is readable). As each phase of the startup goes, I want to update the splash screen with progress.

I have been reading a lot on threading, but I am getting lost on where this should be controlled from (the main() method?). I am also missing how Application.Run() works, is this where the threads for this should be created from? Now, if the form with the system tray control is the "living" form, should the splash come from there? Wouldn't it not load until the form is completed anyway?

I'm not looking for a code handout, more of an algorithm/approach so I can figure this out once and for all :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

ま柒月 2024-07-12 01:08:04

实际上这里的多线程是没有必要的。

每当您想要更新启动屏幕时,让您的业务逻辑生成一个事件。

然后让您的表单在挂接到事件处理程序的方法中相应地更新启动屏幕。

要区分更新,您可以触发不同的事件或在从 EventArgs 继承的类中提供数据。

这样,您就可以拥有漂亮的变化启动屏幕,而无需担心多线程问题。

实际上,您甚至可以在启动画面上支持 gif 图像等。 为了使其正常工作,请在处理程序中调用 Application.DoEvents() :

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}

Actually mutlithreading here is not necessary.

Let your business logic generate an event whenever you want to update splash screen.

Then let your form update the splash screen accordingly in the method hooked to eventhandler.

To differentiate updates you can either fire different events or provide data in a class inherited from EventArgs.

This way you can have nice changing splash screen without any multithreading headache.

Actually with this you can even support, for example, gif image on a splash form. In order for it to work, call Application.DoEvents() in your handler:

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}
永不分离 2024-07-12 01:08:04

我不同意推荐 WindowsFormsApplicationBase 的其他答案。 根据我的经验,它会减慢您的应用程序的速度。 准确地说,当它与启动屏幕并行运行表单的构造函数时,它会推迟表单的 Shown 事件。

考虑一个应用程序(没有闪屏),其构造函数需要 1 秒,而 Shown 上的事件处理程序需要 2 秒。 该应用程序在 3 秒后即可使用。

但假设您使用 WindowsFormsApplicationBase 安装启动屏幕。 您可能认为 3 秒的 MinimumSplashScreenDisplayTime 比较合理,不会减慢您的应用程序的速度。 但是,尝试一下,您的应用程序现在需要 5 秒才能加载。


class App : WindowsFormsApplicationBase
{
    protected override void OnCreateSplashScreen()
    {
        this.MinimumSplashScreenDisplayTime = 3000; // milliseconds
        this.SplashScreen = new Splash();
    }

    protected override void OnCreateMainForm()
    {
        this.MainForm = new Form1();
    }
}

结论

public Form1()
{
    InitializeComponent();
    Shown += Form1_Shown;
    Thread.Sleep(TimeSpan.FromSeconds(1));
}

void Form1_Shown(object sender, EventArgs e)
{
    Thread.Sleep(TimeSpan.FromSeconds(2));
    Program.watch.Stop();
    this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToString();
}

:如果您的应用有 Slown 事件处理程序,请不要使用 WindowsFormsApplicationBase。 您可以编写更好的代码,与构造函数和 Shown 事件并行运行启动。

I disagree with the other answers recommending WindowsFormsApplicationBase. In my experience, it can slow your app. To be precise, while it runs your form's constructor in parallel with the splash screen, it postpone your form's Shown event.

Consider an app (without splashs screen) with a constructor that takes 1 second and a event handler on Shown that takes 2 seconds. This app is usable after 3 seconds.

But suppose you install a splash screen using WindowsFormsApplicationBase. You might think MinimumSplashScreenDisplayTime of 3 seconds is sensible and won't slow your app. But, try it, your app will now take 5 seconds to load.


class App : WindowsFormsApplicationBase
{
    protected override void OnCreateSplashScreen()
    {
        this.MinimumSplashScreenDisplayTime = 3000; // milliseconds
        this.SplashScreen = new Splash();
    }

    protected override void OnCreateMainForm()
    {
        this.MainForm = new Form1();
    }
}

and

public Form1()
{
    InitializeComponent();
    Shown += Form1_Shown;
    Thread.Sleep(TimeSpan.FromSeconds(1));
}

void Form1_Shown(object sender, EventArgs e)
{
    Thread.Sleep(TimeSpan.FromSeconds(2));
    Program.watch.Stop();
    this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToString();
}

Conclusion: don't use WindowsFormsApplicationBase if your app has a handler on the Slown event. You can write better code that runs the splash in parallel to both the constructor and the Shown event.

音栖息无 2024-07-12 01:08:04

我非常喜欢 Aku 的答案,但该代码适用于 C# 3.0 及更高版本,因为它使用 lambda 函数。 对于需要使用 C# 2.0 中的代码的人,这里是使用匿名委托而不是 lambda 函数的代码。 您需要一个名为 formSplash 且具有 FormBorderStyle = None 的最顶层 winform。 表单的 TopMost = True 参数很重要,因为如果不是最顶层,启动屏幕可能看起来像是出现然后很快消失。 我还选择了 StartPosition=CenterScreen,因此它看起来就像专业应用程序对启动屏幕所做的那样。 如果您想要更酷的效果,可以使用 TrasparencyKey 属性制作不规则形状的启动屏幕。

private void formMain_Load(object sender, EventArgs e)
  {

     Hide();
     bool done = false;
     ThreadPool.QueueUserWorkItem(delegate
     {
       using (formSplash splashForm = new formSplash())
       {
           splashForm.Show();
           while (!done)
              Application.DoEvents();
           splashForm.Close();
       }
     }, null);

     Thread.Sleep(2000);
     done = true;
     Show();
  }

I like Aku's answer a lot, but the code is for C# 3.0 and up since it uses a lambda function. For people needing to use the code in C# 2.0, here's the code using anonymous delegate instead of the lambda function. You need a topmost winform called formSplash with FormBorderStyle = None. The TopMost = True parameter of the form is important because the splash screen might look like it appears then disappears quickly if it's not topmost. I also choose StartPosition=CenterScreen so it looks like what a professional app would do with a splash screen. If you want an even cooler effect, you can use the TrasparencyKey property to make an irregular shaped splash screen.

private void formMain_Load(object sender, EventArgs e)
  {

     Hide();
     bool done = false;
     ThreadPool.QueueUserWorkItem(delegate
     {
       using (formSplash splashForm = new formSplash())
       {
           splashForm.Show();
           while (!done)
              Application.DoEvents();
           splashForm.Close();
       }
     }, null);

     Thread.Sleep(2000);
     done = true;
     Show();
  }
明明#如月 2024-07-12 01:08:04
private void MainForm_Load(object sender, EventArgs e)
{
     FormSplash splash = new FormSplash();
     splash.Show();
     splash.Update();
     System.Threading.Thread.Sleep(3000);
     splash.Hide();
}

我从互联网上的某个地方得到了这个,但似乎无法再找到它。 简单但有效。

private void MainForm_Load(object sender, EventArgs e)
{
     FormSplash splash = new FormSplash();
     splash.Show();
     splash.Update();
     System.Threading.Thread.Sleep(3000);
     splash.Hide();
}

I got this from the Internet somewhere but cannot seem to find it again. Simple but yet effective.

删除→记忆 2024-07-12 01:08:04

我在 codeproject 上发布了一篇关于将启动屏幕合并到应用程序中的文章。 它是多线程的,您可能会感兴趣

C# 中的另一个启动屏幕

I posted an article on splash screen incorporation in the application at codeproject. It is multithreaded and might be of your interest

Yet Another Splash Screen in C#

你爱我像她 2024-07-12 01:08:04

一种简单的方法是使用类似 main() 的方法:

<STAThread()> Public Shared Sub Main()

    splash = New frmSplash
    splash.Show()

    ' Your startup code goes here...

    UpdateSplashAndLogMessage("Startup part 1 done...")

    ' ... and more as needed...

    splash.Hide()
    Application.Run(myMainForm)
End Sub

当 .NET CLR 启动应用程序时,它会创建一个“主”线程并开始在该线程上执行 main()。 最后的 Application.Run(myMainForm) 做了两件事:

  1. 启动 Windows“消息泵”,使用已执行 main() 的线程作为 GUI 线程。
  2. 将您的“主表单”指定为应用程序的“关闭表单”。 如果用户关闭该表单,则 Application.Run() 终止,控制权返回到 main(),您可以在其中执行任何所需的关闭操作。

不需要生成一个线程来处理启动窗口,事实上这是一个坏主意,因为这样您就必须使用线程安全技术来更新 main() 中的启动内容。

如果您需要其他线程在应用程序中执行后台操作,您可以从 main() 生成它们。 只需记住将 Thread.IsBackground 设置为 True,这样它们就会在主/GUI 线程终止时死亡。 否则,您将必须自己安排终止所有其他线程,否则当主线程终止时,它们将使您的应用程序保持活动状态(但没有 GUI)。

One simple way is the use something like this as main():

<STAThread()> Public Shared Sub Main()

    splash = New frmSplash
    splash.Show()

    ' Your startup code goes here...

    UpdateSplashAndLogMessage("Startup part 1 done...")

    ' ... and more as needed...

    splash.Hide()
    Application.Run(myMainForm)
End Sub

When the .NET CLR starts your application, it creates a 'main' thread and starts executing your main() on that thread. The Application.Run(myMainForm) at the end does two things:

  1. Starts the Windows 'message pump', using the thread that has been executing main() as the GUI thread.
  2. Designates your 'main form' as the 'shutdown form' for the application. If the user closes that form, then the Application.Run() terminates and control returns to your main(), where you can do any shutdown you want.

There is no need to spawn a thread to take care of the splash window, and in fact this is a bad idea, because then you would have to use thread-safe techniques to update the splash contents from main().

If you need other threads to do background operations in your application, you can spawn them from main(). Just remember to set Thread.IsBackground to True, so that they will die when the main / GUI thread terminates. Otherwise you will have to arrange to terminate all your other threads yourself, or they will keep your application alive (but with no GUI) when the main thread terminates.

羅雙樹 2024-07-12 01:08:04

我认为使用诸如 aku'sGuy 的 是可行的方法,但从具体示例中可以得出一些结论:

  1. 基本前提是尽快在单独的线程上显示您的启动画面。 这就是我倾向于的方式,类似于 aku 所说明的方式,因为这是我最熟悉的方式。 我不知道Guy提到的VB函数。 而且,即使认为它是一个 VB 库,他是对的——最终都是 IL。 所以,即使感觉肮脏,也并没有那么糟糕! :) 我认为您需要确保 VB 在该覆盖中提供一个单独的线程,或者您自己创建一个线程 - 一定要研究这一点。

  2. 假设您创建另一个线程来显示此启动画面,您将需要小心跨线程 UI 更新。 我提出这个问题是因为你提到了更新进度。 基本上,为了安全起见,您需要使用委托在启动表单上调用更新函数(您创建的)。 您将该委托传递给初始屏幕表单对象上的 Invoke 函数。 事实上,如果您直接调用启动窗体来更新其上的进度/UI 元素,只要您在 .Net 2.0 CLR 上运行,您就会收到异常。 根据经验,表单上的任何 UI 元素都必须由创建它的线程进行更新——这就是 Form.Invoke 所保证的。

最后,我可能会选择在代码的 main 方法中创建启动画面(如果不使用 VB 重载)。 对我来说,这比让主窗体执行对象的创建并与其紧密绑定要好。 如果您采用这种方法,我建议创建一个闪屏实现的简单界面(例如 IStartupProgressListener),它通过成员函数接收启动进度更新。 这将允许您根据需要轻松地换入/换出任一类,并很好地解耦代码。 如果您在启动完成时发出通知,启动窗体还可以知道何时自行关闭。

I think using some method like aku's or Guy's is the way to go, but a couple of things to take away from the specific examples:

  1. The basic premise would be to show your splash on a separate thread as soon as possible. That's the way I would lean, similar to what aku's illustrated, since it's the way I'm most familiar with. I was not aware of the VB function Guy mentioned. And, even thought it's a VB library, he is right -- it's all IL in the end. So, even if it feels dirty it's not all that bad! :) I think you'll want to be sure that either VB provides a separate thread for in that override or that you create one yourself -- definitely research that.

  2. Assuming you create another thread to display this splash, you will want to be careful of cross thread UI updates. I bring this up because you mentioned updating progress. Basically, to be safe, you need to call an update function (that you create) on the splash form using a delegate. You pass that delegate to the Invoke function on your splash screen's form object. In fact if you call the splash form directly to update progress/UI elements on it, you'll get an exception provided you are running on the .Net 2.0 CLR. As a rule of thumb, any UI element on a form must be updated by the thread that created it -- that's what Form.Invoke insures.

Finally, I would likely opt to create the splash (if not using the VB overload) in the main method of your code. To me this is better than having the main form perform creation of the object and to be so tightly bound to it. If you take that approach, I'd suggest creating a simple interface that the splash screen implements -- something like IStartupProgressListener -- which receives start-up progress updates via a member function. This will allow you to easily swap in/out either class as needed, and nicely decouples the code. The splash form can also know when to close itself if you notify when start-up is complete.

月野兔 2024-07-12 01:08:04

这是一个老问题,但当我尝试为 WPF 寻找可以包含动画的线程启动屏幕解决方案时,我不断遇到它。

这是我最终拼凑而成的:

App.XAML:

<Application Startup="ApplicationStart" …

App.XAML.cs:

void ApplicationStart(object sender, StartupEventArgs e)
{
        var thread = new Thread(() =>
        {
            Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show()));
            Dispatcher.Run();
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();

        // call synchronous configuration process
        // and declare/get reference to "main form"

        thread.Abort();

        mainForm.Show();
        mainForm.Activate();
  }

This is an old question, but I kept coming across it when trying to find a threaded splash screen solution for WPF that could include animation.

Here is what I ultimately pieced together:

App.XAML:

<Application Startup="ApplicationStart" …

App.XAML.cs:

void ApplicationStart(object sender, StartupEventArgs e)
{
        var thread = new Thread(() =>
        {
            Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show()));
            Dispatcher.Run();
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();

        // call synchronous configuration process
        // and declare/get reference to "main form"

        thread.Abort();

        mainForm.Show();
        mainForm.Activate();
  }
穿透光 2024-07-12 01:08:04

我建议在 aku 提供的答案中的最后一个 Show(); 之后直接调用 Activate();

引用MSDN:

激活表单会将其带到
如果这是活动的,则位于前面
应用程序,或者它闪烁窗口
标题(如果这不是活动的)
应用。 表格必须可见
使此方法产生任何效果。

如果您不激活主窗体,它可能会显示在任何其他打开的窗口后面,使其看起来有点愚蠢。

I recommend calling Activate(); directly after the last Show(); in the answer provided by aku.

Quoting MSDN:

Activating a form brings it to the
front if this is the active
application, or it flashes the window
caption if this is not the active
application. The form must be visible
for this method to have any effect.

If you don't activate your main form, it may be displayed behind any other open windows, making it look a bit silly.

烟燃烟灭 2024-07-12 01:08:04

在搜索了 Google 和 SO 的解决方案后,这是我最喜欢的:
http://bytes.com/topic/c-sharp/answers/277446- winform-startup-splash-screen

FormSplash.cs:

public partial class FormSplash : Form
{
    private static Thread _splashThread;
    private static FormSplash _splashForm;

    public FormSplash() {
        InitializeComponent();
    }

    /// <summary>
    /// Show the Splash Screen (Loading...)
    /// </summary>
    public static void ShowSplash()
    {
        if (_splashThread == null)
        {
            // show the form in a new thread
            _splashThread = new Thread(new ThreadStart(DoShowSplash));
            _splashThread.IsBackground = true;
            _splashThread.Start();
        }
    }

    // called by the thread
    private static void DoShowSplash()
    {
        if (_splashForm == null)
            _splashForm = new FormSplash();

        // create a new message pump on this thread (started from ShowSplash)
        Application.Run(_splashForm);
    }

    /// <summary>
    /// Close the splash (Loading...) screen
    /// </summary>
    public static void CloseSplash()
    {
        // need to call on the thread that launched this splash
        if (_splashForm.InvokeRequired)
            _splashForm.Invoke(new MethodInvoker(CloseSplash));

        else
            Application.ExitThread();
    }
}

Program.cs:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        // splash screen, which is terminated in FormMain
        FormSplash.ShowSplash();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        // this is probably where your heavy lifting is:
        Application.Run(new FormMain());
    }
}

FormMain.cs

    ...

    public FormMain()
    {
        InitializeComponent();            

        // bunch of database access, form loading, etc
        // this is where you could do the heavy lifting of "loading" the app
        PullDataFromDatabase();
        DoLoadingWork();            

        // ready to go, now close the splash
        FormSplash.CloseSplash();
    }

我遇到了问题 Microsoft.VisualBasic 解决方案 - 在 XP 上可以找到,但在 Windows 2003 终端服务器上,主应用程序窗体将在后台显示(在启动屏幕之后),并且任务栏将闪烁。 在代码中将窗口置于前台/焦点是另一堆蠕虫,您可以通过 Google/SO 找到它。

After looking all over Google and SO for solutions, this is my favorite:
http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen

FormSplash.cs:

public partial class FormSplash : Form
{
    private static Thread _splashThread;
    private static FormSplash _splashForm;

    public FormSplash() {
        InitializeComponent();
    }

    /// <summary>
    /// Show the Splash Screen (Loading...)
    /// </summary>
    public static void ShowSplash()
    {
        if (_splashThread == null)
        {
            // show the form in a new thread
            _splashThread = new Thread(new ThreadStart(DoShowSplash));
            _splashThread.IsBackground = true;
            _splashThread.Start();
        }
    }

    // called by the thread
    private static void DoShowSplash()
    {
        if (_splashForm == null)
            _splashForm = new FormSplash();

        // create a new message pump on this thread (started from ShowSplash)
        Application.Run(_splashForm);
    }

    /// <summary>
    /// Close the splash (Loading...) screen
    /// </summary>
    public static void CloseSplash()
    {
        // need to call on the thread that launched this splash
        if (_splashForm.InvokeRequired)
            _splashForm.Invoke(new MethodInvoker(CloseSplash));

        else
            Application.ExitThread();
    }
}

Program.cs:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        // splash screen, which is terminated in FormMain
        FormSplash.ShowSplash();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        // this is probably where your heavy lifting is:
        Application.Run(new FormMain());
    }
}

FormMain.cs

    ...

    public FormMain()
    {
        InitializeComponent();            

        // bunch of database access, form loading, etc
        // this is where you could do the heavy lifting of "loading" the app
        PullDataFromDatabase();
        DoLoadingWork();            

        // ready to go, now close the splash
        FormSplash.CloseSplash();
    }

I had issues with the Microsoft.VisualBasic solution -- Worked find on XP, but on Windows 2003 Terminal Server, the main application form would show up (after the splash screen) in the background, and the taskbar would blink. And bringing a window to foreground/focus in code is a whole other can of worms you can Google/SO for.

甩你一脸翔 2024-07-12 01:08:04

嗯,对于我过去部署的 ClickOnce 应用程序,我们使用 Microsoft.VisualBasic 命名空间来处理启动屏幕线程。 您可以在 .NET 2.0 中引用和使用 C# 中的 Microsoft.VisualBasic 程序集,它提供了许多很好的服务。

  1. 让主窗体继承 Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. 重写“OnCreateSplashScreen”方法,如下所示:

    受保护的覆盖 void OnCreateSplashScreen() 
      { 
          this.SplashScreen = new SplashForm(); 
          this.SplashScreen.TopMost = true; 
      } 
      

非常简单,它在加载过程中显示您的 SplashForm(您需要创建),然后在主窗体加载完成后自动关闭它。

这确实使事情变得简单,并且 VisualBasic.WindowsFormsApplicationBase 当然经过了 Microsoft 的充分测试,并且具有许多功能,可以使您在 Winforms 中的生活变得更加轻松,即使在 100% 的应用程序中也是如此。 C#。

归根结底,这都是 IL 和字节码,所以为什么不使用它呢?

Well, for a ClickOnce app that I deployed in the past, we used the Microsoft.VisualBasic namespace to handle the splash screen threading. You can reference and use the Microsoft.VisualBasic assembly from C# in .NET 2.0 and it provides a lot of nice services.

  1. Have the main form inherit from Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Override the "OnCreateSplashScreen" method like so:

    protected override void OnCreateSplashScreen()
    {
        this.SplashScreen = new SplashForm();
        this.SplashScreen.TopMost = true;
    }
    

Very straightforward, it shows your SplashForm (which you need to create) while loading is going on, then closes it automatically once the main form has completed loading.

This really makes things simple, and the VisualBasic.WindowsFormsApplicationBase is of course well tested by Microsoft and has a lot of functionality that can make your life a lot easier in Winforms, even in an application that is 100% C#.

At the end of the day, it's all IL and bytecode anyway, so why not use it?

半边脸i 2024-07-12 01:08:04

诀窍是创建负责启动屏幕显示的单独线程。
当您运行应用程序时,.net 创建主线程并加载指定的(主)表单。 要隐藏辛苦的工作,您可以隐藏主窗体,直到加载完成。

假设 Form1 - 是您的主窗体,SplashForm 是顶级窗体,边框漂亮的启动窗体:

private void Form1_Load(object sender, EventArgs e)
{
    Hide();
    bool done = false;
    ThreadPool.QueueUserWorkItem((x) =>
    {
        using (var splashForm = new SplashForm())
        {
            splashForm.Show();
            while (!done)
                Application.DoEvents();
            splashForm.Close();
        }
    });

    Thread.Sleep(3000); // Emulate hardwork
    done = true;
    Show();
}

The trick is to to create separate thread responsible for splash screen showing.
When you run you app .net creates main thread and loads specified (main) form. To conceal hard work you can hide main form until loading is done.

Assuming that Form1 - is your main form and SplashForm is top level, borderles nice splash form:

private void Form1_Load(object sender, EventArgs e)
{
    Hide();
    bool done = false;
    ThreadPool.QueueUserWorkItem((x) =>
    {
        using (var splashForm = new SplashForm())
        {
            splashForm.Show();
            while (!done)
                Application.DoEvents();
            splashForm.Close();
        }
    });

    Thread.Sleep(3000); // Emulate hardwork
    done = true;
    Show();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文