65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (51投票s)

2003年1月22日

6分钟阅读

viewsIcon

327622

downloadIcon

10379

本文展示了如何在应用程序初始化期间显示启动画面。

AppLoading

更新 2003 年 1 月 28 日

本文已根据 Chris Austin 的建议进行了更新(感谢 Chris!也感谢 Phil Bolduc 对 Chris 代码的修复;)并且基于 Jabes 的想法添加了一个替代的多线程方法(感谢 Jabes!)。

引言

本文展示了如何在应用程序初始化期间显示启动画面。本文假设您熟悉 C# 和 VS.NET IDE。

背景

我将大量的初始化代码放在了主窗体的 OnLoad() 重写方法中。代码解析配置文件、创建控件等。当我的应用程序启动时,看起来一团糟:主应用程序窗体没有优雅地弹出并准备好使用。我决定创建一个启动画面,它应该在执行程序后立即弹出。在显示启动画面的同时,我希望执行我的初始化,一旦完成,我就隐藏启动画面并显示主应用程序窗口。本文包含实现此目的的各种方法。

版本 A:快速便捷版

这个版本是我想出的快速便捷的解决方案。感谢 Chris Austin 建议使用 ApplicationContext 类。

准备工作

  1. 创建一个新的空 Windows 窗体项目。
  2. 添加一个新的窗体(用于您的启动画面,并命名为 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 中所示)。如果启动画面需要显示很长时间,您会希望多线程处理它;通过从自己的消息循环中运行启动画面,屏幕重绘和其他窗口消息可以正确处理,从而给人留下更专业的印象。

准备工作

  1. 创建一个新的空 Windows 窗体项目。
  2. 添加一个新的窗体(用于您的启动画面,并命名为 SplashForm)。
  3. 重复上一节中创建包含应用程序入口点的 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 首次发布。
© . All rights reserved.