smartsoft

致力于 .NET 平台持久层(数据访问中间件)技术的研究

博客园 首页 新随笔 联系 订阅 管理


这个炫耀窗口使用了一些比较先进的技术和技巧来处理窗口的显示,作者对示例进行了非常详尽的讲解,非常有参考价值,希望能够和大家分享。

以下是源代码和演示的压缩文件下载地址:

--- 非常漂亮的炫耀窗口源代码和演示

Introduction

Every time a customer loads your application, you have the opportunity to impress or disappoint with your splash screen. A good splash screen will:

  • Run on a separate thread
  • Fade in as it appears, and fade out as it disappears
  • Display a running status message that is updated using a static method
  • Display and update a predictive self-calibrating owner-drawn smooth-gradient progress bar
  • Display the number of seconds remaining before load is complete

In this tutorial, we'll explore how to create a splash screen and add these features one at a time. We start with the creation of a simple splash screen, followed by the code changes required for the addition of each feature. You can skip to the bottom of the article to see the complete source code. I've also included a small test project in the download that demonstrates the splash screen.

Background

It was a lot of fun writing the code for this article and, while it's not perfect for all needs, I hope it saves you some coding time. The most fun for me is seeing a completely accurate and smoothly progressing progress bar as my application loads up. Please feel free to post any enhancement suggestions, bugs or other comments you may have.

Create the Simple Splash Screen Project

Start out by creating a Windows Forms project. Name it SplashScreen. Add a Windows Form to the project and name it SplashScreen. Delete Form1.cs.

Now obtain a product bitmap with a light background suitable for putting text over. If you're lucky, a really talented person (like dzCepheus - see the thread below) will provide one for you. Set the Background Image property to it. Set the following properties on the form:
FormBorderStyle = None
StartPosition = CenterScreen

In the form constructor, add the line:

this.ClientSize = this.BackgroundImage.Size;

Make it Available from Static Methods

Because the splash screen will only need a single instance, you can simplify your code by using static methods to access it. By just referencing the SplashScreen project, a component can launch, update or close the splash screen without needing an object reference. Add the following code to SplashScreen.cs:
static SplashScreen ms_frmSplash = null;
// A static entry point to launch SplashScreen.
static public void ShowForm()
{
  ms_frmSplash = new SplashScreen();
  Application.Run(ms_frmSplash);
}
// A static method to close the SplashScreen
static public void CloseForm()
{
  ms_frmSplash.Close();
}

Put it on its Own Thread

A splash screen displays information about your application while it is loading and initializing its components. If you are going to display any dynamic information during that time, you should put it on a separate thread to prevent it from freezing when initialization is hogging the main thread. Generally speaking, you can safely update data in cross-thread method calls, but you cannot update the UI without using Invoke to call the updating methods. In this solution, we will use Invoke for launching the Splash Screen and limit all other calls to updating instance data. A timer (which we need anyway for other effects) will update the UI from the modified data.

Start by using the Threading namespace:

using System.Threading;

Declare a static variable to hold the thread:

static Thread ms_oThread = null;

Now add a method to create and launch the splash screen on its own thread:

static public void ShowSplashScreen()
{
  // Make sure it is only launched once.
  if( ms_frmSplash != null )
    return;
  ms_oThread = new Thread( new ThreadStart(SplashScreen.ShowForm));
  ms_oThread.IsBackground = true;
  ms_oThread.ApartmentState = ApartmentState.STA;
  ms_oThread.Start();
}
Now ShowForm() can be made private, since the form will now be shown using ShowSplashScreen().
// A static entry point to launch SplashScreen.
static private void ShowForm()

Add Code to Fade In and Fade Out

It can add real flair to your splash screen by having it fade in when it first appears, and fade out just as your application appears. The form's Opacity property makes this easy.

Declare variables defining increment and decrement rate. These define how quickly the form appears and disappears. They are directly related to the timer interval, since they represent how much the Opacity increases or decreases per timer tick, so if you modify the timer interval, you will want to change these proportionally.

Private double m_dblOpacityIncrement = .05;
private double m_dblOpacityDecrement = .1;
private const int TIMER_INTERVAL = 50;

Add a timer to the form and then modify the constructor to start the timer and initialize the opacity to zero.

this.Opacity = .0;
timer1.Interval = TIMER_INTERVAL;
timer1.Start();

