Click here to Skip to main content
16,021,765 members
Articles / Desktop Programming / Windows Forms
Article

Ye Aulde Application Starter

Rate me:
Please Sign up or sign in to vote.
4.45/5 (13 votes)
3 Dec 20057 min read 55.4K   894   72   15
Give your application a good start! Handle unhandled exceptions, ensure one instance running, use splash screen for lengthy load and more. All in one pack - enjoy!

Image 1

Introduction

There are problems we need to solve when we start writing a new Win Forms application. First of all, we want our applications to have a neat look, XP-style, or better, XP-theme aware. You'd like to have a nice splash screen taking the user's attention away while the application loads, especially, when it really takes some time for the application to load. Also, many applications should only run a single instance, and should not permit multiple instances simultaneously. There is also an exception handling problem, which is quite tricky, because not every exception is handled, especially while debugging. In release, however, all the exceptions should be caught, at least providing the application with an ability to crash 'gracefully'.

This article is an attempt to provide a template for solving these most typical tasks of an application creation.

Enabling XP themes

A control is considered to be XP-theme aware when its appearance changes in accordance with the XP-theme that is currently active.

Most of the .NET controls are just wrappers over standard Windows controls. In Windows XP, there are two versions of Windows control set - 6.0, which is XP-theme aware and 5.0, which is not. By default, .NET uses version 5.0. In .NET 1.0, the only way to make it use the 6.0 version was to write an application manifest, which is a separate file stored in the application directory. Somehow, this solution was not elegant enough, and in .NET 1.1 a new method - Application.EnableVisualStyles()was introduced. It should be run in the Main() procedure before Application.Run(). Its use, however, is not always successful. E.g. while putting a toolbar on the main form, the toolbar appears without any icons on the tool buttons. To correct this situation, run Application.DoEvents() immediately after Application.EnableVisualStyles(). This combination has proved to be quite reliable:

C#
[STAThread]
static void Main() {
  Application.EnableVisualStyles();
  Application.DoEvents();
  Application.Run(new MainForm());
}

For some controls to be XP-theme aware you also need to set the FlatStyle property to FlatStyle.System. These controls include ButtonBase inheritors (Button, CheckBox and RadioButton), GroupBox and Label, although for the Label control there is hardly any visual difference.

Ensuring one-instance running

If due to some considerations you don't want your application to have multiple instances running, you can use this simple one instance loader:

C#
[STAThread]
static void Main() {
  // Enable XP themed controls
  Application.EnableVisualStyles();
  Application.DoEvents();
  // ensure one instance running
  if(!MainForm.InstanceExists) {
    Application.Run(new MainForm());
  }
}
static Mutex mutex;
static string AppGuid = 
          "4B8CB7C2-1BD3-4db4-AFF0-A398466C5EEF";
static bool InstanceExists {
  get {
    bool notExists;
    mutex = new Mutex( false, "Local\\" + AppGuid, 
                                       out notExists ); 
    return !notExists;
  }
}

The trick is to use one of the kernel named objects, e.g. mutex, to mark the presence of application in the memory. The successive loads of the application should check for this mutex, and run only if it is not found.

You can use GUID to ensure that the mutex name is unique. Also, if your application could be run in terminal services, you should choose your strategy for one-instance application running. If you want your application to be unique in session scope - use the Local\ prefix for the mutex name. If you want it to be unique across sessions - use the Global\ prefix.

To handle unhandled exceptions

Some people recommend catching every exception that you can imagine, thinking that the number of try-catch blocks directly correspond to the robustness of your application. They hunt exceptions like dangerous beasts, considering every exception that escapes unhandled as the worst thing that can ever happen to your application.

Some people like using exceptions as a probe. Like "let's poke it and see if it growls". E.g. let's convert a string to a number and catch the exception if it couldn't.

Both forget one thing that exception is, by its name and nature, exceptional. It is not normal for an application to run into exceptions. Therefore, the presence of too many exception handlers clearly shows the bad design, not robustness. Also, using try-catch to find out things is definitely a bad coding practice. Quite contrary to this, you should do every possible check before making a risky call, to ensure that it would not result in exceptions.

Generally, an exception handling strategy should follow three simple rules:

  • preventing is better than handling,
  • handle only those exceptions that you know how to handle and leave the rest alone,
  • throwing exceptions is not the best way of error reporting, do not overuse it.

And what to do with exceptions that can come out unhandled. On one hand, exceptions should not go away unhandled leading your application to a violent crush, and on the other, there is no sense in handling the exception that you haven't expected, because no one can say if it is safe to continue application execution after it has happened. All we can do is to catch it in a generic way, log it and finalize our application gracefully.

There are three known ways of doing this:

  1. Putting Application.Run() in a try-catch block,
  2. Using Application.ThreadException event,
  3. Using AppDomain.UnhandledException event.

The first is the easiest one, although it is rarely mentioned and least recommended. Its main drawback is that it does not give you the possibility to continue with execution. Otherwise, it works OK, at least I have positive experience with it.

It is important to differentiate the unhandled exception handling for Debug and Release versions. While debugging, it's good to take advantage of the debugger break-in-the-code feature, in the release version you should log all the information about the error to a text file, so that the user could send it to you for analysis. To achieve this, the code should look like this:

C#
[STAThread]
static void Main() {
  #if DEBUG
    // do not catch unhandled exceptions while debugging
    Application.Run(new MainForm());
  #else
    // enable unhandled exceptions trapping for release version
    try {
      Application.Run(new MainForm());
    }
    catch(Exception ex) {
      new ExceptionDialog(ex).ShowDialog();
    }
  #endif
}

