如何在显示启动屏幕时进行应用程序初始化






4.76/5 (51投票s)
2003年1月22日
6分钟阅读

327622

10379
本文展示了如何在应用程序初始化期间显示启动画面。
- 版本 A(快速便捷版):下载演示项目 - 32 Kb
- 版本 B(多线程版):下载演示项目 - 32 Kb
更新 2003 年 1 月 28 日
本文已根据 Chris Austin 的建议进行了更新(感谢 Chris!也感谢 Phil Bolduc 对 Chris 代码的修复;)并且基于 Jabes 的想法添加了一个替代的多线程方法(感谢 Jabes!)。
引言
本文展示了如何在应用程序初始化期间显示启动画面。本文假设您熟悉 C# 和 VS.NET IDE。
背景
我将大量的初始化代码放在了主窗体的 OnLoad()
重写方法中。代码解析配置文件、创建控件等。当我的应用程序启动时,看起来一团糟:主应用程序窗体没有优雅地弹出并准备好使用。我决定创建一个启动画面,它应该在执行程序后立即弹出。在显示启动画面的同时,我希望执行我的初始化,一旦完成,我就隐藏启动画面并显示主应用程序窗口。本文包含实现此目的的各种方法。
版本 A:快速便捷版
这个版本是我想出的快速便捷的解决方案。感谢 Chris Austin 建议使用 ApplicationContext
类。
准备工作
- 创建一个新的空 Windows 窗体项目。
- 添加一个新的窗体(用于您的启动画面,并命名为 SplashForm)。
1. 移动应用程序的入口点
这不是必需的,但有助于初学者理解 Main()
入口点以及默认放入其中的 Form1 类并非必然耦合。因此,让我们创建一个名为 AppLoader 的新类,并将入口点放在其中。完成后,该类应如下所示:
public class AppLoader { public AppLoader() { } [STAThread] static void Main() { } }
完成后,从您的 Form1.cs 中删除代码,以免出现两个入口点。
2. 创建启动画面
只需创建一个新的 Windows 窗体,在其上放置一个 PictureBox,并设置一些属性使其看起来更漂亮。窗体应在屏幕中央显示(StartPosition
),置顶(TopMost
),并且没有边框(FormBorderStyle
)。至于 PictureBox,将 Dock
属性设置为 Fill,并将 Image
属性设置为您的启动图像。就是这样。在实际应用中,您可能需要编写一些代码来显示应用程序名称、版本、注册用户和版权声明等。
3. 修改 MainForm 的代码
现在我们需要对 Form1 类进行一些修改。由于某些初始化代码可以放在类构造函数中(例如读取文件等),而有些则不能(例如创建控件),因为构造函数中 this
不可用。所以您不能将控件添加到窗体的 Controls
集合中。让我们添加一个新的公共方法(例如 public void PreLoad(){...}
)。此函数应如下所示:
public void PreLoad() { if (_Loaded) { // just return. this code can't execute twice! return; } // do your initialization here // ... // flag that we have loaded all we need. _Loaded = true; }
_Loaded
变量是一个私有的 bool 类型,应初始化为 false
。您肯定应该在主窗体的 OnLoad()
重写方法(在窗体首次显示之前调用)中检查此标志。例如:
protected override void OnLoad(System.EventArgs e) { if (!_Loaded) { // good idea to throw an exception here. // the form shouldn't be shown w/o being initialized! return; } }
4. 修改 AppLoader 类
这是 AppLoader
类的代码。请参阅注释以获取信息。
public class AppLoader { private static ApplicationContext context; private static SplashForm sForm = new SplashForm(); private static MainForm mForm = new MainForm(); [STAThread] static void Main(string[] args) { // first we retrieve an application context for usage in the // OnAppIdle event handler context = new ApplicationContext(); // then we subscribe to the OnAppIdle event... Application.Idle += new EventHandler(OnAppIdle); // ...and show our SplashForm sForm.Show(); // instead of running a window, we use the context Application.Run(context); } private static void OnAppIdle(object sender, EventArgs e) { if(context.MainForm == null) { // first we remove the eventhandler Application.Idle -= new EventHandler(OnAppIdle); // here we preload our form mForm.PreLoad(); // now we set the main form for the context... context.MainForm = mForm; // ...show it... context.MainForm.Show(); // ...and hide the splashscreen. done! sForm.Close(); sForm = null; } } }
版本 B:多线程版
此版本完全基于 Jabes 的想法。有关更多信息,请阅读下面的消息。再次感谢 Jabes 允许我在本文中使用他的代码!
在此版本中,SplashForm 在一个单独的线程中显示,并显示当前的加载状态(如 Photoshop 中所示)。如果启动画面需要显示很长时间,您会希望多线程处理它;通过从自己的消息循环中运行启动画面,屏幕重绘和其他窗口消息可以正确处理,从而给人留下更专业的印象。
准备工作
- 创建一个新的空 Windows 窗体项目。
- 添加一个新的窗体(用于您的启动画面,并命名为 SplashForm)。
- 重复上一节中创建包含应用程序入口点的 AppLoader 类的步骤。暂时将
Main()
的方法体留空。不要忘记从 Form1 代码中删除Main()
方法!
1. 修改 SplashForm
为了在窗体上显示加载状态,我们需要在窗体上放置一个标签。在我们的示例中,我们将其命名为 lStatusInfo。此外,我们创建一个名为 StatusInfo 的 get/set 属性。之后,我们引入一个私有的字符串成员 _StatusInfo,它直接映射到属性。属性实现应如下所示:
public string StatusInfo { set { _StatusInfo = value; ChangeStatusText(); } get { return _StatusInfo; } }
ChangeStatusText()
是一个辅助函数,每次更新 StatusInfo 时都会调用它。其实现应如下所示:
public void ChangeStatusText() { try { if (this.InvokeRequired) { this.Invoke(new MethodInvoker(this.ChangeStatusText)); return; } lStatusInfo.Text = _StatusInfo; } catch (Exception e) { // do something here... } }
检查 InvokeRequired
属性是必要的,因为控件不是线程安全的。因此,如果您从不同于创建控件的线程调用控件上的方法,而不将调用封送到正确的线程,可能会发生奇怪的事情。
就是这样。让我们创建 Splasher 类,它负责线程的创建、启动和停止。
2. 创建 Splasher 类
静态成员 Show()
、Close()
和 Status
属性可以按名称所示用于显示和关闭 SplashWindow,以及更新 SplashForm 上显示的加载状态(使用之前创建的 StatusInfo
属性)。
类实现如下所示:
public class Splasher { static SplashForm MySplashForm = null; static Thread MySplashThread = null; // internally used as a thread function - showing the form and // starting the messageloop for it static void ShowThread() { MySplashForm = new SplashForm(); Application.Run(MySplashForm); } // public Method to show the SplashForm static public void Show() { if (MySplashThread != null) return; MySplashThread = new Thread(new ThreadStart(Splasher.ShowThread)); MySplashThread.IsBackground = true; MySplashThread.ApartmentState = ApartmentState.STA; MySplashThread.Start(); } // public Method to hide the SplashForm static public void Close() { if (MySplashThread == null) return; if (MySplashForm == null) return; try { MySplashForm.Invoke(new MethodInvoker(MySplashForm.Close)); } catch (Exception) { } MySplashThread = null; MySplashForm = null; } // public Method to set or get the loading Status static public string Status { set { if (MySplashForm == null) { return; } MySplashForm.StatusInfo = value; } get { if (MySplashForm == null) { throw new InvalidOperationException("Splash Form not on screen"); } return MySplashForm.StatusInfo; } } }
好的,我们来看看这里有什么
Show()
方法将使用静态 ShowThread
函数作为目标来创建一个新线程。该函数将简单地显示窗体并为其启动一个 MessageLoop。Close()
方法将再次将对 SplashForm Close()
方法的调用封送到适当的线程,从而导致窗体关闭。Status
属性可用于检索或更新 SplashScreen 上显示的 StatusInfo。
以下是一些简单的使用示例:
... // note that there is no instance required since those functions are static Splasher.Show(); // shows the SplashScreen // sets the status info displayed on the SplashScreen Splasher.Status = "Initializing Database Connection..."; Splasher.Close(); // closes the SplashScreen ...
修改 AppLoader 类
为了使用 Splasher 类,我们必须相应地修改我们的 AppLoader 类:
public class AppLoader { public AppLoader() { } [STAThread] static void Main(string[] args) { Splasher.Show(); DoStartup(args); Splasher.Close(); } static void DoStartup(string[] args) { // do whatever you need to do Form1 f = new Form1(); Application.Run(f); } }
首先,我们显示 SplashForm
并设置初始 StatusInfo
文本。然后,我们调用 DoStartup()
,这是放置任何不需要创建窗体的初始化代码的好地方。
您可能会问,为什么要把启动代码移到一个单独的函数中?答案很简单:由于 Jitter 是按函数进行的,因此在您的启动画面实际显示在屏幕上之前,它不需要处理太多代码。
为了确保在加载主应用程序窗体后,它实际上获得了焦点;您可能希望将以下语句放入 Form1 的 OnLoad
重写方法中:
this.Activate();
结语
好了,就这些了。这里没有深奥的智慧,但希望这能帮助您入门。再次感谢那些帮助改进本文的人。历史
27.01.03 | 更新了一个替代方法(多线程)来解决此问题。此外,对现有示例代码进行了补充改进。 |
22.01.03 | 首次发布。 |