Modify the CloseForm() method to initiate the fade away process instead of closing the form.

static public void CloseForm()
{
  if( ms_frmSplash != null )
  {
    // Make it start going away.
    ms_frmSplash.m_dblOpacityIncrement = -ms_frmSplash.m_dblOpacityDecrement;
  }
  ms_oThread = null;  // we do not need these any more.
  ms_frmSplash = null;
}

Add a Tick event handler to change the opacity as the form is fading in or fading out, and to close the splash screen form when the opacity reaches 0.

private void timer1_Tick(object sender, System.EventArgs e)
{
  if( m_dblOpacityIncrement > 0 )
  {
    if( this.Opacity < 1 )
      this.Opacity += m_dblOpacityIncrement;
  }
  else
  {
    if( this.Opacity > 0 )
      this.Opacity += m_dblOpacityIncrement;
    else
      this.Close();
  }
}

At this point, you have a splash screen that fades into view when you call the ShowSplashScreen() method and starts fading away when you call the CloseForm() method.

Add Code to Display a Status String

Now that the basic splash screen is complete, we can add status information to the form, so the user can tell that something's going on. To do this, we add the member variable m_sStatus to the form to store the status and a label lblStatus to display it. We then add an accessor method to set the variable and modify the timer tick method to update the label. The accessor is thread-safe because it only modifies the data; it doesn't directly modify the label.

private string m_sStatus;
...
// A static method to set the status.
static public string SetStatus(string newStatus)
{
  if( ms_frmSplash == null )
    return;
  ms_frmSplash.m_sStatus = newStatus;
}

Now we modify the timer1_Tick method to update the label.

lblStatus.Text = m_sStatus;

Now Add a Progress Bar

There's no reason to use the standard WinForms progress bar here unless you really want that look. We'll make a gradient progress bar by painting our own Panel control. To do this, add a panel named pnlStatus to the form and set its background color to Transparent. In practice, you might want to derive your own control from the Panel if you expect to use it in more than one place. Here, we'll just respond to the paint event.

Declare a variable to hold the percent completion value. It is a double with a value that will vary between 0 and 1 as the progress bar progresses. Also declare a rectangle to hold the current progress rectangle.

private double m_dblCompletionFraction = 0;
private Rectangle m_rProgress;

For now, add a public property for setting the current percent complete. Later, when we add the self-calibration feature, we'll eliminate the need for it.

// Static method for updating the progress percentage.
static public double Progress
{
  get 
  {
    if( ms_frmSplash != null )
      return ms_frmSplash.m_dblCompletionFraction; 
    return 100.0;
  } 
  set
  {
    if( ms_frmSplash != null )
      ms_frmSplash.m_dblCompletionFraction = value;
  }
}

Now we modify the timer's Tick event handler to invalidate the portion of the Panel we want to paint.

  ...
  int width = (int)Math.Floor(pnlStatus.ClientRectangle.Width 
     * m_dblCompletionFraction);
  int height = pnlStatus.ClientRectangle.Height;
  int x = pnlStatus.ClientRectangle.X;
  int y = pnlStatus.ClientRectangle.Y;
  if( width > 0 && height > 0 )
  {
    m_rProgress = new Rectangle( x, y, width, height);
    pnlStatus.Invalidate(m_rProgress);
  }
  ...

Finally, add a Panel control named pnlStatus to the form and a paint handler to paint the gradient progress bar. You will probably want to fiddle with the RGB values to get a color scheme that works with your graphic.

// Paint the portion of the panel invalidated during the tick event.
private void pnlStatus_Paint(object sender, 
    System.Windows.Forms.PaintEventArgs e)
{
  if( e.ClipRectangle.Width > 0 && m_iActualTicks > 1 )
  {
    LinearGradientBrush brBackground = 
      new LinearGradientBrush(m_rProgress, 
                              Color.FromArgb(50, 50, 200),
                              Color.FromArgb(150, 150, 255), 
                              LinearGradientMode.Horizontal);
    e.Graphics.FillRectangle(brBackground, m_rProgress);
  }
}

Smooth the Progress by Extrapolating Between Progress Updates

I don't know about you, but I've always been annoyed by the way progress bars progress. They're jumpy, stop during long operations, and always cause me vague anxiety that maybe they've stopped responding.

