applica-Application Structure All Windows Forms applications have something in common, regardless ofwhether they are created with Visual Studio .NET or written from scratch: • They all h
Trang 1A Desktop Quick Reference
IN A NUTSHELL
.NET WINDOWS FORMS
Controls, Forms, Menus, GDI+ and more
…
Trang 2.NET WINDOWS FORMS
IN A NUTSHELL
Ian Griffiths and Matthew Adams
Trang 3Chapter 1
Table of Contents
Preface ix
Part I Introduction to Windows Forms
1 .NET and Windows Forms Overview 3
2 Controls 23
Trang 44 Menus and Toolbars 88
7 Redrawing and GDI+ 141
9 Controls and the IDE 233
10 Data Binding 269
Trang 5Part II Windows Forms Reference
11 How To Use This Quick Reference 303
Finding a Quick-Reference Entry 303 Reading a Quick-Reference Entry 304 12 Converting from C# to VB Syntax 309
General Considerations 309 Classes 310 Structures 310 Interfaces 311 Class, Structure, and Interface Members 311 Delegates 315 Enumerations 315 13 The System.ComponentModel Namespace .317
14 The System.Drawing Namespace 389
15 The System.Drawing.Drawing2D Namespace 459
16 The System.Drawing.Imaging Namespace .486
17 The System.Drawing.Printing Namespace 515
18 The System.Drawing.Text Namespace .537
19 The System.Windows.Forms Namespace 541
20 The System.Windows.Forms.Design Namespace .810
Part III Appendixes A Namespaces and Assemblies .834
B Type, Method, Property, Event, and Field Index .835
Index 905
Trang 6Chapter 3Forms, Apps, Containers
class As with any user interface element, theFormclass inherits from theControl
class, but it adds windowing features, such as management of the window borderand interaction with the Windows taskbar All Windows Forms applications have
at least one class derived fromForm
In this chapter we will examine the structure of a typical Windows Forms tion and the way its constituent forms are created We will look at theprogramming model for forms, and the way that the Visual Studio NET FormsDesigner uses this model We will look in detail at the relationship between aform and the controls it contains, and also at the relationships that can existbetween forms The mechanisms underpinning the automatic layout featuresdescribed in the previous chapter will be examined, and we will see how to usethese to add our own custom layout facilities
applica-Application Structure
All Windows Forms applications have something in common, regardless ofwhether 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 waythat their lifetime is managed by the NET Framework
Trang 7Forms, Apps,
Startup and Shutdown
All programs have to start executing somewhere, and NET applications have aspecial method that is called when the application is run This method is respon-sible for creating whatever windows the application requires and performing anyother necessary initialization
In C# and Visual Basic, this entry point is always a static method calledMain Itdoesn’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 likethe C# code shown in Example 3-1
Although Visual Studio makesMainvisible if you’re developing with C#, it hides it
if you’re developing with Visual Basic In Visual Basic projects, the code forMainisnot displayed in the form’s code window, nor is it listed in Class View or in theObject Browser However, examining a compiled Windows Forms applicationusing ILDASM, the NET disassembler, indicates that a hidden public methodnamed Main is present in the application’s main form, as Figure 3-1 shows Itssource code corresponds to that shown in Example 3-2
Example 3-1 A typical application entry point
Trang 8If 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 havethe compiler add it) so that it takes a parameter of typestring[]orString( ) Youwill then be passed an array of strings, one for each argument You can alsochange the return type tointif you wish to return an exit code Examples 3-3 and3-4 illustrate these techniques The STAThread custom attribute is a backward-compatibility feature that will be discussed shortly
It is also possible to retrieve the command-line arguments using the
Environment class’s GetCommandLineArgs method You might findthis approach easier because you can call this method anywhere inyour program, not just in Main It also means you don’t need tomodify theMainmethod’s signature, and in VB, it means you don’tneed to define aMain method at all
TheMainfunction turns out to be trivial in the majority of applications becausemost interesting initialization takes place inside individual forms All that happens
inMainis an instance of the program’s main user interface (Form1) is created, andcontrol is then passed to the framework’sApplicationclass, which manages theapplication’s execution for the remainder of its lifetime The program runs untiltheApplicationclass decides it is time to exit By default, this is when the mainform is closed
The Application Class
To do its job, the Windows Forms framework needs to have a high degree ofcontrol over our application In particular, it must respond correctly to the kind ofinput that all Windows applications are required to handle, such as mouse clicks
Example 3-2 An application entry point in VB
<STAThread> Public Shared Sub Main( )
Trang 9method In fact, interacting with the Application class is the first thing mostprograms do They typically start like Example 3-1, calling Application.Run tosurrender control to Windows Forms This causes the framework to display the
Formobject 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 Ofcourse, 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 work 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 unbearablycomplex if we could be asked to start handling a new event before we had finisheddealing with the previous one; reentrant code is notoriously hard to get right, so it
frame-is a good thing that it frame-is not usually required
The only problem is that if our event handlers take a long time to execute, theuser interface will become unresponsive Until our code returns control to theframework, the user will not be able to click on or type into our program, or tomove the windows around (Strictly speaking the input won’t be lost—suchevents are stored in a queue, just as they are with normal Windows programs Butthere will be no response to this input until the handler returns.) We can’t evengive 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 toexecute, this is not always possible Fortunately, long-running event handlers canchoose to give the framework a chance to deal with any events that may bequeued up and awaiting processing The Applicationclass provides a methodcalledDoEvents This handles any pending input and then returns Of course, anycode that calls this method needs to be careful, because it is inviting reentrantbehavior, so whenever you call this method, you must consider the implications
of another of your event handlers being run beforeDoEventsreturns But it doesmean that slow code has a way of making sure the application does not appear tolock up completely
The DoEvents method is not the only way of reentering the framework’s eventhandling code Whenever you display a modal dialog (e.g., by using the
MessageBox class, or by displaying a form with the ShowDialog method, asdescribed later), Windows Forms is once again in charge of your thread and willprocess events for you for as long as the window is displayed
* This is similar to the way that classic Win32 applications must service the message queue.
Trang 10Because the Applicationclass effectively owns our thread, we must get its helpwhen we wish to shut down our program By default, it monitors the form that wepassed to itsRunmethod (usually the program’s main form), and it exits when thatform closes However, we can also force a shutdown by calling itsExitmethod;this closes all windows and then exits (In other words, whenExitis called, the
Runmethod returns This will usually cause the program to exit, because the onlything the Main function usually does is call the Run method, as shown inExample 3-1 When theMain method finishes, the program exits.)
The Application class also provides a few miscellaneous utility features Forexample, you can modify the way exceptions are handled If any of your eventhandlers should throw an exception, the default behavior is for the application toterminate But the Application class has a static (or shared) event called
ThreadExceptionthat is raised whenever such an exception occurs; handling thisevent prevents the unhandled exception dialog from appearing, and the applica-tion will not exit unless you explicitly terminate it in your handler The
Applicationclass also exposes anIdleevent that is fired whenever some input hasjust been handled and the application is about to become idle You could use this
to perform background processing tasks
Forms and Threads
With all this talk of theApplicationobject owning our thread, and of keeping theuser interface responsive in the face of long-running operations, you may well bewondering about the use of threads in Windows Forms applications Although it ispossible to write multithreaded Windows Forms applications, there are someserious restrictions A full discussion of multithreaded programming is well beyondthe 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: youcan 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,*nor can you read or write its properties The only exceptions tothis rule are calls to theInvoke,BeginInvoke, andEndInvokemethods and to the
InvokeRequired property, which can all be used from any thread
This may seem a surprisingly draconian restriction, but it is not as bad as itsounds It is possible to use theControlclass’sInvokemethod to run code on theright thread for the control—you just pass a delegate to theInvokemethod, and itcalls that delegate for you on the correct thread The call will not occur until thenext time the Windows Forms framework processes messages on the control’sthread (This is to avoid reentrancy.)Invokewaits for the method to complete, so
if an event is being handled by the user interface thread currently,Invokewill waitfor 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 tofinish running—it just adds the request to run the method to the framework’sinternal event queue and then returns immediately (It is possible that your userinterface thread was waiting for your worker thread to do something, so if you
* A worker thread is any thread other than the UI thread.
Trang 11BeginInvokemethod to force a particular method to run on the correct thread, asshown in the following C# code fragment:
private void MustRunOnUIThread( )
This method checks to see if it is on the right thread, and if not, it uses
BeginInvoketo direct the call to the control’s own thread.* MethodInvokeris a gate type defined by Windows Forms that represents methods with no parametersand no return value (or, in Visual Basic, aSubwith no parameters) In fact, youcan use any delegate type you like, and there is an overloaded version ofControl BeginInvokethat takes a parameter list (as anobjectarray) as its second param-eter, allowing you to use a delegate that requires parameters to be passed
dele-You may also be wondering why Visual Studio NET places anSTAThreadattribute
on your application’sMainfunction, as shown in Example 3-1 This is required forActiveX controls to work If you want to use ActiveX controls, the COM runtimemust 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 COMinterop and COM’s threading model is beyond the scope of this book, although ifyou are familiar with COM, you might find it helpful to know that this attributeensures that the main thread will belong to an STA
So theApplicationclass is responsible for managing our application’s lifetime, mainthread, and event processing But all the interesting activity surrounds the formsthat make up our applications, so let’s now look in more detail at theForm class
The Form Class
All windows in a Windows Forms application are represented by objects of sometype deriving from theFormclass Of course,Formderives fromControl, as do allclasses that represent visual elements, so we have already seen much of what itcan do in the previous chapter But we will now look at the features that theForm
class adds
* 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 ment—the methods are public, so you can call them on any control.
Trang 12require-You will rarely use theFormclass directly—any forms you define in your tion will be represented by a class that inherits fromForm Adding a new form inVisual Studio NET simply adds an appropriate class definition to your project.
applica-We will examine how it structures these classes when generating new forms, and
we will look at how it cleans up any resource used by the form when it isdestroyed Then, we will consider the different types of forms Finally, we willlook at extender properties These provide a powerful way of extending thebehavior of all controls on a form to augment the basicControl functionality
The Forms Designer
Most forms are designed using the Forms Designer in Visual Studio NET This isnot an essential requirement—the designer just generates code that you couldwrite manually instead It is simply much easier to arrange the contents of a formvisually than it is to write code to do this
When you add a new form to a project, a new class definition is created TheDesigner always uses the same structure for the source code of these classes Theybegin withprivatefields in C# andFriendfields in VB to hold the contents of theform (The Designer inserts new fields here as you add controls to the form.) Next
is the constructor, followed by the Dispose and InitializeComponent methods;these are all described below If this is the main form in your application, theprogram’s entry point (the Main method described above) will follow in C#programs; in VB programs, it will be added by the compiler at compile time, butwill not be displayed with the form’s source code Finally, any event handlers forcontrols on your form will be added at the end of the class
The Designer does not make it obvious where you are expected to add any code ofyour own, such as fields or methods other than event handlers This is because itdoesn’t matter—Visual Studio NET is pretty robust about working around you
It is even happy for you to move most of the code that it generates if you don’tlike the way it arranges things, with the exception of the code inside the
InitializeComponentmethod, which you should avoid modifying by hand (Theeditor hides this code by default to discourage you from changing it.)
Initialization
Any freshly created form will contain a constructor and anInitializeComponent
method The jobof these methods is to make sure a form is correctly initializedbefore it is displayed
The generated constructor is very simple—it just calls the InitializeComponent
method The intent here is that the Forms Designer places all its initializationcode inInitializeComponent, and you will write any initialization that you require
in the constructor The designer effectively owns InitializeComponent, and it isrecommended that you avoid modifying its contents, because this is liable toconfuse the Designer So when you look at the source code for a form class, VisualStudio NET conceals theInitializeComponentmethod by default—it is lurking
Trang 13Forms, Apps,
behind a line that appears as “Windows Form Designer generated code.”*You cansee this code by clicking on the + symbol at the left of this line in the editor
You must not make any modifications to the overall structure of the
InitializeComponentmethod It is usually acceptable to make small
changes to existing lines, or to remove them entirely, but more
sub-stantial changes will almost certainly confuse Visual Studio NET,
and you could find that you can no longer edit your form visually in
the designer Most changes can be made using the Forms designer or
by modifying values in its Properties window, which causes Visual
Studio to update theInitializeComponent method automatically
Although the theory is that you will never need to modify anything inside this ated code, you may occasionally have to make edits If you do make such changes
gener-by hand, you must be very careful not to change the overall structure of the method,
as this could confuse the Designer, so it is useful to know roughly how the method
is arranged It begins by creating the objects that make up the UI: each control onthe form will have a corresponding line calling thenewoperator, and store the result
in the relevant field In C#, for example, such code appears as follows:
this.button1 = new System.Windows.Forms.Button( );
this.label1 = new System.Windows.Forms.Label( );
this.textBox1 = new System.Windows.Forms.TextBox( );
and in VB, it appears as follows:
Me.Button1 = New System.Windows.Forms.Button( )
Me.Label1 = New System.Windows.Forms.Label( )
Me.TextBox1 = New System.Windows.Forms.TextBox( )
Next, there will be a call to theSuspendLayoutmethod, which is inherited from the
Controlclass Layout is discussed in detail later on, but the purpose of this call is
to prevent the form from attempting to rearrange itself every time a control is set
up Then each control is configured in turn—any necessary properties are set(position, name, and taborder, at a minimum), and event handlers (in C# only)are added In C#, this looks like the following:
this.textBox1.Location = new System.Drawing.Point(112, 136);
The corresponding VB code appears as follows:
Me.TextBox1.Location = New System.Drawing.Point(112, 136)
Trang 14After this, the form’s size is set and then all the controls are added to itsControls
collection (Simply creating controls and storing them in private fields is not enough
to make them appear on screen—they must be explicitly added to the form onwhich they are to appear; this process will be discussed in detail later.) Finally, the
ResumeLayoutmethod, which is inherited from theControlclass, is called This isthe counterpart of the earlier call toSuspendLayout, and it indicates to the form thatthe various additions and modifications are complete, and that it won’t be wastingCPU cycles when it manages its layout This call will also cause an initial layout to
be performed, causing any docked controls to be positioned appropriately
Disposal
The other method created on all new forms is the Dispose method This runswhen the form is destroyed and frees any resources that were allocated for theform In fact, all controls have twoDisposemethods: one public, supplied by theframework, and one protected, which you usually write yourself To understandwhy, we must first look at the way resources are normally released in NET.The CLR has a garbage collector, which means that when objects fall out of use,the memory used by those objects will eventually be freed automatically Classescan have special functions called finalizers, which are run just before the garbagecollector frees an object Classes in the NET Framework that represent expensiveresources such as window handles usually have finalizers that release theseresources So in the long run, there will be no resource leaks—everything willeventually be freed either by the garbage collector or by the finalizers that thegarbage collector calls Unfortunately, the garbage collector only really caresabout memory usage, and only bothers to free objects when it is low on memory.This means that a very long time (minutes or even hours) can pass between anobject falling out of use and the garbage collector noticing and running its final-izer This is unacceptable for many types of resources, especially the kinds used byGUI applications (Although current versions of Windows are much moreforgiving than the versions of old, hogging graphical resources has never been agood idea and is best avoided even today.)
So the NET Framework defines a standard idiom for making sure such resourcesare freed more quickly, and the C# language has special support for this idiom.Objects that own expensive resources should implement the IDisposableinter-face, which defines a single method,Dispose If code is using such an object, assoon as it has finished with the object it should call itsDisposemethod, allowing it
to free the resources it is using (Such objects usually also have finalizers, so if theclient code forgets to callDispose, the resources will be freed eventually, if some-what late But this is not an excuse for not calling the method.)
The Control class (and therefore any class deriving from it) implements
IDisposable, as do most of the classes in GDI+, so almost everything you use inWindows Forms programming relies on this idiom Fortunately, the C# languagehas special support for it The usingkeyword can automatically free disposableresources for us at the end of a scope:
Trang 15When the code exits the block that follows theusingstatement, theBrushobject’s
Dispose method will be called (The Brushclass is part of GDI+, and it mentsIDisposable; this example is typical of redraw code in a custom control.)The most important feature of this construct is that it will callDisposeregardless
imple-of how we leave the block Even if the code returns from the middle imple-of the block
or throws an exception,Disposewill still be called, because the compiler puts thiscode in afinally block for us.*
Unfortunately, Visual Basic does not have any equivalent to using
blocks in C# You must remember to callDispose yourself
Forms typically have a lot of resources associated with them, so it is not surprisingthat they are always required to support this idiom In fact, all user elements are—theControlclass enforces this because it implementsIDisposable The good news
is that most of the work is done for us by theControlclass, as is so often the case
It provides an implementation that callsDisposeon all the controls contained bythe form and frees all resources that the Windows Forms framework obtained onyour behalf for the form But it also provides us with the opportunity to free anyresources that we may have acquired that it might not know about (For example,
if you obtain a connection to a database for use on your form, it is your bility to close it when the form is disposed.)
responsi-The picture is complicated slightly by the fact that there are two times at whichresource disposal might occur Not only must all resources be freed whenDispose
is called, they must also be freed if the client has failed to callDisposeby the timethe finalizer runs The model used by theControl class†enables you to use thesame code for both situations: any code to free resources allocated by your formlives in an overload of the Disposemethod, distinguished by its signature:void Dispose(bool)(in C#) orSub Dispose(Boolean)(in VB) This method will be called
in both scenarios—either when the user callsIDispose.Disposeor when the izer runs
final-It is important to distinguish between timely disposal and finalization whencleaning up resources In a finalizer, it is never possible to be sure whether anyreferences you hold to other objects are still valid: if the runtime has determinedthat your object is to be garbage collected, it is highly likely that it will also havedecided that the objects you are using must be collected too Because the CLRmakes no guarantees of the order in which finalizers are run, it is entirely possible
* A finally block is a block of code that the CLR guarantees to run, regardless of how the flow of execution leaves the preceding block It allows a single piece of cleanup code to be used in the face of normal exit, premature returns, and exceptions.
† Strictly speaking it inherits this model from its base class, the Component class in the System ComponentModel namespace.
Trang 16that any objects to which you hold references have already had their finalizers run.
In this case, callingDisposeon them could be dangerous—most objects will notexpect to have their methods called once they have been finalized So most of thetime, yourDispose method will only want to do anything when the object wasexplicitly disposed of by the user The only resources you would free during final-ization would be those external to the CLR, such as any temporary files created byyour object or any handles obtained through interop
TheDisposemethod that you are intended to override is protected, so it cannot becalled by external code It will be called by theControlclass if the user calls thepublicDisposemethod (IDispose.Dispose) In this case, the parameter passed to
the protectedDisposemethod will betrue It will also be called when the finalizerruns, in which case the parameter will befalse (Note that this method will only
be called once—if IDispose.Dispose is called, the Control class disables theobject’s finalizer.) So the parameter indicates whether resources are being freedpromptly or in a finalizer, allowing you to choose the appropriate behavior.Consider the code generated by the Designer, as shown in Examples 3-5 and 3-6
This checks to see if the public Dispose method was called, and if it was, itdisposes of thecomponents object, if present (Thecomponents object is a collec-tion of any non-Controlcomponents in use on the form, e.g., data sources.) But iffinalization is in progress (i.e., the disposing parameter is false), it doesn’tbother, for the reasons detailed above If you add any code to this Dispose
method, it too will normally live inside theif(disposing) { } block
Example 3-5 The default protected Dispose method in C#
protected override void Dispose( bool disposing )
Example 3-6 The default protected Dispose method in VB
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
Trang 17Forms, Apps,
Components added to a form using the Forms Designer in Visual
Studio NET will not necessarily be added to the form’scomponents
collection Only those components with a constructor that takes a
single parameter of typeIContainerwill be added (All the
compo-nents in the framework that require disposal have such a
construc-tor.) If you are writing your own component that has code in its
Disposemethod, you must supply an appropriate constructor This
constructor must callAddon the supplied container to add itself to
thecomponents collection
There are two very important rules you must stick to if you need to modify thisresource disposal code in your form First, you must always call the base class’s
Disposemethod in yourDisposemethod, because otherwise theControlclass willnot release its resources correctly Second, you should never define your ownfinalizer in a form—doing so could interact badly with theControl class’s ownfinalizer; the correct place to put code to release resources in a form (or any other
UI element) is in the overridden protectedDisposemethod This is precisely whatthe code generated by the forms designer does, as shown in Examples 3-5 and 3-6.You may be wondering what thecomponentsmember is for, and why it needs to bedisposed of It is a collection of components, and its jobis to dispose of thosecomponents—if you add a component such as a Timer to a form, the FormsDesigner will automatically generate code to add that component to the
componentscollection In fact, it does this by passingcomponentsas a constructionparameter to the component, e.g.:
this.timer1 = new System.Windows.Forms.Timer(this.components);
The component will then add itself to thecomponentscollection As you can seefrom Examples 3-5 and 3-6, the defaultDisposemethod supplied by the Designerwill callDisposeon thecomponentscollection This in turn will cause that collec-tion to callDisposeon each component it contains So if you are using a componentthat implementsIDispose, the easiest way to make sure it is freed correctly is simply
to add it to thecomponentscollection The Forms Designer does this automaticallyfor any components that require disposal (It determines which require disposal byexamining their constructors—if a component supplies a constructor that takes an
IContaineras a parameter, it will use that constructor, passing componentsas thecontainer.) You can also add any objects of your own to the collection:
components.Add(myDisposableObject);
or:
components.Add(myDisposableObject)
Showing Modal and Non-Modal Forms
All forms created by Visual Studio NET will conform to the structure justdescribed But as with dialogs in classic Windows applications, there are two ways
in which they can be shown: forms can exhibit either modal or non-modal behavior
Trang 18A modal form is one that demands the user’s immediate attention, and blocks
input to any other windows the application may have open (The application
enters a mode where it will only allow the user to access that form, hence the
name.) Forms should be displayed modally only if the application cannot proceeduntil the form is satisfied Typical examples would be error messages that mustnot go unnoticed or dialogs that collect data from the user that must be suppliedbefore an operation can be completed (e.g., the File Open dialog—an applicationneeds to know which file it is supposed to load before it can open it)
You select between modal and non-modal behavior when you display the form.The Form class provides two methods for displaying a form:ShowDialog, whichdisplays the form modally, andShow, which displays it non-modally
The Show method returns immediately, leaving the form on screen (The eventhandling mechanism discussed earlier can deliver events to any number ofwindows.) A non-modal form has a life of its own once it has been displayed; itmay even outlive the form that created it
By contrast, the ShowDialog method does not return until the dialog has beendismissed by the user Of course, this means that the thread will not return to the
Applicationclass’s main event-handling loop until the dialog goes away, but this
is not a problem because the framework will process events inside theShowDialog
method However, events are handled differently when a modal dialog is open—any attempts to click on a form other than the one being displayed modally arerejected Other forms will still be redrawn correctly, but will simply beep if theuser tries to provide them with any input This forces the user to deal with themodal dialog before progressing
There is a more minor (and somewhat curious) difference between modal andnon-modal use of forms: resizable forms have a subtly different appearance Whendisplayed modally, a form will always have a resize grip at the bottom righthandcorner Non-modal forms only have a resize grip if they have a status bar
Be careful with your use of modal dialogs, because they can prove somewhatannoying for the user: dialogs that render the rest of the application inaccessible for
no good reason are just frustrating For example, older versions of InternetExplorer would prevent you from scrolling the main window if you had a searchdialog open If you wanted to look at the text just below the match, you had tocancel the search to do so Fortunately this obstructive and needless use of a modaldialog has been fixed—Internet Explorer’s search dialog is now non-modal Toavoid making this kind of design error in your own applications, you should followthis guideline: do not make your dialogs modal unless they really have to be
Closing forms
Having displayed a form, either modally or non-modally, we will want to close it
at some point There are several ways in which a form can be closed From aprogrammer’s point of view, the most direct approach is to call itsClosemethod,
as follows:
this.Close( ); // C#
Me.Close( ) ' VB
Trang 19Forms, Apps,
A form may also be closed automatically by the Windows Forms framework inresponse to user input; for example, if the user clicks on a form’s close icon, thewindow will close However, if you want to prevent this (as you might if, forexample, the window represents an unsaved file), you can do so by handling the
Formclass’sClosingevent The framework raises this event just before closing thewindow, regardless of whether the window is being closed automatically or by anexplicit call to the Close method The event’s type is CancelEventHandler; itsBooleanCancelproperty enables us to prevent the window from closing if neces-sary Examples 3-7 and 3-8 illustrate the use of this property when handling the
Closing event
Example 3-7 Handling the Closing event in C#
private void MyForm_Closing(object sender,
Example 3-8 Handling the Closing event in VB
Private Sub MyForm_Closing(sender As Object, _
e As System.ComponentModel.CancelEventArgs)
If Not IsWorkSaved( ) Then
Dim rc As DialogResult = MessageBox.Show( _
"Save work before exiting?", _
Trang 20The form in Examples 3-7 and 3-8 checks to see if there is unsaved work.(IsWorkSavedis just a fictional method for illustrating this example—it is not part
of the framework.) If there is, it displays a message box giving the user a chance tosave this work, abandon it, or cancel, which keeps the window open In the lattercase, this code informs the framework that the window should not be closed afterall by setting theCancel property of theCancelEventArgs argument totrue
If you write an MDI application (i.e., an application that can display multipledocuments as children of a single main frame), the framework treats an attempt toclose the main window specially Not only does the main window get aClosing
andClosedevent, so does each child window The child windows are asked first,
so if each child represents a different document, each child can prompt the user ifthere is unsaved work But none of the children are closed until all of the windows(the children and the main window) have fired theClosingevent This means theclose can be vetoed by any of the windows The close will only happen if all thechild windows and the main window are happy
If nothing cancels the Closingevent, the window will be closed, and theClosed
event will be raised If the form is shown non-modally, the framework then callsthe form’sDispose method to make sure that all the form’s resources are freed.This means once a non-modal form has been closed, you cannot reuse the object
to display the form a second time If you callShowon a form that has already beenclosed, an exception will be thrown For modal dialogs, however, it is common towant to use the form object after the window has closed For example, if the dialogwas displayed to retrieve information from the user, you will want to get thatinformation out of the object once the window closes Modal dialogs are thereforenot disposed of when they are closed, and you must call Dispose yourself, asshown in Examples 3-9 and 3-10 You should make sure that you use any proper-ties or methods that you need before callingDispose (i.e., inside theusing block)
Example 3-9 Disposing of a modal dialog in C#
using (LoginForm lf = new LoginForm( ))
Trang 21Forms, Apps,
Although the framework will automatically try to close a window when its closeicon is pressed, it is common to want to close a form as the result of a buttonclick It turns out that if the button does nothing more than close the form, you
do not need to write a click handler to make this happen The Windows Formsframework will automatically close the form when any button with aDialogResult
is clicked So we will now look at dialog results
Automatic button click handling
A dialog might be closed for several different reasons Instead of clicking the OKbutton, the user might attempt to cancel the dialog by clicking on its close icon orCancel button, or by pressing the Escape key Most applications will distinguishbetween such cancellation and normal completion, and some may make a finerdistinction still, such as a message box with Yes, No, and Cancel buttons.Windows Forms provides support for automatically managing the various ways ofclosing a window without having to write click handlers It also makes it easy forusers of a form to find out which way a form was closed Both of these facilitiesrevolve around dialog results
TheFormclass’sShowDialogmethod returns a value indicating how the dialog wasdismissed The returned value corresponds to theDialogResult property of thebutton with which the user closed the window The following code shows anexcerpt from the initialization of a form containing two buttons, buttonOK and
buttonCancel(the Forms Designer will generate such code if you set a button’s
DialogResult property in the Properties window):
buttonOK.DialogResult = DialogResult.OK;
buttonCancel.DialogResult = DialogResult.Cancel;
Any code that shows this dialog will be able to determine which button wasclicked fromShowDialog’s return code The returned value can also be retrievedlater from theDialogResult property of theForm object
The type of theShowDialogmethod’s return value and of theDialogResulterty of both theFormobject and of individualButtoncontrols is alsoDialogResult,which is an enumeration type containing values for the most widely used dialogbuttons:OK,Cancel,Yes,No,Abort,Retry, andIgnore
prop-To handle button clicks without an event handler, you must set a button’s
DialogResult property to any value other than the default (DialogResult.None).Then clicking that button will cause the framework to close the form and returnthat value If you want, you can still supply aClickevent handler for the button,which will be run before the window is closed But the window will be closedwhether you supply one or not (unless there is aClosinghandler for the form thatcancels the close, as described earlier)
It is also possible to return a dialog result without using aButtoncontrol If youwish to close the form in response to some event that did not originate from abutton, you can also set theForm class’sDialogResult property before callingClose.But what about when the form is cancelled by pressing the Escape key? Wenormally want the form to behave in the same way regardless of how it isdismissed Specifically, we would like to run the same event handler and return