【问题标题】:Making a splash screen制作启动画面
【发布时间】:2015-12-27 07:07:39
【问题描述】:

启动画面的概念并没有让我觉得应该如此复杂,但是我在绘制整个启动画面时遇到了麻烦。

假设我有两个System.Windows.Forms.Forms:RealForm 和 SplashScreen。 RealForm 包含初始 GUI。在 RealForm 的加载过程中(即在Load 事件的事件处理程序中),RealForm 创建到数据库的连接,然后测试该连接。这可能需要几秒钟,所以在我做这一切之前,我试着抛出一个像这样的启动画面:

private void RealForm_Load(object sender, EventArgs e)
{
    SplashScreen splash = new SplashScreen();
    splash.Show();
    loadAndCheckDatabase();
    splash.Close();
}

问题在于,启动画面仅被部分绘制,因此它看起来并不像启动画面。知道为什么吗?我该怎么办?

对于奖励积分,有没有人知道在哪里可以找到对典型创建、使用和破坏形式中发生的所有事件系列的解释?上述问题可能是因为我不明白事情发生的顺序或在哪里挂钩表单事件。


更新 关闭,但不完全:寻找更多建议。这是当前代码:

private SplashScreen splash = new SplashScreen();

private void RealForm_Load(object sender, EventArgs e)
{

    splash.Show();

    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
         (worker_RunWorkerCompleted);
    worker.RunWorkerAsync();
    this.Visible = false;

}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    splash.Close();
    this.Visible = true;
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    loadAndCheckDatabase();
}

不幸的是,this.Visible = false 没有做任何事情,因为它在我无法控制的代码中自动设置为 true。因此,为了解决这个问题,我将this.Visible = false 放入表单的Shown 事件的处理程序中。但是,正如您可能猜到的那样,您仍然可以在一瞬间看到表单......所以这并不是一个很好的解决方案。

当我仍在负载处理程序中时,有什么方法可以等待工作线程?

