如何在Windows窗体应用程序中构建启动画面?

76

我需要在应用程序启动时显示闪屏界面几秒钟。有人知道如何实现吗?

非常感谢您的帮助。


14
+1是因为迄今为止没有一个真正好的“溅泼效果”的例子。我从不同的博客和网站上找到了一些混乱的实现方式,但在经历了很多麻烦之后,终于出现了一个好的实现方法。这个问题确实很好。 - Anderson Matos
有另一种选项,不需要使用计时器或在主窗体和启动窗体之间保持引用关系:https://dev59.com/vFzUa4cB1Zd3GeqP6caj - Veldmuis
可能是启动画面示例的重复问题。 - Jim G.
以下示例来自Telerik,使用了ShapedForm控件,但是将其更改为普通的Windows表单。这是我见过的最简单和最好的方法。http://www.telerik.com/support/kb/winforms/forms-and-dialogs/details/add-splashscreen-to-your-application - driverobject
尝试这个解决方案 https://dev59.com/bWUo5IYBdhLWcg3wrhPj#15836105 - 4lex
显示剩余2条评论
13个回答

99
首先,创建一个带有图像的无边框、不可移动的窗体作为闪屏,将其初始化显示在屏幕中央,颜色根据需要设置。这些都可以在设计器中设置,具体来说,您需要:
  • 将窗体的ControlBox、MaximizeBox、MinimizeBox和ShowIcon属性设置为“False”
  • 将StartPosition属性设置为“CenterScreen”
  • 将FormBorderStyle属性设置为“None”
  • 将窗体的MinimumSize和MaximumSize设置为其初始大小。
然后,您需要决定在哪里显示它和在哪里关闭它。这两个任务需要在程序的主要启动逻辑的相反方向上发生。这可以在应用程序的main()例程中完成,或者可能在您的主应用程序窗体的Load处理程序中完成;无论你在哪里创建大量昂贵的对象、从硬盘读取设置以及在主应用程序屏幕显示之前做很长时间的工作。
然后,您只需要创建一个窗体实例,Show()它,并在启动初始化期间保留对它的引用。一旦您的主窗体加载完成,就Close()它。
如果您的闪屏将有一个动画图像,则窗口也需要进行“双缓冲”,并且您需要确保所有初始化逻辑都发生在GUI线程之外(这意味着您不能在主窗体的Load处理程序中拥有主要加载逻辑;您将不得不创建一个BackgroundWorker或其他线程化例程)。

8
启动画面应该在单独的线程上运行,以便在应用程序显示时运行后台进程,使应用程序能够执行工作(例如连接到数据库、加载文件)。它向用户表明应用程序正在运行,并且不会浪费他们的时间,只是等待 X 秒钟。 - Scott Wylie
1
我使用新的应用程序循环(Application.Run)和单例模式创建了一个启动画面,这样我就可以在登录表单显示之前创建启动画面,并在其关闭。+1获取详细信息。 - Anderson Matos
@ALMMa - 我在构建启动屏幕实现时几乎做了同样的事情,包括通过命令或检测到某些类型的窗口(如Windows安全登录对话框)来明确隐藏或“降低”它的能力。 - KeithS
1
缺少一个可工作的脚本示例。 - JinSnow
@JinSnow - “如何做到这一点”并不总是最好回答“像这样:”后跟一个代码块。例如,最佳的启动屏幕实现可以根据其插入的应用程序的加载行为而显着变化。我们也不鼓励在这里要求“gimme t3h c0d3z”。我的答案(以及许多其他答案)会给出您需要遵循的步骤(包括要设置的特定属性),以及在将其插入初始化逻辑时要注意的各种注意事项。寻求更具体帮助的问题应该展示他们已经得到了什么。 - KeithS

5

以下是一些指南步骤...

  1. 创建一个无边框的窗体(这将成为您的启动画面)。
  2. 在应用程序启动时,启动一个定时器(间隔几秒钟)。
  3. 显示您的启动画面。
  4. 在Timer.Tick事件中,停止定时器并关闭闪屏窗体 - 然后显示您的主应用程序窗体。

试试这个方法,如果遇到问题,请回来询问与您的问题相关的更具体的问题。