Well, this next bit of code tries to alleviate that anxiety by making the progress bar move even during lengthy operations. We do this by changing the meaning of the Progress updates. Instead of indicating current percent complete, they now indicate the percentage of time we expect the current activity to take before the next Progress update. For example, the first update might indicate that 25% of the total will pass before the second update. This allows us to use the timer to paint more and more of the status bar, up to and including 25% (but not beyond) while we are waiting for the next update. For now, we'll guess at how much to progress per timer tick. Later, we'll calculate this based on experience.

Add member variables to represent the previous progress and the amount to increment the progress bar per timer tick.

private double m_dblLastCompletionFraction = 0.0;
private double m_dblPBIncrementPerTimerInterval = .0015;

Modify the Progress property to save the previous value before setting the new Progress value.

ms_frmSplash.m_dblLastCompletionFraction = 
    ms_frmSplash.m_dblCompletionFraction;

Modify the Timer.Tick event handler to do the progressive update:

if( m_dblLastCompletionFraction < m_dblCompletionFraction )
{
  m_dblLastCompletionFraction += m_dblPBIncrementPerTimerInterval;
  int width = (int)Math.Floor(pnlStatus.ClientRectangle.Width 
                   * m_dblLastCompletionFraction);
  int height = pnlStatus.ClientRectangle.Height;
  int x = pnlStatus.ClientRectangle.X;
  int y = pnlStatus.ClientRectangle.Y;
  if( width > 0 && height > 0 )
  {
    pnlStatus.Invalidate(new Rectangle( x, y, width, height));
  }
}

Now Make the Progress Bar Calibrate Itself

We can now eliminate the need to specify the progress percentages by calculating the values and remembering them between splash screen invocations. Notice that this will work only if you make a fixed sequence of calls to SetStatus() and SetReferencePoint() during startup.

Registry Access

For completeness, we'll define a simple utility class for accessing the registry. You can replace this with whatever persistent string storage mechanisms you use in your application. In the source code provided, this class appears below the SplashScreen class.

Don't forget to update the registry key strings to reflect your application name and company name!

using Microsoft.Win32;
...
/// A class for managing registry access.
public class RegistryAccess
{
  private const string SOFTWARE_KEY = "Software";
  private const string COMPANY_NAME = "MyCompany";
  private const string APPLICATION_NAME = "MyApplication";
  // Method for retrieving a Registry Value.
  static public string GetStringRegistryValue(string key, 
    string defaultValue)
  {
    RegistryKey rkCompany;
    RegistryKey rkApplication;
    rkCompany = Registry.CurrentUser
                        .OpenSubKey(SOFTWARE_KEY, false)
                        .OpenSubKey(COMPANY_NAME, false);
    if( rkCompany != null )
    {
      rkApplication = rkCompany.OpenSubKey(APPLICATION_NAME, true);
      if( rkApplication != null )
      {
        foreach(string sKey in rkApplication.GetValueNames())
        {
          if( sKey == key )
          {
            return (string)rkApplication.GetValue(sKey);
          }
        }
      }
    }
    return defaultValue;
  }
  // Method for storing a Registry Value.
  static public void SetStringRegistryValue(string key, string stringValue)
  {
    RegistryKey rkSoftware;
    RegistryKey rkCompany;
    RegistryKey rkApplication;
    rkSoftware = Registry.CurrentUser.OpenSubKey(SOFTWARE_KEY, true);
    rkCompany = rkSoftware.CreateSubKey(COMPANY_NAME);
    if( rkCompany != null )
    {
      rkApplication = rkCompany.CreateSubKey(APPLICATION_NAME);
      if( rkApplication != null )
      {
        rkApplication.SetValue(key, stringValue);
      }
    }
  }
}

Member Variables

Now declare variables for keeping track of how long each interval between updates is taking (this time) and what it took per interval last time (from the registry). Declare registry key constants and a Boolean flag to indicate whether this is the first launch.

private bool m_bFirstLaunch = false;
private DateTime m_dtStart;
private bool m_bDTSet = false;
private int m_iIndex = 1;
private int m_iActualTicks = 0;
private ArrayList m_alPreviousCompletionFraction;
private ArrayList m_alActualTimes = new ArrayList();
private const string REG_KEY_INITIALIZATION = "Initialization";
private const string REGVALUE_PB_MILISECOND_INCREMENT = "Increment";
private const string REGVALUE_PB_PERCENTS = "Percents";

Reference Points

