C#中的多线程闪屏?

时间:2008-09-08 00:10:07

标签: c# .net winforms multithreading splash-screen

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

我一直在阅读很多关于线程的内容,但是我会迷失在哪里(main()方法?)。我也想念Application.Run()如何工作,这是应该创建的线程吗?现在,如果带有系统托盘控件的表单是“活动”表单,那么它应该来自那里吗?在形式完成之前不会加载吗?

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

12 个答案:

答案 0 :(得分:46)

诀窍是创建单独的线程,负责显示闪屏 运行时,app .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();
}

答案 1 :(得分:46)

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

  1. 让主表单继承自Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. 覆盖“OnCreateSplashScreen”方法,如下所示:

    protected override void OnCreateSplashScreen()
    {
        this.SplashScreen = new SplashForm();
        this.SplashScreen.TopMost = true;
    }
    
  3. 非常简单,它会在加载过程中显示您的SplashForm(您需要创建),然后在主窗体完成加载后自动关闭它。

    这真的让事情变得简单,VisualBasic.WindowsFormsApplicationBase当然经过了微软的测试,并且有很多功能可以让你在Winforms中的生活变得更轻松,即使在100%C#的应用程序中也是如此。

    在一天结束时,无论如何都是IL和bytecode,为什么不使用呢?

答案 2 :(得分:13)

在遍布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的蠕虫。

答案 3 :(得分:9)

这是一个老问题,但在尝试为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();
  }

答案 4 :(得分:8)

我建议在aku提供的答案中的最后Activate();之后直接致电Show();

引用MSDN:

  

激活表单会将其带到   前面,如果这是活跃的   应用程序,或它闪烁的窗口   如果这不是活动的标题   应用。表格必须可见   对于这种方法有任何影响。

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

答案 5 :(得分:6)

我认为使用aku'sGuy's之类的方法是可行的方法,但要从具体示例中删除一些内容:

  1. 基本前提是尽快在单独的帖子上展示你的情节。这就是我倾向的方式,类似于aku的说明,因为这是我最熟悉的方式。我不知道Guy提到的VB函数。而且,即使认为它是一个 VB 库,他也是对的 - 最终它都是IL。所以,即使它感觉也不是那么糟糕! :)我想你要确保VB为这个覆盖提供一个单独的线程,或者你自己创建一个 - 绝对研究它。

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

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

答案 6 :(得分:5)

一种简单的方法是使用像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. 使用已执行main()作为GUI线程的线程启动Windows'消息泵'。
  2. 将“主要表单”指定为应用程序的“关闭表单”。如果用户关闭该表单,则Application.Run()终止并控制返回到main(),您可以在其中执行任何关闭。
  3. 没有必要生成一个线程来处理启动窗口,事实上这是一个坏主意,因为那时你必须使用线程安全技术来更新main()中的启动内容。 / p>

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

答案 7 :(得分:4)

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

Yet Another Splash Screen in C#

答案 8 :(得分:4)

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

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

答案 9 :(得分:3)

我非常喜欢Aku的答案,但是代码适用于C#3.0及以上,因为它使用了lambda函数。对于需要在C#2.0中使用代码的人来说,这里是使用匿名委托而不是lambda函数的代码。您需要一个名为formSplash的最顶层winform FormBorderStyle = None。表单的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();
  }

答案 10 :(得分:2)

我不同意推荐WindowsFormsApplicationBase的其他答案。根据我的经验,它可以减慢你的应用程序。确切地说,当它与启动画面并行运行表单的构造函数时,它会推迟表单的已显示事件。

考虑一个应用程序(没有启动画面),构造函数需要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事件并行运行启动。

答案 11 :(得分:0)

实际上这里不需要多线程。

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

然后让表单在连接到eventhandler的方法中相应地更新启动屏幕。

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

通过这种方式,你可以拥有漂亮的闪屏,而不会出现任何多线程问题。

实际上,你甚至可以在启动表格上支持gif图像。为了使它工作,请在处理程序中调用Application.DoEvents():

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