5

创建启动画面的简单易行解决方案

  1. 打开一个名为"SPLASH"的新窗体
  2. 更改背景图片为任何你想要的
  3. 选择进度条
  4. 选择计时器

现在在计时器中设置计时器间隔:

private void timer1_Tick(object sender, EventArgs e)
{
    progressBar1.Increment(1);
    if (progressBar1.Value == 100) timer1.Stop();        
}

在表单1中使用名称为"FORM-1"的新表单,并使用以下命令。

注意:在打开表单1之前,闪屏表单将起作用。

  1. add this library

    using System.Threading;
    
  2. create function

    public void splash()
    {     
        Application.Run(new splash());
    }
    
  3. use following command in initialization like below.

    public partial class login : Form
    {     
        public login()
        {
            Thread t = new Thread(new ThreadStart(splash));
            t.Start();
            Thread.Sleep(15625);
    
            InitializeComponent();
    
            enter code here
    
            t.Abort();
        }
    }
    

http://solutions.musanitech.com/c-create-splash-screen/


1
这是我找到的最简单和最好的例子。完美地运行。 - Ben Winding
1
@TylerDurden,“simplest”和“best”?不如看看这个(https://dev59.com/bWUo5IYBdhLWcg3wrhPj#15836105)(我个人认为应该谨慎地称之为“足够好”)。 - Sinatr
谢谢@Sinatr,下次我可能会使用那个实现,它看起来更加灵活。当我开始学习C#时,这个解决方案只是更简单而已,我最初正在寻找一个简单的解决方案,它不需要委托和太多线程。我也学到了随着你学习新方法,“最佳解决方案”会发生变化;)感谢分享。 - Ben Winding
1
如何消除 t.abort() 抛出的异常? - Vilas Joshi
抱歉,new splash() 是什么? - Alex S.

3

我希望有一个启动画面,直到主程序界面准备好显示,所以计时器等工具对我来说没有用处。我也想尽可能简单地保持它。 我的应用程序启动方式为(缩写):

static void Main()
{
    Splash frmSplash = new Splash();
    frmSplash.Show();
    Application.Run(new ReportExplorer(frmSplash));
}

接下来,ReportExplorer具有以下功能:

public ReportExplorer(Splash frmSplash)
{
    this.frmSplash = frmSplash;
    InitializeComponent();
}

最终,在所有初始化完成后:
if (frmSplash != null) 
{
     frmSplash.Close();
     frmSplash = null;
}

也许我漏掉了什么,但这似乎比搞线程和定时器要容易得多。

适用于简单情况,其中线程不重要或不必要。非常适合于初始表单为登录表单,后续表单为“主”表单,但在登录表单和主表单之间有大量数据加载的情况下。 - GoldBishop
唯一的问题是当您在启动画面上有一些动画时。例如,使用跑马灯样式的进度条将无法进行动画处理。 - Maxter

2
创建启动画面
private void timer1_Tick(object sender, EventArgs e)
{
    counter++;
    progressBar1.Value = counter *5;
    // label2.Text = (5*counter).ToString();
    if (counter ==20)
    {
        timer1.Stop();
        this.Close();
    }
}
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.GradientInactiveCaption;
this.ClientSize = new System.Drawing.Size(397, 283);
this.ControlBox = false;
this.Controls.Add(this.label2);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.label1);
this.ForeColor = System.Drawing.SystemColors.ControlLightLight;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "Splash";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
this.PerformLayout();

在你的应用程序中,
sp = new Splash();
sp.ShowDialog();

2
这里的其他答案已经介绍得很好了,但值得知道的是,Visual Studio内置了闪屏功能:如果您打开窗体应用程序的项目属性并查看“应用程序”选项卡,则底部有一个“闪屏屏幕:”选项。您只需选择您希望显示为闪屏屏幕的应用程序中的表单,它将在应用程序启动时处理显示,并在显示主表单后隐藏它。
您仍然需要按上述方式设置您的表单(具有正确的边框、定位、大小等)。

这听起来很理想,但我在VS 2013中找不到这个选项。我在网上能找到的唯一参考资料都是关于Visual Basic的,也许只支持那个? - Giles
1
嗨@Giles,那很可能是这种情况,我只在VB应用程序中使用过它。 - tomRedox