We need to declare methods for recording various reference points during application startup. Reference points are critical to making a self-calibrating progress bar since they replace progress bar percent-complete updates. To make the best use of this capability, you should sprinkle reference points inside of the initialization code that runs during application startup. The more you place, the smoother and more accurate your progress bar will be. This is when static access really pays off, because you don't have to pass a reference to SplashScreen to the initialization code.

First, we'll need a simple utility function to return elapsed Milliseconds since the Splash Screen first appeared. This is used for calculating the percentage of overall time allocated to each interval between ReferencePoint calls.

// Utility function to return elapsed Milliseconds since the 
// SplashScreen was launched.
private double ElapsedMilliSeconds()
{
  TimeSpan ts = DateTime.Now - m_dtStart;
  return ts.TotalMilliseconds;
}

SetStatus() and SetReferencePoint() both call SetReferenceInternal() which records the time of the first call and adds the elapsed time of each subsequent call to an array for later processing. It sets the progress bar values by referencing previous recorded values for the progress bar. For example, if we're processing the 3rd SetReferencePoint() call, we use the actual percentage of the overall load time that occurred between the 3rd and 4th calls during the previous invocation.

// Static method called from the initializing application to 
// give the splash screen reference points.  Not needed if
// you are using a lot of status strings.
static public void SetReferencePoint()
{
  if( ms_frmSplash == null )
    return;
  ms_frmSplash.SetReferenceInternal();
}
// Internal method for setting reference points.
private void SetReferenceInternal()
{
  if( m_bDTSet == false )
  {
    m_bDTSet = true;
    m_dtStart = DateTime.Now;
    ReadIncrements();
  }
  double dblMilliseconds = ElapsedMilliSeconds();
  m_alActualTimes.Add(dblMilliseconds);
  m_dblLastCompletionFraction = m_dblCompletionFraction;
  if( m_alPreviousCompletionFraction != null 
      && m_iIndex < m_alPreviousCompletionFraction.Count )
    m_dblCompletionFraction = (double)m_alPreviousCompletionFraction[
      m_iIndex++];
  else
    m_dblCompletionFraction = ( m_iIndex > 0 )? 1: 0;
}

The next two functions, ReadIncrements() and StoreIncrements(), read and write the calculated intervals associated with each of the ReferencePoint values.

// Function to read the checkpoint intervals from the 
// previous invocation of the
// splashscreen from the registry.
private void ReadIncrements()
{
  string sPBIncrementPerTimerInterval = GetStringRegistryValue(
                            REGVALUE_PB_MILISECOND_INCREMENT, "0.0015");
  double dblResult;
  if( Double.TryParse( sPBIncrementPerTimerInterval,
                       System.Globalization.NumberStyles.Float,
                       System.Globalization.NumberFormatInfo.InvariantInfo,
                       out dblResult) )
    m_dblPBIncrementPerTimerInterval = dblResult;
  else
    m_dblPBIncrementPerTimerInterval = .0015;
  string sPBPreviousPctComplete = GetStringRegistryValue( 
     REGVALUE_PB_PERCENTS, "" );
  if( sPBPreviousPctComplete != "" )
  {
    string [] aTimes = sPBPreviousPctComplete.Split(null);
    m_alPreviousCompletionFraction = new ArrayList();
    for(int i = 0; i < aTimes.Length; i++ )
    {
      double dblVal;
      if( Double.TryParse(aTimes[i], 
                     System.Globalization.NumberStyles.Float,
                     System.Globalization.NumberFormatInfo.InvariantInfo,
                     out dblVal) )
        m_alPreviousCompletionFraction.Add(dblVal);
      else
        m_alPreviousCompletionFraction.Add(1.0);
    }
  }
  else
  {
    // If this is the first launch, flag it so we don't try to 
    // show the scroll bar.
    m_bFirstLaunch = true;
  }
}
// Method to store the intervals (in percent complete) 
// from the current invocation of
// the splash screen to the registry.
private void StoreIncrements()
{
  string sPercent = "";
  double dblElapsedMS = ElapsedMilliSeconds();
  for( int i = 0; i < m_alActualTimes.Count; i++ )
    sPercent += ((double)m_alActualTimes[i]/dblElapsedMS).ToString(
        "0.####", System.Globalization.NumberFormatInfo.InvariantInfo) + " ";
  SetStringRegistryValue( REGVALUE_PB_PERCENTS, sPercent );
  m_dblPBIncrementPerTimerInterval = 1.0/(double)m_iActualTicks;
  SetStringRegistryValue( REGVALUE_PB_MILISECOND_INCREMENT,
       m_dblPBIncrementPerTimerInterval.ToString("#.000000",
       System.Globalization.NumberFormatInfo.InvariantInfo));
}