The catcher shows an exception handling dialog with 'save to text file' ability:

Image 2

The Application.ThreadException and AppDomain.UnhandledException events provide you with the ability to handle exceptions in abort-retry-ignore fashion. Unfortunately, I have no experience using them, and can only recommend you some useful articles on the topic:

Showing the splash screen

The main purpose of the splash screen is to inform the user that the program has really started, present useful information about the name, version and copyright, and draw attention while the program loads. The main splash screen requirements are:

  • start as soon as possible before the main form loads,
  • remain at the topmost form of the project,
  • when the project fully loads - hide on first user click anywhere on the project.

To start as soon as possible we need to show the splash form in the very beginning of the main form constructor, before any other component loads:

C#
public MainForm() {
  Splash.Show(this);
  // that is to imitate lengthy (5 sec) load
  Thread.Sleep(5000);  
  InitializeComponent();
}

To give the splash screen an appropriate appearance we should set the following properties:

C#
this.FormBorderStyle = 
        System.Windows.Forms.FormBorderStyle.FixedDialog;
this.ShowInTaskbar = false;
this.StartPosition = 
        System.Windows.Forms.FormStartPosition.CenterScreen;
this.TopMost = true;

in the form designer.

Then we should bind the click event of the form and its controls to the HideFrom handler:

C#
public formSplash() {
  InitializeComponent();
  this.Click += new System.EventHandler(this.HideForm);
  this.Deactivate +=new EventHandler(formSplash_Deactivate);
  foreach(Control c in GetChildControls(this)) {
    c.Click += new EventHandler(this.HideForm);
  }
}

private ArrayList GetChildControls(Control control) {
  ArrayList a = new ArrayList();
  foreach(Control c in control.Controls) {
    a.Add(c);
    foreach(Control cc in GetChildControls(c)) {
      a.Add(cc);
    }
  }
  return a;
}

public int Started = 0;
 
public void HideForm(object sender, System.EventArgs e) {
  if(Started>=1) {
    this.Close();
  }
}

private void formSplash_Deactivate(object sender, EventArgs e) {
  this.Started ++;
}

The Deactivate event is necessary to handle the case of the user clicking anywhere else on the application after it fully loads. The Started counter is used to ensure correct behavior during maximized or normal main form load, because different number of deactivation events occurs in each case. The Splash static class is used to simplify splash screen handling and taking most of the necessary code away from the main form.

This ensures form paint at the start:

C#
public static void Show(Form mainform) {
  mainform.Activated+=new EventHandler(mainform_Activated);
  splashForm = new formSplash();
  splashForm.Show();
  splashForm.Update();
}

And deals with the closing of splash screen by deactivation (clicking outside of it):

C#
private static void mainform_Activated(object sender, EventArgs e) {
  int count;
  if((sender as Form).WindowState==FormWindowState.Normal)
    count = 2;
  else
    count = 3;
  if(splashForm.Started < count)
    splashForm.Activate();
  else
    splashForm.Close();
}

The result is consistent and pleasant behavior of the splash form. All the complexity of its behavior is taken to the Splash class and the formSplash code. All you need to do in the main form is to call:

C#
Splash.Show(this);

in the first line of the constructor.

Summary

What we have finally acquired is a convenient template of a general purpose Win Forms project, implementing all the basic requirements of an up-to-date application.

This article uses the ideas and concepts described in my previous publications:

Acknowledgements

  • Jörgen Sigvardsson - for very useful ideas on mutex naming.

History

  • 3rd December, 2005 - Article submitted.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
General:: TtZz Pin
DIEGO FELDNER25-Nov-09 3:02
DIEGO FELDNER25-Nov-09 3:02 
QuestionUnique instance of a form Pin
thachvv1811-Jul-07 6:54
thachvv1811-Jul-07 6:54 
GeneralBug Pin
crnriman29-Jan-07 11:25
crnriman29-Jan-07 11:25 
Generalis it error Pin
margiex9-Dec-05 23:21
margiex9-Dec-05 23:21 
when i click the mainform's simulate unexpected error button, ExceptionDialog can't be showed, it's the default normal exception dialog, why?
GeneralMutex / Multiple Instances Pin
Christopher Scholten8-Dec-05 22:11
professionalChristopher Scholten8-Dec-05 22:11 
AnswerRe: Mutex / Multiple Instances Pin
miklovan8-Dec-05 23:53
miklovan8-Dec-05 23:53 
GeneralRemote Desktop/Terminal Services Pin
Jörgen Sigvardsson4-Dec-05 7:00
Jörgen Sigvardsson4-Dec-05 7:00 
AnswerRe: Remote Desktop/Terminal Services Pin
miklovan4-Dec-05 7:52
miklovan4-Dec-05 7:52 
GeneralRe: Remote Desktop/Terminal Services Pin
Jörgen Sigvardsson4-Dec-05 8:04
Jörgen Sigvardsson4-Dec-05 8:04 
AnswerRe: Remote Desktop/Terminal Services Pin
miklovan4-Dec-05 17:54
miklovan4-Dec-05 17:54 
GeneralSuggestions Pin
leppie3-Dec-05 5:11
leppie3-Dec-05 5:11 
GeneralRe: Suggestions Pin
miklovan3-Dec-05 17:45
miklovan3-Dec-05 17:45 
GeneralRe: Suggestions Pin
Mikael Levn7-Dec-05 3:15
Mikael Levn7-Dec-05 3:15 
GeneralRe: Suggestions Pin
CodeGimp11-Dec-05 22:33
CodeGimp11-Dec-05 22:33 
GeneralRe: Suggestions Pin
miklovan11-Dec-05 23:20
miklovan11-Dec-05 23:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.