2

其他答案并没有完全满足我的需求。接下来请看我解决问题的方案。

我希望有一个启动画面,从0%不透明度到100%不透明度淡入,最少显示2000毫秒(以展示完整的淡入效果)。一旦所有内容都准备好了,我希望启动画面再显示500毫秒,同时主屏幕在启动画面后面显示。然后我希望启动画面消失,只留下主屏幕运行。

请注意,我在winforms中使用MVP模式。如果您不使用MVP,您需要简化以下示例。

长话短说,您需要创建一个继承自ApplicationContextAppContext类。我将其放在我的Program.cs中,如下所示:

static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.SetHighDpiMode(HighDpiMode.SystemAware);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new AppContext());
    }
}

public class AppContext : ApplicationContext
{
    private IMainPresenter _mainPresenter;
    private bool _ready;

    public AppContext()
    {
        _ready = false;

        using (ISplashPresenter splashPresenter = new SplashPresenter(new SplashView()))
        {
            Stopwatch sw = Stopwatch.StartNew();

            _mainPresenter = new MainPresenter(new MainView());
            _mainPresenter.Closed += MainPresenter_Closed;

            new Thread(() =>
                {
                    // !!! Do work here !!!

                    if (sw.ElapsedMilliseconds < 2000)
                        Thread.Sleep(2000 - (int)sw.ElapsedMilliseconds);

                    _ready = true;
                })
                .Start();

            while (!_ready)
            {
                Application.DoEvents();
                Thread.Sleep(1);
            }

            _mainPresenter.Show();

            _ready = false;

            new Thread(() =>
                {
                    Thread.Sleep(500);

                    _ready = true;
                })
                .Start();

            while (!_ready)
            {
                Application.DoEvents();
                Thread.Sleep(1);
            }
        }
    }

    private void MainPresenter_Closed(object sender, EventArgs e)
    {
        ExitThread();
    }
}

这里有一些具体的实现细节我没有详细说明,比如ISplashPresenter实现了IDisposable接口以及淡入效果的具体管理方式。如果有足够多的人请求,我会编辑这个回答并提供完整的示例。


1

首先,您应该创建一个带有或不带边框的表单(无边框更适合这些事情)。

public class SplashForm : Form
{
    Form _Parent;
    BackgroundWorker worker;
    public SplashForm(Form parent)
    {
         InitializeComponent();
         BackgroundWorker worker = new BackgroundWorker();
         this.worker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.worker _DoWork);
         backgroundWorker1.RunWorkerAsync();
         _Parent = parent;
    }
    private void worker _DoWork(object sender, DoWorkEventArgs e)
    {
         Thread.sleep(500);
         this.hide();
         _Parent.show();
    }     
}

在主函数中,你应该使用它

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

2
这很糟糕。这将使您的SplashForm成为应用程序的主窗体。这意味着当您关闭它时,应用程序将停止。 - Matias Cicero

1
也许有点晚回答,但我想分享我的方法。我在主程序中使用线程找到了一种简单的方法,适用于Winform应用程序。
假设你有一个名为“splashscreen”的表单,其中包含动画,以及一个名为“main”的表单,其中包含所有应用程序代码。
 [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Thread mythread;
        mythread = new Thread(new ThreadStart(ThreadLoop));
        mythread.Start();
        Application.Run(new MainForm(mythread));           
    }

    public static void ThreadLoop()
    {
        Application.Run(new SplashScreenForm());           
    }

在你的主表单的构造函数中:

 public MainForm(Thread splashscreenthread)
    {
        InitializeComponent();

        //add your constructor code

        splashscreenthread.Abort();            
    }

这样,启动画面只会持续到主窗体加载完成。

您的启动画面应该有自己的动画/显示信息的方式。 在我的项目中,启动画面会启动一个新线程,在每个x毫秒内更改主图片为另一个略微不同的齿轮,从而产生旋转的 illusio。

我的启动画面示例:

