`
isiqi
  • 浏览: 16453207 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

显示一个WinForms闪屏

阅读更多
Q: 显示一个WinForms闪屏(Splash Screen)
我的应用程序需要一定的时间来启动。我想在应用程序继续加载时显示一个闪屏(就像Visual Studio .NET和Office应用程序那样)。工具箱中没有这样的控件。我该如何实现呢?

A:
本专栏所附带的代码中包含了一个 SplashScreen类:

public class SplashScreen
{
   public SplashScreen(Bitmap splash);
   public void Close();
}

SplashScreen的构造器可以将显示的位图作为参数。Close方法用来关闭闪屏。通常情况下,我们在处理窗体(form)的Load事件的方法中运用SplashScreen(在图1中可以看到形成的闪屏):

private void OnLoad(object 
   sender,EventArgs e)
{
   Bitmap splashImage;
   splashImage = new 
      Bitmap("Splash.bmp");

   SplashScreen splashScreen;
   splashScreen = new 
      SplashScreen(splashImage);

   //Do some lengthy operations, then: 
   splashScreen.Close();
   Activate();
}

在关闭闪屏后,你必须激活窗体,将它放到最显著的位置。

你可以将任何位图作为一个闪屏。你也可以通过构建一个新的位图对象从BMP或JPG文件创建位图:

 Bitmap splashImage;
splashImage = new Bitmap("Splash.bmp");

或者你也可以用从窗体资源加载的一个图片:

 using System.Resources;

ResourceManager resources;
resources = new 
   ResourceManager(typeof(MyForm));

Bitmap splashImage;
SplashImage = 
   (Bitmap)(resources.GetObject(
   "SplashImage"))

要实现一个闪屏不只是我们所看到的这些内容。它可以依赖于一些很好的WinForms功能,而且它也涉及一些应用在其它WinForms环境中的有趣的设计问题。闪屏实际上是一个叫做SplashForm的WinForms窗体。你可以通过WinForms的可视设计窗口( Visual Designer)充分利用所需要的变化,将一个缺省的窗体转换成一个闪屏——这就证明了WinForms不仅简单易用,而且还有很多功能。在这个例子中,我们添加了一个单独的控件——一个叫做m_SplashPictureBox的简单的图片框。

在编译的时候,我们并不知道闪屏图片的大小,因为它是一个runtime参数,但是图片框需要根据图片来调整大小。你可以通过将m_SplashPictureBox的SizeMode属性设置为AutoSize很容易地实现这一点。接下来,你必须将图片框定位到窗体的左上角。你可以通过将m_SplashPictureBox的Dock属性设置为Fill来实现它。这就会将图片框固定在左上角了。在运行时,它会向右下角扩展来填充窗体,因为大小模式被设置成了AutoSize。最后,将m_SplashPictureBox的Cursor属性设置为AppStarting(带有一个指示器的沙漏),这样的话,如果用户将鼠标移动到闪屏上,他或她就会知道应用程序正在启动。


				图2.
图2. 为闪屏窗体和图片框设置可视的属性
闪屏窗体不应该显示任何控制框按钮(关闭、最小化和最大化),它也不会有一个标题栏。我们可以通过可视设计窗口将SplashForm的ControlBox属性设置为False;这样就取消了控制框(control box)。可以在设计窗口中清除Text属性来删除标题栏。

下面我们来看闪屏的边界。它应该是一条单独的线——不是缺省的可调整的边界样式——所以我们应该将窗体的FormBorderStyle属性设置为FixedSingle。将TopMost属性设置为True,使闪屏总是在z-order(Windows在桌面显示窗口的顺序)的顶部。闪屏应该总是在屏幕的中心。幸运的是,我们可以将StartPosition属性设置为CenterScreen来实现这一点,WinForms会自动考虑窗口的大小,并将它居中。图2显示了SplashForm和m_SplashPictureBox的Properties窗口,总结了你需要设置的属性和新的值。

接下来,我们需要写一些代码来调整闪屏的大小。SplashForm的构造器可以将闪动的图片作为参数,并将它赋值给图片框的图片:

internal class SplashForm : Form
{
   PictureBox m_SplashPictureBox;
   public SplashForm(Bitmap 
      splashImage)
   {
      InitializeComponent();
      m_SplashPictureBox.Image = 
         splashImage;
      ClientSize = 
         m_SplashPictureBox.Size;
   }
   //Rest of the implementation 
}

注意,你必须将SplashForm的客户端大小设置为图片框的大小,它会根据图片的大小自动调节自己的大小。结果SplashForm就可以在图片框中精确地显示图片了,因为图片框是被放在窗体的左上角的。

你不能在用来加载应用程序的同一个线程上显示SplashForm,因为那个线程在忙于加载应用程序而不会考虑显示或重绘闪屏。作为替代,我们应该让SplashScreen创建一个工作线程(worker thread)来显示SplashForm(见列表1)。工作线程调用Show方法,该方法会创建SplashForm对象并调用它的ShowDialog方法:

void Show()
{
   m_SplashForm = new 
      SplashForm(m_SplashImage);
   m_SplashForm.ShowDialog();
}

ShowDialog显示窗体并开始将Windows消息填充到里面。闪屏是在它自己的线程上运行的,因此该线程可以进行消息处理——不是指忙于加载应用程序的那个主应用程序线程。

接下来的任务是为主应用程序找到一个方法来关闭闪屏。最容易的方法就是用信号通知工作线程关闭窗体——除非该线程的方法(Show)正忙于在窗体的消息循环中(ShowDialog方法)填充消息,而不能查看标记或事件。解决的方法很简单,就是用Windows Timers。运用设计窗口在窗体上添加一个Timer控件,将它的Interval属性设置为适当的值,如500毫秒。Timer类实际上是基于VM_TIMER消息的,所以timer的Tick事件是Windows消息驱动的。工作线程将那个消息提供给闪屏,在那里它会查看是否需要关闭闪屏,因为主应用程序已经完成了加载。SplashForm类提供了Boolean属性HideSplash,SplashScreen的Close方法将它设置为:

public void Close()
{
   m_SplashForm.HideSplash = true;
   m_WorkerThread.Join();
} 

HideSplash可以访问SplashForm的m_HideSplash Boolean成员变量。m_HideSplash可以由多个线程访问,所以HideSplash需要通过锁定SplashForm以一种线程安全的方法来访问m_HideSplash:

public bool HideSplash
{
   get
   {
      lock(this){
         return m_HideSplash;
      }
   }
   set
   {
      lock(this){
         m_HideSplash = value;
      }
   }
}

SplashForm在OnTick方法中处理timer的Tick事件:

private void OnTick(object 
   sender,EventArgs e)
{
   if(HideSplash == true)
   {
      m_Timer.Enabled = false;
      Close();
   }
}

如果HideSplash属性设置为true(因为调用了SplashScreen的Close方法),OnTick就会使timer无效并关闭SplashForm。它的运作过程是这样的:主窗体开始加载,并在另外的一个线程上显示闪屏。然后,主窗体继续启动应用程序。闪屏定期查看(运用timer)是否应该关闭。当主窗体完成加载时会调用SplashScreen的Close方法。Close方法将HideSplash设置为true,并在工作线程上调用Join,等闪屏关闭。这会阻碍主窗体的显示,所以只要显示闪屏,主窗体就不会显示。下一次timer响了时,它就会查看HideSplash的值。它会取消timer并关闭SplashForm,因为HideSplash被设置为true。这会返回ShowDialog方法(该方法在SplashScreen的Show方法中被调用),然后返回Show。一旦返回Show,线程就终止了,因为Show是工作线程的线程方法。这时候,会返回SplashScreen的Close方法中的Join。Close方法被返回到主窗体,现在就可以显示主窗体了。

Q:允许可序列化的(Serializable)类型包含不可序列化的(Nonserializable)成员
我有一个可序列化的类,它包含一个数据库连接,作为一个成员变量。当我试着去序列化这个类时,出现了一个异常,因为连接是不可序列化的。如果我将连接标识为不可序列化,那么我就可以序列化类了——但在反序列化(deserialization)后,我就不能用这个对象了,因为连接成员是无效的。我该怎么处理呢?

A:
当你用Serializable属性来标识一个类进行序列化时,.NET认为所有的成员变量也都是可序列化的,如果它发现一个不可序列化的成员,它在序列化时就会抛出一个SerializationException类型的异常。然而,类可能会包含一个不能被序列化的成员。该类型没有Serializable属性,不能让所包含的类型被序列化。通常情况下,这个不可序列化成员是一个引用类型,需要一些特殊的初始化设置。要解决这个问题,我们需要将这样的一个成员标识为不可序列化,并在反序列化中采用一个自定义的步骤来初始化它。

你必须用NonSerialized字段属性来标识成员,让一个可序列化的类型包含一个不可序列化的类型,作为一个成员变量:

public class MyOtherClass
{..}

[Serializable]
public class MyClass
{
   [NonSerialized]
   MyOtherClass m_Obj;
   /* Methods and properties */
}

当.NET序列化一个成员变量时,它会首先查看它是否有NonSerialized属性:如果有,.NET就会忽略该变量,跳过它。然而,当.NET反序列化对象时,它就会初始化那个类型的不可序列化的成员变量,将它设置为缺省值(对所有引用类型来说,缺省值为零)。然后,就由你来提供代码将变量初始化到正确的值。最后,对象必须知道它是在什么时候被反序列化的。你必须实现IDeserializationCallback接口,该接口是在System.Runtime.Serialization命名空间中定义的:

public interface 
   IDeserializationCallback
{
   void OnDeserialization(object 
      sender);
}

在.NET完成对对象的反序列化处理后,就会调用IDeserializationCallback的OnDeserialization()方法,让它执行所需要的自定义的初始化步骤。你可以忽略发送的参数,因为.NET总是将它设置为零。下面的代码说明了如何通过实现IDeserializationCallback来执行自定义的序列化:

using System.Runtime.Serialization;

[Serializable]
public class MyClass : 
   IDeserializationCallback
{
   [NonSerialized]
   IDbConnection m_Connection;

   public void OnDeserialization(object 
      sender)
   {
      Debug.Assert(m_Connection == 
         null);
      m_Connection = new 
         SqlConnection();
      m_Connection.ConnectionString = 
         "data 
         source= ... ";
      m_Connection.Open();
   }
   /* Other members  */
}

在上面的代码中,MyClass类有一个作为成员变量的数据库连接。连接对象(SqlConnection)不是一个可序列化的类型,所以你需要用NonSerialized属性来标识它。MyClass在它的OnDeseralization()实现中创建了一个新的连接对象,因为连接成员在反序列化后被设置为缺省值(零)。然后,通过提供一个连接字符串,MyClass初始化了一个连接对象并打开它。


关于作者:
Juval Lowy是位经验丰富的软件架构师,并且是IDesign的负责人。这是一家专门从事.NET设计和.NET移植的咨询和培训公司。作为Microsoft在硅谷的地区主管,Juval负责帮助将.NET运用到企业中。最近,他写了一本名为Programming .NET Components (O'Reilly & Associates)的书。你可以通过www.idesign.net与他联系。
分享到:
评论

相关推荐

    winform 窗体 闪屏 彻底解决

    在Windows Forms(WinForm)开发中,用户可能会遇到一个常见的问题,那就是窗体在加载或刷新时出现“闪屏”现象。这个问题通常是由于系统在绘制窗体及其控件时的多帧重绘导致的,尤其是在控件数量较多时,这种现象...

    SharpGL.WinForms.zip_C#插件_数据处理_点云处理_点云数据显示_点云显示

    总的来说,SharpGL.WinForms作为C#平台上的一个高效工具,不仅简化了OpenGL在Windows应用程序中的集成,而且为点云数据的处理和显示提供了强大支持。无论是用于科研、教育还是工业应用,这款插件都能成为开发者的...

    闪屏添加器(给程序加弹出窗口)

    在IT行业中,闪屏或启动画面通常是应用程序的一个重要组成部分,它可以在用户打开程序的第一时间传达品牌信息,提升用户体验。闪屏添加器的工作原理是将用户选择的图片或动画文件集成到目标程序中,使其在程序启动或...

    一组Winforms皮肤

    Winforms皮肤是Windows Forms应用程序界面设计中的一个重要元素,它能够为用户带来更加美观、现代的交互体验。在本文中,我们将深入探讨Winforms皮肤的概念、种类以及如何在项目中进行应用。 Winforms,全称为...

    WinForms

    压缩包中的"WinForms3"可能包含一个示例项目,演示了如何在WinForms应用程序中使用这三个控件。通常,这样的示例可能会展示如何配置Timer控件来更新ListView或TreeView的内容,或者如何响应用户在ListView和...

    我自己写的一个Winforms桌面日历

    【标题】:“我自己写的一个Winforms桌面日历” 在编程领域,开发一款桌面日历程序是一种常见的实践项目,尤其对于初学者来说,它可以帮助提升Windows应用程序设计能力。本项目是作者自行开发的一款基于Winforms...

    winforms-modernui-master_ModernUI_Winforms_stiffcld_winform_

    标题“winforms-modernui-master_ModernUI_Winforms_stiffcld_winform_”指的是一个基于Windows Forms的项目,该项目使用了Modern UI (也称为Metro UI)设计风格,用于创建具有现代感的Windows应用程序。这个项目可能...

    C# Winforms学员管理系统

    【C# Winforms学员管理系统】是一个基于C#编程语言和Windows Forms(Winforms)平台的桌面应用程序,设计用于教育机构管理学员信息。该系统采用了MVC(Model-View-Controller)设计模式,这是一种广泛应用于软件工程...

    WinForms航班详细信息

    【WinForms航班详细信息】是一个基于Windows Forms技术开发的小型应用程序,主要功能是管理航班的详细信息,包括添加新航班、删除现有航班、修改航班数据以及查询航班信息。这个程序适用于航空公司或者旅行代理等...

    WeifenLuo.WinFormsUI.Docking.dll 源码+示例 支持framwork4 最新版

    DockPanel Suite是一个国外开源项目。 项目要引用WeifenLuo.WinFormsUI.Docking.dll,这个程序集包含了控件及相关类。需要在DockPanel控件中显示的子窗体(多标签,停靠,自动隐藏)必须继承自WeifenLuo.WinFormsUI...

    Microsoft.ReportViewer.WinForms.dll

    标题 "Microsoft.ReportViewer.WinForms.dll" 是微软开发的一个控件,用于在Windows Forms应用程序中嵌入报表查看器功能。这个DLL文件是.NET Framework的一部分,主要用于显示和交互基于Microsoft SQL Server ...

    C#计时器 WinForms

    - 定时刷新UI:例如,如果你有一个显示实时数据的控件,可以使用计时器每隔几秒更新数据。 - 倒计时功能:在游戏或计时器应用中,可以通过减去时间来实现倒计时。 - 定时执行后台任务:虽然这更适合System.Timers...

    WeifenLuo.WinFormsUI.Docking 控件 应用 C# winform

    WeifenLuo.WinFormsUI.Docking 控件是一个强大的界面布局控件,可以保存自定义的布局为 XML 文件,并可以加载 XML 配置文件。下面是关于 WeifenLuo.WinFormsUI.Docking 控件的应用介绍。 控件简介 WeifenLuo....

    winforms项目安装实例

    WinForms是.NET Framework中用于创建桌面应用程序的一个重要工具,它提供了丰富的用户界面元素和设计功能,使得开发者可以构建出交互性强、功能完备的应用程序。本教程将详细介绍如何安装和配置WinForms项目,以便于...

    C#winforms自动更新

    这个主题“C# WinForms自动更新”涉及到如何在局域网环境中设计并实现一个能自动检查并安装更新的功能。下面将详细讲解这个知识点。 首先,我们需要理解C# WinForms的基本概念。C#是一种面向对象的编程语言,常用于...

    WinForms MySchool完整版

    **WinForms MySchool完整版** 是一个基于Windows Forms(WinForms)开发的学生管理系统。这个系统设计用于实现基础的用户登录和注册功能,并且具备学生信息的添加、修改和删除等操作。通过集成DataGridVie和ListView...

    WinForms 图书管理系统

    总的来说,"WinForms 图书管理系统"是一个典型的桌面应用程序实例,涵盖了数据库操作、对象模型、事件处理等多个核心编程概念。通过学习和分析这个项目,可以深入理解C#和WinForms开发的基本原理与实践技巧。

    WeifenLuo.WinFormsUI.Docking3.1.0

    布局控件"WeifenLuo.WinFormsUI.Docking"是一个非常棒的开源控件,用过的人都深有体会,该控件之强大、美观、不亚于商业控件。而且控件使用也是比较简单的. 源码地址:...

    C# 功能全的WinForms多媒体播放器源码

    本项目提供了一个功能全面的C# WinForms多媒体播放器源码,旨在帮助开发者理解如何利用C#实现多媒体播放功能,同时为开发自己的应用程序提供参考。 1. **C#基础** C#是一种面向对象的语言,由微软公司开发,主要...

    VC++ WINFORMS介绍

    VC++中的WinForms是.NET框架下开发Windows桌面应用程序的一种方式,它是基于C++的一个子集,结合了C++的强大功能和.NET Framework的易用性。WinForms为开发者提供了丰富的控件库,使得创建用户界面变得简单高效。 1...

Global site tag (gtag.js) - Google Analytics