We now can modify the SetStatus() method to add a Reference when the Status is updated. We also add an overloaded method to permit a Status update without the SetReferenceInternal() call. This is useful if you are in a section of code that has a variable set of status string updates. Note that depending on how often SetStatus() is called, you may not need many SetReference() calls in your startup code.

static public void SetStatus(string newStatus)
{
  SetStatus(newStatus, true);
}
static public void SetStatus(string newStatus, bool setReference)
{
  if( ms_frmSplash == null )
    return;
  ms_frmSplash.m_sStatus = newStatus;
  if( setReference )
    ms_frmSplash.SetReferenceInternal();
}

We also need to modify the timer tick and progress bar paint event handlers to paint only when m_bFirstLaunch is false. This prevents the first launch from showing an uncalibrated progress bar.

...
// Timer1_Tick()
if( m_bFirstLaunch == false && m_dblLastCompletionFraction 
    < m_dblCompletionFraction )
...
//pnlStatus_Paint()
if( m_bFirstLaunch == false && e.ClipRectangle.Width > 0 
    && m_iActualTicks > 1 )

Add a Time Remaining Counter

Finally, we can fairly accurately estimate the remaining time for initialization by examining what percentage is yet to be done. Add a label called lblTimeRemaining to the splash screen form to display it. Add the following code to the timer1_Tick() event handler to update the lblTimeRemaining label on the SplashScreen form.
int iSecondsLeft = 1 + (int)(TIMER_INTERVAL * 
  ((1.0 - m_dblLastCompletionFraction)/m_dblPBIncrementPerTimerInterval)) 
  / 1000;
if( iSecondsLeft == 1 )
  lblTimeRemaining.Text = string.Format( "1 second remaining");
else
  lblTimeRemaining.Text = string.Format( "{0} seconds remaining",
     iSecondsLeft);

Using the SplashScreen