int status = 0;
private bool IsRunning = false;
    public Form1()
    {
        InitializeComponent();
        StartAnimation();
    }

    public void StartAnimation()
    {
        backgroundWorker1.WorkerReportsProgress = false;
        backgroundWorker1.WorkerSupportsCancellation = true;
        IsRunning = true;
        backgroundWorker1.RunWorkerAsync();
    }


    public void StopAnimation()
    {
        backgroundWorker1.CancelAsync();
    }

    delegate void UpdatingThreadAnimation();
    public void UpdateAnimationFromThread()
    {

        try
        {
            if (label1.InvokeRequired == false)
            {
                UpdateAnimation();
            }
            else
            {
                UpdatingThreadAnimation d = new UpdatingThreadAnimation(UpdateAnimationFromThread);
                this.Invoke(d, new object[] { });
            }
        }
        catch(Exception e)
        {

        }
    }

 private void UpdateAnimation()
    {
    if(status ==0) 
    {
    // mypicture.image = image1
     }else if(status ==1)
     {
    // mypicture.image = image2
     }
    //doing as much as needed

      status++;
        if(status>1) //change here if you have more image, the idea is to set a cycle of images
        {
            status = 0;
        }
        this.Refresh();
    }

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        while (IsRunning == true)
        {
            System.Threading.Thread.Sleep(100);
            UpdateAnimationFromThread();
        }
    }

希望这能帮到一些人。如果我犯了一些错误,抱歉,因为英语不是我的母语。


"splashscreenthread.Abort(); " 似乎并不总是能够中止线程。 - Maxter

1

这是我对一个关于2011的问题给出的2023年观点。


随着时间的推移,我以许多种方式多次完成了这个任务。当前使用的方法是:
  • 强制创建主窗体Handle,以便通过BeginInvoke将创建启动画面的消息发布到主窗体的消息队列中。这允许主窗体的构造函数返回。通常情况下,句柄(本机hWnd)直到显示才存在。因此,在它仍然隐藏的时候,需要将其强制执行。

  • 覆盖SetVisibleCore(),防止主窗口在启动画面完成处理之前变为可见状态。

startup flow


public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        Debug.Assert(!IsHandleCreated, "Expecting handle is not yet created.");
        // Ordinarily we don't get the handle until
        // window is shown. But we want it now.
        _ = Handle;
        Debug.Assert(IsHandleCreated, "Expecting handle exists.");

        // Call BeginInvoke on the new handle so as not to block the CTor.
        BeginInvoke(new Action(()=> execSplashFlow()));
    }
    protected override void SetVisibleCore(bool value) =>
        base.SetVisibleCore(value && _initialized);

    bool _initialized = false;

    private void execSplashFlow()
    {
        using (var splash = new SplashForm())
        {
            splash.ShowDialog();
        }
        _initialized= true;
        WindowState = FormWindowState.Maximized;
        Show();
    }
}

启动界面示例

异步初始化可以在Splash类本身中执行,或者可以触发事件引起主应用程序执行操作。无论哪种方式,当关闭它自身时,主窗体都会将_initialized布尔值设置为true,现在可以显示启动界面。

public partial class SplashForm : Form
{
    public SplashForm()
    {
        InitializeComponent();
        StartPosition = FormStartPosition.CenterScreen;
        FormBorderStyle = FormBorderStyle.None;
    }
    protected async override void OnVisibleChanged(EventArgs e)
    {
        base.OnVisibleChanged(e);
        if (Visible)
        {
            labelProgress.Text = "Updating installation...";
            progressBar.Value = 5;
            await Task.Delay(1000);
            progressBar.Value = 25;


            // SIMULATED background task like making an API call or loading a
            // database (long-running task that doesn't require the UI thread).
            labelProgress.Text = "Loading avatars...";
            await Task.Delay(1000);

            labelProgress.Text = "Fetching game history...";
            progressBar.Value = 50;
            await Task.Delay(1000);
            labelProgress.Text = "Initializing scenario...";
            progressBar.Value = 75;
            await Task.Delay(1000);
            labelProgress.Text = "Success!";
            progressBar.Value = 100;
            await Task.Delay(1000);
            DialogResult= DialogResult.OK;
        }
    }
}

2
克隆演示代码:https://github.com/IVSoftware/app-with-splash.git - IVSoftware

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接