3.1 Application Structure

All Windows Forms applications have something in common, regardless of whether they are created with Visual Studio .NET or written from scratch:

  • They all have at least one form, the main application window.

  • They all need to display that form at start up.

  • They must shut down correctly at the appropriate time.

This section describes the basic structure that all applications have and the way that their lifetime is managed by the .NET Framework.

3.1.1 Startup and Shutdown

All programs have to start executing somewhere, and .NET applications have a special method that is called when the application is run. This method is responsible for creating whatever windows the application requires and performing any other necessary initialization.

In C# and Visual Basic, this entry point is always a static method called Main . It doesn't matter which class this is defined in, although Visual Studio always makes it a member of the main form that it puts in any new project. It generates code like the C# code shown in Example 3-1.

Example 3-1. A typical application entry point
 [STAThread] static void Main()  {     Application.Run(new Form1()); } 

Although Visual Studio makes Main visible if you're developing with C#, it hides it if you're developing with Visual Basic. In Visual Basic projects, the code for Main is not displayed in the form's code window, nor is it listed in Class View or in the Object Browser. However, examining a compiled Windows Forms application using ILDASM, the .NET disassembler, indicates that a hidden public method named Main is present in the application's main form, as Figure 3-1 shows. Its source code corresponds to that shown in Example 3-2.

Figure 3-1. The hidden VB entry point revealed in ILDASM
figs/winf_0301.gif
Example 3-2. An application entry point in VB
 <STAThread> Public Shared Sub Main()    Application.Run(new Form1()) End Sub 