【问题讨论】:

    标签: c# winforms splash-screen


    【解决方案1】:

    您正在显示启动屏幕并在同一线程上检查您的数据库。线程一次只能做一件事。

    解决此问题的一种快速而廉价的方法是让loadAndCheckDatabase() 定期致电Application.DoEvents()。但是,这是一个便宜的解决方案。

    您确实想在自己的线程上运行 loadAndCheckDatabase(),BackgroundWorker 是一个很好的简单方法。

    【讨论】:

    • 这可能很明显,但请确保您的 loadAndCheckDatabase 调用中没有任何 UI 代码,甚至没有消息框,否则您可能会遇到麻烦。
    • 我什至不会打扰 Application.DoEvents(),只需使用 BackgroundWorker。如果你想在 UI 保持响应的同时执行一个简单的逻辑单元,请使用 BackgroundWorker。如果您希望 UI 线程去执行在 UI 的消息队列中等待的所有内容,即使如果用户设法将执行随机其他逻辑位的鼠标单击排队,这会干扰您当前的操作一种看起来像是在运行大量线程,但实际上只有一个在运行的方式,然后使用 Application.DoEvents()。
    【解决方案2】:

    像我一样,您可能会在事后才创建它,并且不想经历重新设计代码以适应多线程架构的所有麻烦......

    首先创建一个名为 SpashScreen 的新表单,在属性中单击 BackgroundImage 并导入您想要的任何图像。还要将 FormBorderStyle 设置为 None,这样您就不能单击 x 来关闭屏幕。

        public Form1()
        {
            InitializeComponent();
    
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerAsync();  // start up your spashscreen thread
            startMainForm();      // do all your time consuming stuff here  
            bw.CancelAsync();     // close your splashscreen thread  
        }
    
    
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
    
            SplashScreen ss = new SplashScreen();
            ss.Show();
    
            while (!worker.CancellationPending) //just hangout and wait
            {
                Thread.Sleep(1000);
            }
    
            if (worker.CancellationPending)
            {
                ss.Close();
                e.Cancel = true;
            }
    
        }
    

    这不支持进度条或任何花哨的东西,但我相信它可以调整。

    【讨论】:

    • 感谢您的样品。最后一个“if”语句是不必要的 - 除非 worker.CancellationPending 为真,否则代码不会到达那里,给定上述 while 循环。
    【解决方案3】:

    尝试将 loadAndCheckDatabase 调用放在后台线程中,将启动画面的关闭移到那里,或者只是在启动画面中使用计时器将其关闭。在后台线程中工作,所有 UI 功能将能够不间断地运行。

    【讨论】:

      【解决方案4】:

      您应该在不同的线程中运行启动画面,这应该允许它正确绘制。

      看看这里:

      http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx

      【讨论】:

      • 由于闪屏是一个 UI 组件,它确实应该运行在 UI 线程(WinForms 应用程序的主线程)上。 loadAndCheckDatabase() 应该被推送到工作线程。
      • UI 组件确实不能也不应该在后台线程上运行——它们应该只在主线程上运行。
      • 对,后台线程没有UI,会得到一堆跨线程调用异常。
      • 我认为这看起来很有希望。你能帮我理解为什么这是一件坏事吗?这似乎更容易,而且由于 SplashScreen 并没有真正任何事情,我不明白为什么它不能进入​​不同的线程。
      • 从技术上讲,您可以愉快地在两个单独的线程上运行两个窗口。这没有问题。只有当您尝试与在不同线程上创建的 UI 元素进行交互时,才会出现问题。通常,将所有应用程序初始化转移到后台工作程序中比抛出一个线程来运行启动屏幕要复杂得多。
      【解决方案5】:

      为什么不在您运行应用程序时打开一个表单,该表单将您需要加载的任何内容加载到类中,然后在完成加载后打开您的主表单并将类发送到其中?或者,您可以使用单例加载所有内容。

      在您的 Program.cs 中:

          [STAThread]
          static void Main()
          {
              Application.EnableVisualStyles();
              Application.SetCompatibleTextRenderingDefault(false);
              Application.Run(new SplashScreen());
          }
      

      然后在 SplashScreen 中:

          public SplashScreen()
          {
              InitializeComponent();
              LoadEverything();
              this.Visible = false;
              MainForm mainForm = new MainForm(LoadedClass);
              mainForm.ShowDialog();
              this.Close();
          }
      

      我需要更新这个:

      这是工作代码(以上只是概念)。

          public SplashScreen()
          {
              InitializeComponent();
              _currentDispatcher = Dispatcher.CurrentDispatcher;
      
              // This is just for the example - start a background method here to call
              // the LoadMainForm rather than the timer elapsed
              System.Timers.Timer loadTimer = new System.Timers.Timer(2000);
              loadTimer.Elapsed += LoadTimerElapsed;
              loadTimer.Start();
          }
      
          public void LoadMainForm()
          {
              // Do your loading here
              MainForm mainForm = new MainForm();
      
              Visible = false;
              mainForm.ShowDialog();
              System.Timers.Timer closeTimer = new System.Timers.Timer(200);
              closeTimer.Elapsed += CloseTimerElapsed;
      
              closeTimer.Start();
          }
      
          private Dispatcher _currentDispatcher;
      
          private void CloseTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
          {
              if (sender is System.Timers.Timer && sender != null)
              {
                  (sender as System.Timers.Timer).Stop();
                  (sender as System.Timers.Timer).Dispose();
              }
              _currentDispatcher.BeginInvoke(new Action(() => Close()));
          }
      
          private void LoadTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
          {
              if (sender is System.Timers.Timer && sender != null)
              {
                  (sender as System.Timers.Timer).Stop();
                  (sender as System.Timers.Timer).Dispose();
              }
              _currentDispatcher.BeginInvoke(new Action(() => LoadMainForm()));
          }
      

      【讨论】:

        【解决方案6】:

        您可以在 .Net Framework 4.5 中使用 await 命令 在任务完成之前,您的表单将不可见

            private void YourForm_Load(object sender, EventArgs e)
            {
                //call SplashScreen form
                SplashScreen splash = new SplashScreen();
                splash.Show();
                Application.DoEvents();
                //Run your long task while splash screen is displayed i.e. loadAndCheckDatabase
                Task processLongTask = loadAndCheckDatabase();
                //wait for the task to be completed
                processLongTask.Wait();
                splash.Close();
        
                //...
            }
        

        【讨论】:

        • 上述代码绝对没有优势,因为主线程立即阻塞等待任务完成。你也可以在主线程中做长任务,它不会有任何区别。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-14
        • 2023-03-22
        • 1970-01-01
        • 1970-01-01
        • 2014-08-02
        相关资源
        最近更新 更多