Controlling a Windows Forms Application

Controlling a Windows Forms Application

The Application class is used to provide basic application-level behavior for a Windows Forms application. You never create an instance of the Application class directly; instead, you invoke static methods and properties exposed by the Application class. The static methods provided by the Application class, such as Run and Exit, enable you to control how your application starts and stops. The static properties exposed by the Application class enable you to determine information such as the path used to store application data. As mentioned, you can’t create an instance of the Application class, but the following sections will show you how to supply context objects and event handlers that collaborate with the Application class to control the behavior of your application.

Starting an Application

A Windows Forms application is started by calling the static Application.Run method, as shown here:

Application.Run(new Form1());

There are three overloaded versions of the Run method. This example shows the most commonly used version of Run, which accepts the top-level form for the application as a parameter. The Run method will create a message loop and display the form passed as a parameter. This version of Run is called for you automatically in Windows Forms projects created by Microsoft Visual Studio .NET.

Alternatively, you can call Run with no parameters, as shown here:

Application.Run();

When Run is called with no parameters, the Application class will create a Windows message loop on the current thread but won’t display an initial form. This version of Run is useful if you’ll be displaying the form at a later time, or perhaps not displaying a form at all.

The third version of the Run method accepts an ApplicationContext object that will be used by the Application class. The default ApplicationContext object used with the previous versions of the Run method will automatically exit the application when the main form is closed. By deriving a new class from ApplicationContext, you can define your own specialized behavior. The following code is an example of a class derived from ApplicationContext that overrides the built-in behavior for closing the application:

public class QueryApplicationContext: ApplicationContext
{
    private string _saveMessage = "Exit and discard changes?";
    private string _caption = "Application Exit";

    public QueryApplicationContext(MainForm form)
    {
        form.Show();
        form.Closing += new CancelEventHandler(FormClosing);
    }

    private void FormClosing(object sender, CancelEventArgs e)
    {
        DialogResult result = MessageBox.Show(saveMessage,
                                       _caption,
                                       MessageBoxButtons.YesNo,
                                       MessageBoxIcon.Question,
                                       MessageBoxDefaultButton.Button1);
        if(result == DialogResult.Yes)
        {
            ExitThread();
        }
        else
        {
            e.Cancel = true;
        }
    }
}

In this code, the FormClosing method is used to handle the Closing event for the application’s main form. The Closing event is raised when a form is closed, enabling its handler to prevent closure through the CancelEventArgs object passed as a parameter. In the FormClosing method, a confirmation message box is displayed to the user. If the user clicks the Yes button, ExitThread is called, which causes the application to terminate. If the user clicks No, the Closing event is canceled.

To use a specialized version of the ApplicationContext class, you pass an instance of the class to Application.Run, as shown here:

static void Main() 
{
    MainForm form = new MainForm();
    QueryApplicationContext appContext;
    appContext = new QueryApplicationContext(form);
    Application.Run(appContext);
}
Determining Application Path Information

When locating files needed by your Windows Forms application, you often need to determine the path information. The Application class includes properties that simplify the task of determining commonly used paths, such as your application path, and folders used for storing application data.

The path to the executable that launched the current application can be obtained through the ExecutablePath property, as shown here:

string executablePath = Application.ExecutablePath;

To retrieve the start-up directory path, use the StartupPath property, as shown here:

string startupPath = Application.StartupPath;

According to Windows application design guidelines, application data should be stored in specific ways to simplify the way users interact with applications. Application data should always be stored in a subdirectory of the Documents And Settings folder, with user-specific data stored in a path under My Documents (which maps to Documents And Settings\<user_name>). Storing data in this way allows users to have a single location for their data and allows applications to be used in roaming profiles. The Application class helps you do the right thing by calculating these paths for you.

Common data that’s shared by all users of a Windows Forms application should be stored in an application-specific subdirectory of the Documents And Settings folder. To determine the location of this subdirectory, use the Common­AppDataPath property, as shown here:

string commonDataPath = Application.CommonAppDataPath;

The location for this directory is in the <company_name>\<product_name> \<version> subdirectory of the Documents And Settings\All Users\Application Data folder. The company name and product name are determined by inspecting the assembly attributes in the AssemblyInfo.cs file. If these attributes are empty, as they are when a project is initially created by Visual C# .NET, the company name and product name will be set to the name of the Visual C# project.

This directory won’t be created unless you access the property to ask for its location. At that point, the Application class will assume that you have an interest in storing information in the common application data directory and will create a directory if it doesn’t already exist.

The path for user-specific information is obtained through the UserAppDataPath property, as shown here:

string userDataPath = Application.UserAppDataPath; 

The path for per-user information follows the same rules as common application data, except that the path is composed using the current user’s identity. For example, a user named Ali would have a per-user path located in the Documents And Settings\Ali\Application Data folder.

Performing Idle Work

The Application class includes an event that’s raised when the message loop is performing no work. This event, known as the Idle event, is used to trigger low-priority tasks that are performed when no other work is being done. For example, you can use the Idle event to trigger an update of user interface elements such as status bars or other informational displays.

The first step in handling the Idle event is to define a method that conforms to the EventHandler delegate signature, as shown here:

private void OnIdle(object sender, EventArgs e)
{
    // Perform idle processing here.
}

Before your Idle event handler will be called, you must subscribe to Idle events by creating an EventHandler delegate and appending it to any existing Application.Idle delegates, as shown here:

MyForm()
{
    
    

    Application.Idle += new EventHandler(OnIdle);
}
Closing an Application

By default, an application will exit when the main form is closed. As discussed in the previous section, you can override this behavior through the ApplicationContext class. Another approach is to interact with the Application class directly.

The Application class provides the Exit method, which can be used to initialize shutdown for an application. The Exit method can be called by any thread, as shown here:

Application.Exit();

The Exit method doesn’t cause the application to close immediately. Instead, all of the message pumps are closed and the Run method returns. Returning from the Run method will typically cause the application to terminate, as the application’s Main method usually contains only code used to clean up application resources after Run, as shown here:

static void Main() 
{
    
    

    Application.Run(new Form1());
    appMutex.Close();
    stream.Close();
    
    

}

You generally won’t call Exit directly because it immediately closes any forms that are currently open. The preferred way to close an application is to call the main form’s Close method.

To close a specific thread in a Windows Forms application, call the ExitThread method, as shown here:

if(Application.MessageLoop == true)
    Application.ExitThread();

Here the MessageLoop property is used to determine whether a message loop exists on the current thread before calling ExitThread. If the current thread has a message loop, calling ExitThread causes the message loop to close. If the Run method has been called for the current thread, calling ExitThread causes Run to return to its caller.

To receive notification when an application is exiting, add a handler for the ApplicationExit event. As shown in the following code, you can use this event handler to clean up resources owned by the application:

// Form constructor
AddressForm()
{
    
    

    Application.ApplicationExit += new EventHandler(OnAppExit);
}

private void OnAppExit(object sender, EventArgs e)
{
    appMutex.Close();
    stream.Close();
}

When handling the ApplicationExit event, you can clean up resources that haven’t yet been disposed of; however, you can’t control the exit of the application. When your ApplicationExit event handler is invoked, the application has already begun to shut down.



Part III: Programming Windows Forms