If your application needs to read the command-line parameters, you can modify Main (or, if you're coding in Visual Basic, you can add it yourself, rather than have the compiler add it) so that it takes a parameter of type string[] or String() . You will then be passed an array of strings, one for each argument. You can also change the return type to int if you wish to return an exit code. Examples Example 3-3 and Example 3-4 illustrate these techniques. The STAThread custom attribute is a backward-compatibility feature that will be discussed shortly.

Example 3-3. C# application entry point with parameters
 [STAThread] static int Main(string[] args)  {     Application.Run(new Form1()); } 
Example 3-4. VB application entry point with parameters
 <STAThread> _ Public Shared Function Main(args As String()) As Integer    Application.Run(New Form1()) End Sub 

It is also possible to retrieve the command-line arguments using the Environment class's GetCommandLineArgs method. You might find this approach easier because you can call this method anywhere in your program, not just in Main . It also means you don't need to modify the Main method's signature, and in VB, it means you don't need to define a Main method at all.


The Main function turns out to be trivial in the majority of applications because most interesting initialization takes place inside individual forms. All that happens in Main is an instance of the program's main user interface ( Form1 ) is created, and control is then passed to the framework's Application class, which manages the application's execution for the remainder of its lifetime. The program runs until the Application class decides it is time to exit. By default, this is when the main form is closed.

3.1.2 The Application Class

To do its job, the Windows Forms framework needs to have a high degree of control over our application. In particular, it must respond correctly to the kind of input that all Windows applications are required to handle, such as mouse clicks and redraw requests . This means the framework needs to be in charge of our application's main thread most of the time; otherwise , it cannot deal with these events. [1]

[1] This is similar to the way that classic Win32 applications must service the message queue.

Although our application's execution is stage-managed by the framework, we can still influence its behavior by using the Application class. For example, we can tell the framework to shut down our program by calling the Application.Exit method. In fact, interacting with the Application class is the first thing most programs do. They typically start like Example 3-1, calling Application.Run to surrender control to Windows Forms. This causes the framework to display the Form object that it is given, after which it sits and waits for events. From then on, our code will only be run as a result of some activity, such as a mouse click, causing the framework to call one of our event handlers.

This event-driven style of execution is an important feature of Windows Forms. The framework is able to deal with events only because we leave it in charge. Of course, while one of our event handlers is running (e.g., the code in a Click handler is executing), we are temporarily back in charge, which means the framework will be unable to process any other events until our event handler returns. Most of the time, this is a good thing, because life would become unbearably complex if we could be asked to start handling a new event before we had finished dealing with the previous one; reentrant code is notoriously hard to get right, so it is a good thing that it is not usually required.

The only problem is that if our event handlers take a long time to execute, the user interface will become unresponsive . Until our code returns control to the framework, the user will not be able to click on or type into our program, or to move the windows around. (Strictly speaking the input won't be lostsuch events are stored in a queue, just as they are with normal Windows programs. But there will be no response to this input until the handler returns.) We can't even give the user a way to abort the operation if it takes too long because the inability to process user input makes it difficult to support any kind of Cancel button.

While the obvious solution is to avoid writing event handlers that take too long to execute, this is not always possible. Fortunately, long-running event handlers can choose to give the framework a chance to deal with any events that may be queued up and awaiting processing. The Application class provides a method called DoEvents . This handles any pending input and then returns. Of course, any code that calls this method needs to be careful, because it is inviting reentrant behavior, so whenever you call this method, you must consider the implications of another of your event handlers being run before DoEvents returns. But it does mean that slow code has a way of making sure the application does not appear to lock up completely.

The DoEvents method is not the only way of reentering the framework's event handling code. Whenever you display a modal dialog (e.g., by using the MessageBox class, or by displaying a form with the ShowDialog method, as described later), Windows Forms is once again in charge of your thread and will process events for you for as long as the window is displayed.

Because the Application class effectively owns our thread, we must get its help when we wish to shut down our program. By default, it monitors the form that we passed to its Run method (usually the program's main form), and it exits when that form closes . However, we can also force a shutdown by calling its Exit method; this closes all windows and then exits. (In other words, when Exit is called, the Run method returns. This will usually cause the program to exit, because the only thing the Main function usually does is call the Run method, as shown in Example 3-1. When the Main method finishes, the program exits.)

The Application class also provides a few miscellaneous utility features. For example, you can modify the way exceptions are handled. If any of your event handlers should throw an exception, the default behavior is for the application to terminate. But the Application class has a static (or shared) event called ThreadException that is raised whenever such an exception occurs; handling this event prevents the unhandled exception dialog from appearing, and the application will not exit unless you explicitly terminate it in your handler. The Application class also exposes an Idle event that is fired whenever some input has just been handled and the application is about to become idle. You could use this to perform background processing tasks .

3.1.3 Forms and Threads

With all this talk of the Application object owning our thread, and of keeping the user interface responsive in the face of long-running operations, you may well be wondering about the use of threads in Windows Forms applications. Although it is possible to write multithreaded Windows Forms applications, there are some serious restrictions. A full discussion of multithreaded programming is well beyond the scope of this book, but it is important to know what the restrictions are.

There is one fundamental rule for threads in Windows Forms applications: you can only use a control's methods or properties from the thread on which it was created. In other words, you must never call any methods on a control from a worker thread, [2] nor can you read or write its properties. The only exceptions to this rule are calls to the Invoke , BeginInvoke , and EndInvoke methods and to the InvokeRequired property, which can all be used from any thread.

[2] A worker thread is any thread other than the UI thread.

This may seem a surprisingly draconian restriction, but it is not as bad as it sounds. It is possible to use the Control class's Invoke method to run code on the right thread for the controlyou just pass a delegate to the Invoke method, and it calls that delegate for you on the correct thread. The call will not occur until the next time the Windows Forms framework processes messages on the control's thread. (This is to avoid reentrancy.) Invoke waits for the method to complete, so if an event is being handled by the user interface thread currently, Invoke will wait for that handler to finish. Beware of the potential for deadlock here; BeginInvoke is sometimes a better choice because it doesn't wait for the invoked method to finish runningit just adds the request to run the method to the framework's internal event queue and then returns immediately. (It is possible that your user interface thread was waiting for your worker thread to do something, so if you also make your worker thread wait for the user interface thread to do something, both threads will deadlock, causing your application to freeze.)

The InvokeRequired property is a bool or Boolean that tells you whether you are on the right thread for the control ( InvokeRequired returns False ) or not ( InvokeRequired returns True ). This can be used in conjunction with the BeginInvoke method to force a particular method to run on the correct thread, as shown in the following C# code fragment:

 private void MustRunOnUIThread() {     if (InvokeRequired)     {         BeginInvoke(new MethodInvoker(MustRunOnUIThread));         return;     }     ... invoke not required, must be on right thread already } 

This method checks to see if it is on the right thread, and if not, it uses BeginInvoke to direct the call to the control's own thread. [3] MethodInvoker is a delegate type defined by Windows Forms that represents methods with no parameters and no return value (or, in Visual Basic, a Sub with no parameters). In fact, you can use any delegate type you like, and there is an overloaded version of Control.BeginInvoke that takes a parameter list (as an object array) as its second parameter, allowing you to use a delegate that requires parameters to be passed.

[3] This particular example shows a member function of some class that derives from Control this is why it is able to use the InvokeRequired and BeginInvoke members directly. This is not a requirementthe methods are public, so you can call them on any control.

You may also be wondering why Visual Studio .NET places an STAThread attribute on your application's Main function, as shown in Example 3-1. This is required for ActiveX controls to work. If you want to use ActiveX controls, the COM runtime must be initialized in a particular way on the user interface thread. In .NET, COM is always initialized by the CLR, so we use this attribute to tell the CLR how we would like it to configure COM on this thread. A full discussion of COM interop and COM's threading model is beyond the scope of this book, although if you are familiar with COM, you might find it helpful to know that this attribute ensures that the main thread will belong to an STA.

So the Application class is responsible for managing our application's lifetime, main thread, and event processing. But all the interesting activity surrounds the forms that make up our applications, so let's now look in more detail at the Form class.



. Net Windows Forms in a Nutshell
.NET Windows Forms in a Nutshell
ISBN: 0596003382
EAN: 2147483647
Year: 2002
Pages: 794

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net