To use the splash screen, just call SplashScreen.ShowSplashScreen() on the first line of your Main() entry point. Periodically call either SetStatus() (if you have a new status to report) or SplashScreen.SetReferencePoint() (if you don't) to calibrate the progress bar. When your initialization is complete, call SplashScreen.CloseForm() to start the fade out process. Take a look at the test module provided in the download if you have any questions.

You may want to play around with the various constants to adjust the time of fade in and fade out. If you set the interval to a very short time (like 10 ms), you'll get a beautiful smoothly progressing progress bar but your performance may suffer.

When the application first loads, you will notice that the progress bar and time remaining counter do not display. This is because the splash screen needs one load to calibrate the progress bar. It will appear on subsequent application launches.

SplashScreen.cs Source Code

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using Microsoft.Win32;
namespace SplashScreen
{
  /// Summary description for SplashScreen.
  public class SplashScreen : System.Windows.Forms.Form
  {
    // Threading
    static SplashScreen ms_frmSplash = null;
    static Thread ms_oThread = null;
    // Fade in and out.
    private double m_dblOpacityIncrement = .05;
    private double m_dblOpacityDecrement = .08;
    private const int TIMER_INTERVAL = 50;
    // Status and progress bar
    private string m_sStatus;
    private double m_dblCompletionFraction = 0;
    private Rectangle m_rProgress;
    // Progress smoothing
    private double m_dblLastCompletionFraction = 0.0;
    private double m_dblPBIncrementPerTimerInterval = .015;
    // Self-calibration support
    private bool m_bFirstLaunch = false;
    private DateTime m_dtStart;
    private bool m_bDTSet = false;
    private int m_iIndex = 1;
    private int m_iActualTicks = 0;
    private ArrayList m_alPreviousCompletionFraction;
    private ArrayList m_alActualTimes = new ArrayList();
    private const string REG_KEY_INITIALIZATION = "Initialization";
    private const string REGVALUE_PB_MILISECOND_INCREMENT = "Increment";
    private const string REGVALUE_PB_PERCENTS = "Percents";
    private System.Windows.Forms.Label lblStatus;
    private System.Windows.Forms.Label lblTimeRemaining;
    private System.Windows.Forms.Timer timer1;
    private System.Windows.Forms.Panel pnlStatus;
    private System.ComponentModel.IContainer components;
    /// Constructor
    public SplashScreen()
    {
      InitializeComponent();
      this.Opacity = .00;
      timer1.Interval = TIMER_INTERVAL;
      timer1.Start();
      this.ClientSize = this.BackgroundImage.Size;
    }
    /// Clean up any resources being used.
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if(components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }
    #region Windows Form Designer generated code
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    private void InitializeComponent()
    {
      this.components = new System.ComponentModel.Container();
      System.Resources.ResourceManager resources = 
          new System.Resources.ResourceManager(typeof(SplashScreen));
      this.lblStatus = new System.Windows.Forms.Label();
      this.pnlStatus = new System.Windows.Forms.Panel();
      this.lblTimeRemaining = new System.Windows.Forms.Label();
      this.timer1 = new System.Windows.Forms.Timer(this.components);
      this.SuspendLayout();
      // 
      // lblStatus
      // 
      this.lblStatus.BackColor = System.Drawing.Color.Transparent;
      this.lblStatus.Location = new System.Drawing.Point(152, 116);
      this.lblStatus.Name = "lblStatus";
      this.lblStatus.Size = new System.Drawing.Size(237, 14);
      this.lblStatus.TabIndex = 0;
      // 
      // pnlStatus
      // 
      this.pnlStatus.BackColor = System.Drawing.Color.Transparent;
      this.pnlStatus.Location = new System.Drawing.Point(152, 138);
      this.pnlStatus.Name = "pnlStatus";
      this.pnlStatus.Size = new System.Drawing.Size(237, 24);
      this.pnlStatus.TabIndex = 1;
      this.pnlStatus.Paint += 
        new System.Windows.Forms.PaintEventHandler(this.pnlStatus_Paint);
      // 
      // lblTimeRemaining
      // 
      this.lblTimeRemaining.BackColor = System.Drawing.Color.Transparent;
      this.lblTimeRemaining.Location = new System.Drawing.Point(152, 169);
      this.lblTimeRemaining.Name = "lblTimeRemaining";
      this.lblTimeRemaining.Size = new System.Drawing.Size(237, 16);
      this.lblTimeRemaining.TabIndex = 2;
      this.lblTimeRemaining.Text = "Time remaining";
      // 
      // timer1
      // 
      this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
      // 
      // SplashScreen
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.BackColor = System.Drawing.Color.LightGray;
      this.BackgroundImage = ((System.Drawing.Image)(
        resources.GetObject("$this.BackgroundImage")));
      this.ClientSize = new System.Drawing.Size(419, 231);
      this.Controls.Add(this.lblTimeRemaining);
      this.Controls.Add(this.pnlStatus);
      this.Controls.Add(this.lblStatus);
      this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
      this.Name = "SplashScreen";
      this.StartPosition = 
         System.Windows.Forms.FormStartPosition.CenterScreen;
      this.Text = "SplashScreen";
      this.DoubleClick += new System.EventHandler(
         this.SplashScreen_DoubleClick);
      this.ResumeLayout(false);
    }
    #endregion
    // ************* Static Methods *************** //
    // A static method to create the thread and 
    // launch the SplashScreen.
    static public void ShowSplashScreen()
    {
      // Make sure it is only launched once.
      if( ms_frmSplash != null )
        return;
      ms_oThread = new Thread( new ThreadStart(SplashScreen.ShowForm));
      ms_oThread.IsBackground = true;
      ms_oThread.ApartmentState = ApartmentState.STA;
      ms_oThread.Start();
    }
    // A property returning the splash screen instance
    static public SplashScreen SplashForm 
    {
      get
      {
        return ms_frmSplash;
      } 
    }
    // A private entry point for the thread.
    static private void ShowForm()
    {
      ms_frmSplash = new SplashScreen();
      Application.Run(ms_frmSplash);
    }
    // A static method to close the SplashScreen
    static public void CloseForm()
    {
      if( ms_frmSplash != null && ms_frmSplash.IsDisposed == false )
      {
        // Make it start going away.
        ms_frmSplash.m_dblOpacityIncrement = - 
           ms_frmSplash.m_dblOpacityDecrement;
      }
      ms_oThread = null;  // we do not need these any more.
      ms_frmSplash = null;
    }
    // A static method to set the status and update the reference.
    static public void SetStatus(string newStatus)
    {
      SetStatus(newStatus, true);
    }
    
    // A static method to set the status and optionally update the reference.
    // This is useful if you are in a section of code that has a variable
    // set of status string updates.  In that case, don't set the reference.
    static public void SetStatus(string newStatus, bool setReference)
    {
      if( ms_frmSplash == null )
        return;
      ms_frmSplash.m_sStatus = newStatus;
      if( setReference )
        ms_frmSplash.SetReferenceInternal();
    }
    // Static method called from the initializing application to 
    // give the splash screen reference points.  Not needed if
    // you are using a lot of status strings.
    static public void SetReferencePoint()
    {
      if( ms_frmSplash == null )
        return;
      ms_frmSplash.SetReferenceInternal();
    }
    // ************ Private methods ************
    // Internal method for setting reference points.
    private void SetReferenceInternal()
    {
      if( m_bDTSet == false )
      {
        m_bDTSet = true;
        m_dtStart = DateTime.Now;
        ReadIncrements();
      }
      double dblMilliseconds = ElapsedMilliSeconds();
      m_alActualTimes.Add(dblMilliseconds);
      m_dblLastCompletionFraction = m_dblCompletionFraction;
      if( m_alPreviousCompletionFraction != null 
          && m_iIndex < m_alPreviousCompletionFraction.Count )
        m_dblCompletionFraction = 
            (double)m_alPreviousCompletionFraction[m_iIndex++];
      else
        m_dblCompletionFraction = ( m_iIndex > 0 )? 1: 0;
    }
    // Utility function to return elapsed Milliseconds since the 
    // SplashScreen was launched.
    private double ElapsedMilliSeconds()
    {
      TimeSpan ts = DateTime.Now - m_dtStart;
      return ts.TotalMilliseconds;
    }
    // Function to read the checkpoint intervals 
    // from the previous invocation of the
    // splashscreen from the registry.
    private void ReadIncrements()
    {
      string sPBIncrementPerTimerInterval = 
             RegistryAccess.GetStringRegistryValue( 
                 REGVALUE_PB_MILISECOND_INCREMENT, "0.0015");
      double dblResult;
      if( Double.TryParse(sPBIncrementPerTimerInterval, 
              System.Globalization.NumberStyles.Float,
              System.Globalization.NumberFormatInfo.InvariantInfo, 
              out dblResult) )
        m_dblPBIncrementPerTimerInterval = dblResult;
      else
        m_dblPBIncrementPerTimerInterval = .0015;
      string sPBPreviousPctComplete = RegistryAccess.GetStringRegistryValue(
            REGVALUE_PB_PERCENTS, "" );
      if( sPBPreviousPctComplete != "" )
      {
        string [] aTimes = sPBPreviousPctComplete.Split(null);
        m_alPreviousCompletionFraction = new ArrayList();
        for(int i = 0; i < aTimes.Length; i++ )
        {
          double dblVal;
          if( Double.TryParse(aTimes[i],
                  System.Globalization.NumberStyles.Float, 
                  System.Globalization.NumberFormatInfo.InvariantInfo, 
                  out dblVal) )
            m_alPreviousCompletionFraction.Add(dblVal);
          else
            m_alPreviousCompletionFraction.Add(1.0);
        }
      }
      else
      {
        m_bFirstLaunch = true;
        lblTimeRemaining.Text = "";
      }      
    }
    // Method to store the intervals (in percent complete)
    // from the current invocation of
    // the splash screen to the registry.
    private void StoreIncrements()
    {
      string sPercent = "";
      double dblElapsedMilliseconds = ElapsedMilliSeconds();
      for( int i = 0; i < m_alActualTimes.Count; i++ )
        sPercent += ((double)m_alActualTimes[i]/
              dblElapsedMilliseconds).ToString("0.####",
              System.Globalization.NumberFormatInfo.InvariantInfo) + " ";
      RegistryAccess.SetStringRegistryValue( 
          REGVALUE_PB_PERCENTS, sPercent );
      m_dblPBIncrementPerTimerInterval = 1.0/(double)m_iActualTicks;
      RegistryAccess.SetStringRegistryValue( 
           REGVALUE_PB_MILISECOND_INCREMENT, 
           m_dblPBIncrementPerTimerInterval.ToString("#.000000",
           System.Globalization.NumberFormatInfo.InvariantInfo));
    }
    //********* Event Handlers ************
    // Tick Event handler for the Timer control.  
    // Handle fade in and fade out.  Also
    // handle the smoothed progress bar.
    private void timer1_Tick(object sender, System.EventArgs e)
    {
      lblStatus.Text = m_sStatus;
      if( m_dblOpacityIncrement > 0 )
      {
        m_iActualTicks++;
        if( this.Opacity < 1 )
          this.Opacity += m_dblOpacityIncrement;
      }
      else
      {
        if( this.Opacity > 0 )
          this.Opacity += m_dblOpacityIncrement;
        else
        {
          StoreIncrements();
          this.Close();
        }
      }
      if( m_bFirstLaunch == false && m_dblLastCompletionFraction 
          < m_dblCompletionFraction )
      {
        m_dblLastCompletionFraction += m_dblPBIncrementPerTimerInterval;
        int width = (int)Math.Floor(
           pnlStatus.ClientRectangle.Width * m_dblLastCompletionFraction);
        int height = pnlStatus.ClientRectangle.Height;
        int x = pnlStatus.ClientRectangle.X;
        int y = pnlStatus.ClientRectangle.Y;
        if( width > 0 && height > 0 )
        {
          m_rProgress = new Rectangle( x, y, width, height);
          pnlStatus.Invalidate(m_rProgress);
          int iSecondsLeft = 1 + (int)(TIMER_INTERVAL * 
            ((1.0 - m_dblLastCompletionFraction)/
              m_dblPBIncrementPerTimerInterval)) / 1000;
          if( iSecondsLeft == 1 )
            lblTimeRemaining.Text = string.Format( "1 second remaining");
          else
            lblTimeRemaining.Text = string.Format( "{0} seconds remaining", 
              iSecondsLeft);
        }
      }
    }
    // Paint the portion of the panel invalidated during the tick event.
    private void pnlStatus_Paint(object sender, 
         System.Windows.Forms.PaintEventArgs e)
    {
      if( m_bFirstLaunch == false && e.ClipRectangle.Width > 0 
            && m_iActualTicks > 1 )
      {
        LinearGradientBrush brBackground = 
          new LinearGradientBrush(m_rProgress, 
                                  Color.FromArgb(100, 100, 100),
                                  Color.FromArgb(150, 150, 255), 
                                  LinearGradientMode.Horizontal);
        e.Graphics.FillRectangle(brBackground, m_rProgress);
      }
    }
    // Close the form if they double click on it.
    private void SplashScreen_DoubleClick(object sender, System.EventArgs e)
    {
      CloseForm();
    }
  }
  /// A class for managing registry access.
  public class RegistryAccess
  {
    private const string SOFTWARE_KEY = "Software";
    private const string COMPANY_NAME = "MyCompany";
    private const string APPLICATION_NAME = "MyApplication";
    // Method for retrieving a Registry Value.
    static public string GetStringRegistryValue(string key, 
        string defaultValue)
    {
      RegistryKey rkCompany;
      RegistryKey rkApplication;
      rkCompany = Registry.CurrentUser.OpenSubKey(SOFTWARE_KEY, 
           false).OpenSubKey(COMPANY_NAME, false);
      if( rkCompany != null )
      {
        rkApplication = rkCompany.OpenSubKey(APPLICATION_NAME, true);
        if( rkApplication != null )
        {
          foreach(string sKey in rkApplication.GetValueNames())
          {
            if( sKey == key )
            {
              return (string)rkApplication.GetValue(sKey);
            }
          }
        }
      }
      return defaultValue;
    }
    // Method for storing a Registry Value.
    static public void SetStringRegistryValue(string key, 
         string stringValue)
    {
      RegistryKey rkSoftware;
      RegistryKey rkCompany;
      RegistryKey rkApplication;
      rkSoftware = Registry.CurrentUser.OpenSubKey(SOFTWARE_KEY, true);
      rkCompany = rkSoftware.CreateSubKey(COMPANY_NAME);
      if( rkCompany != null )
      {
        rkApplication = rkCompany.CreateSubKey(APPLICATION_NAME);
        if( rkApplication != null )
        {
          rkApplication.SetValue(key, stringValue);
        }
      }
    }
  }
}

 
posted on 2005-08-31 22:41  smartsoft 2005  阅读(5140)  评论(0编辑  收藏  举报