Applications


An application is anything with an EXE extension that can be started from the shell. However, applications are also provided for directly in WinForms by the Application class from the System. Windows .Forms namespace:

 
 NotInheritable Class Application   ' Properties   Shared Property AllowQuit() As Boolean   Shared Property CommonAppDataPath() As String   Shared Property CommonAppDataRegistry() As RegistryKey   Shared Property CompanyName() As String   Shared Property CurrentCulture() As CultureInfo   Shared Property CurrentInputLanguage() As InputLanguage   Shared Property ExecutablePath() As String   Shared Property LocalUserAppDataPath() As String   Shared Property MessageLoop() As Boolean   Shared Property ProductName() As String   Shared Property ProductVersion() As String   Shared Property SafeTopLevelCaptionFormat() As String   Shared Property StartupPath() As String   Shared Property UserAppDataPath() As String   Shared Property UserAppDataRegistry() As RegistryKey   ' Events   Shared Event ApplicationExit As EventHandler   Shared Event Idle As EventHandler   Shared Event ThreadExcpetion As ThreadExceptionEventHandler   Shared Event ThreadExit As EventHandler   ' Methods   Shared Sub AddMessageFilter(value As IMessageFilter)   Shared Sub DoEvents()   Shared Sub Exit()   Shared Sub ExitThread()   Shared Function OleRequired() As ApartmentState   Shared Sub OnThreadException(t As Exception)   Shared Sub RemoveMessageFilter(value As IMessageFilter)   Shared Overloads Sub Run()   Shared Overloads Sub Run(context As ApplicationContext)   Shared Overloads Sub Run(mainForm As Form) End Class 

Notice that all of the members of the Application class are Shared. Although there is per-application state in WinForms, there is no instance of an Application class. Instead, the Application class is a scoping mechanism for the various services that the class provides, including lifetime control, message handling, and settings.

Application Lifetime

A WinForms application starts when the Main method is called. However, to initialize a WinForms application fully and start it routing WinForms events, you need a call to Application.Run.

There are three ways to call the Application class's Run method. The first is to simply call Run with no arguments at all. This is useful only if other means have already been used to show an initial UI:

 
 <STAThread()> _ Shared Sub Main()   ' Create and show the main form modelessly   Dim myform As Form = New MainForm()   Myform.Show()   ' Run the application   Application.Run() End Sub 

When you call Run with no arguments, the application runs until explicitly told to stop, even when all its forms are closed. This puts the burden on some part of the application to call the Application class's Exit method:

 
 Sub MainForm_Closed(sender As Object, e As EventArgs)   ' Close the application when the main form goes away   ' Only for use when Application.Run is called without   ' any arguments   Application.Exit() End Sub 

Typically, you call Application.Run without any arguments only when the application needs a secondary UI thread. A UI thread is one that calls Application.Run and can process the events that drive a Windows application. Because the vast majority of applications contain a single UI thread and because most of those have a main form that, when closed, causes the application to exit, another overload of the Run method is used far more often. This overload of Run takes as an argument a reference to the form designated as the main form. When Run is called this way, it shows the main form and doesn't return until the main form closes :

 
 <STAThread()> _ Shared Sub Main()   ' Create the main form   Dim myform As Form = New MainForm()   ' Run the application until the main form is closed   Application.Run(myform) End Sub 

In this case, there is no need for explicit code to exit the application. Instead, the Application watches for the main form to close and then exits itself.

Application Context

Internally, the Run method creates an instance of the ApplicationContext class from the System.Windows.Forms namespace. It's this class that subscribes to the main form's Closed event and exits the application as appropriate:

 
 Class ApplicationContext   ' Constructors   Public Overloads Sub New()   Public Overloads Sub New(mainForm As Form)   ' Properties   Property MainForm() As Form   ' Events   Event ThreadExit As EventHandler   ' Methods   Sub ExitThread()   Protected MustOverride Sub OnMainFormCloased( _       sender As Object, e As EventArgs) End Class 

In fact, the Run method allows you to pass an ApplicationContext yourself:

 
 <STAThread()> _ Shared Sub Main()   ' Run the application with a context   Dim ctx As ApplicationContext = _     New ApplicationContext(New MainForm())   Application.Run(ctx) End Sub 

This is useful if you'd like to derive from the ApplicationContext class and provide your own custom context:

 
 Class MyTimedContext   Inherits ApplicationContext   Dim mytimer As Timer = New Timer()   Public Sub New(myform As Form)       MyBase.New(myform)       AddHandler mytimer.Tick, New EventHandler(AddressOf TimesUp)       mytimer.Interval = 300000 ' 5 minutes = 300,000 milliseconds       mytimer.Enabled = True   End Sub   Sub TimesUp(sender As Object, e As EventArgs)       mytimer.Enabled = False       mytimer.Dispose()       Dim res As DialogResult = _           MessageBox.Show("OK to charge your credit card?", _           "Time's Up!", _           MessageBoxButtons.YesNo)       If res = DialogResult.No Then           ' See ya...           MyBase.MainForm.Close()       End If       ...   End Sub End Class <STAThread()> _ Shared Sub Main()   ' Run the application with a custom context   Dim ctx As ApplicationContext = New MyTimedContext(New MainForm())   Application.Run(ctx) End Sub 

This custom context class waits for five minutes after an application has started and then asks to charge the user 's credit card. If the answer is no, the main form of the application will be closed (available from the MainForm property of the base ApplicationContext class), causing the application to exit.

Conversely, if you'd like to stop the application from exiting when the main form goes away, you can override the OnMainFormClosed method from the ApplicationContext base class:

 
 Class RemotingServerContext   Inherits ApplicationContext   Public Sub New(myform As Form)       MyBase.New(myform)   End Sub   Protected Overrides Sub OnMainFormClosed(sender As Object, _ e As EventArgs) ' Don't let base class exit application       ' NOTE: Remember to call Application.Exit       '       later when the remoting service       '       is finished servicing its clients       If ServicingRemotingClient() Then Exit Sub       ' Let base class exit application       MyBase.OnMainFormClosed(sender, e)   End Sub   Protected Function ServicingRemotingClient() As Boolean       ...   End Function End Class 

This example assumes an application that is serving .NET Remoting [1] clients and so needs to stick around even if the user has closed the main form.

[1] NET Remoting is a technology that allows objects to talk to each other across application and machine boundaries. Remoting is beyond the scope of this book but is covered very nicely in Ingo Rammer's book Advanced .NET Remoting (APress 2002).

Application Events

During the lifetime of an application, several application events will be fired : idle, thread exit, application exit, and sometimes a thread exception. You can subscribe to application events at any time, but it's most common to do it in the Main function:

 
 Shared Sub App_Exit(sender As Object, e As EventArgs) Shared Sub App_Idle(sender As Object, e As EventArgs) Shared Sub App_ThreadExit(sender As Object, e As EventArgs) <STAThread()> _ Shared Sub Main()   AddHandler Application.Idle, New EventHandler(AddressOf App_Idle)   AddHandler Application.ThreadExit, _     New EventHandler(AddressOf App_ThreadExit)   AddHandler Application.ApplicationExit, New EventHandler(App_Exit) End Sub 

The idle event happens when all events in a series of events have been dispatched to event handlers and no more events are waiting to be processed . The idle event can sometimes be used to perform concurrent processing in tiny chunks , but it's much more convenient and robust to use worker threads for those kinds of activities. This technique is covered in Chapter 14: Multithreaded User Interfaces.

When a UI thread is about to exit, it receives a notification via the thread exit event. When the last UI thread goes away, the application's exit event is fired.

UI Thread Exceptions

One other application-level event that can be handled is a thread exception event. This event is fired when a UI thread causes an exception to be thrown. This one is so important that WinForms provides a default handler if you don't.

The typical .NET unhandled exception on a user's machine behavior yields a dialog box as shown in Figure 11.1.

Figure 11.1. Default .NET Unhandled-Exception Dialog Box

This kind of exception handling tends to make the user unhappy . This dialog is confusing, and worse , there is no way to continue the application to attempt to save the data being worked on at the moment. On the other hand, by default, a WinForms application that experiences an exception during the processing of an event shows a dialog like that in Figure 11.2.

Figure 11.2. Default WinForms Unhandled-Exception Dialog Box

Although this dialog may look functionally the same as the one in Figure 11.1, there is one major difference: The WinForms version has a Continue button. What's happening is that WinForms itself catches exceptions thrown by event handlers; in this way, even if that event handler caused an exception ”for example, if a file couldn't be opened or there was a security violation ”the user is allowed to continue running the application with the hope that saving will work, even if nothing else does. This is a safety net that makes WinForms applications more robust in the face of even unhandled exceptions than Windows applications of old. However, if a user has triggered an exception and it's caught, the application could be in an inconsistent state, so it's best to encourage your users to save their files and restart the application.

If you'd like to replace the WinForms unhandled-exception dialog with something application-specific, you can do so by handling the application's thread exception event:

 
 Imports System.Threading Shared Sub App_ThreadException(sender As Object,e as _     ThreadExceptionEventArgs)   Dim msg As String = _       "A problem has occurred in this application:" & vbCrLf & _       vbTab & e.Exception.Message & vbCrLf & _       "Would you like to continue the application so that" & _         vbCrLf & "you can save your work?" Dim res As DialogResult = _       MessageBox.Show(msg, "Unexpected Error", _         MessageBoxButtons.YesNo)   ' Returning continues the application   If res = DialogResult.Yes Then Exit Sub   ' Must exit the application manually if handling   ' the thread exception event   Application.Exit() End Sub <STAThread()> _ Shared Sub Main   ' Handle unhandled thread exceptions   AddHandler Application.ThreadException, _       New EventHandler(AddressOf App_ThreadException)   ' Run the application   Application.Run(New MainForm()) End Sub 

Notice that the thread exception handler takes a ThreadExceptionEvent object, which includes the exception that was thrown. This is handy if you want to tell the user what happened , as shown in Figure 11.3.

Figure 11.3. Custom Unhandled-Exception Dialog

If you provide a thread exception handler, the default exception handler will not be used, so it's up to you to let the user know that something bad has happened. If the user decides not to continue with the application, calling Application.Exit will shut down the application:

 
 Shared Sub App_ThreadException(sender As Object, _       E As ThreadExceptionEventArgs)   System.Diagnostics.Debug.WriteLine("App_ThreadException")   Dim msg As String = _       "A problem has occurred in this application:" & vbCrLf & _       vbTab & e.Exception.Message & vbCrLf & _       "Would you like to continue the application so that" & _         vbCrLf & "you can save your work?"   Dim res As DialogResult = _       MessageBox.Show(msg, "Unexpected Error", _         MessageBoxButtons.YesNo) If res = DialogResult.Yes Then Exit Sub   ' Shut 'er down, Clancy, she's a'pumpin' mud!   Application.Exit() End Sub 

Single-Instance Applications

By default, each EXE is an application and has an independent lifetime, even if multiple instances of the same application are running at the same time. However, it's common to want to limit an EXE to a single instance, whether it's a Single Document Interface (SDI) application with a single top-level window, a Multiple Document Interface (MDI) application, or an SDI application with multiple top-level windows. All these kinds of applications require that another instance detect the initial instance and then cut its own lifetime short. You can do this using an instance of the Mutex class from the System.Threading namespace: [2]

[2] This method of detecting single instances will stop multiple instances across desktops and sessions. For a detailed explanation of other definitions of "single instance," read Joseph Newcomer's treatment of this topic at http://www.pgh.net/~newcomer/nomultiples.htm.

 
 Imports System.Threading ... Shared Sub Main()   ' Check for existing instance   Dim firstInstance As Boolean = False   Dim safeName As String = _     Application.UserAppDataPath.Replace("\", "_")   Dim mymutex As Mutex = New Mutex(True, safeName, _     ByRef firstInstance)     If Not(firstInstance) Then Exit Sub   Application.Run(New MainForm()) End Sub 

This code relies on a named kernel object , that is, an object that is managed by the Windows kernel. The fact that it's a mutex doesn't really matter. What matters is that we create a kernel object with a systemwide unique name and that we can tell whether an object with that same name already exists. When the first instance is executed, that kernel object won't exist, and we won't return from Main until Application.Run returns. When another instance is executed, the kernel object will already exist, so Main will exit before the application is run.

One interesting note on the name of the mutex is worth mentioning. To make sure we have a unique name for the mutex, we need something specific to the version of the application but also specific to the user. It's important to pick a string that's unique per application so that multiple applications don't prevent each other from starting. If there are multiple users, it's equally important that each user get his or her own instance, especially in the face of Windows XP, fast user switching, and terminal services. [3] Toward that end, we use the UserAppDataPath property of the Application object. It's a path in the file system where per-user settings for an application are meant to be stored, and it takes the following form:

[3] However, this code doesn't take into account the possibility that the same user may be logged in to the same machine in two different sessions.

 
 C:\Documents and Settings\  csells  \Application Data\  SingleInstance\SingleInstance.0.1121.38811  

What makes this string useful is that it contains the application name, the version number, and the user name ”all the things we need to make the mutex unique per version of an application and per user running the application. Also, because the back slashes are illegal in mutex names , those must be replaced with something else (such as underscores).

Passing Command Line Arguments

This single-instance scheme works fine until the first instance of the application needs to get the command line arguments from any subsequent instance. For example, if the first instance of an MDI application needs to open the file passed to the other instance of the MDI application, the other instance needs to be able to communicate with the initial instance. The easiest solution to this problem is to use .NET Remoting and threading:

 
 Imports System.Threading Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp ... ' Make main form accessible from other instance event handler Shared mymainForm As MainForm = New MainForm() ' Signature of method to call when other instance is detected Delegate Sub OtherInstanceCallback(args() As String) <STAThread()> _ Shared Sub Main(args() As String)   ' Check for existing instance   Dim firstInstance As Boolean = False   Dim safeName As String = _     Application.UserAppDataPath.Replace("\", "_")   Dim mymutex As Mutex = New Mutex(True, safeName, _     ByRef firstInstance)   If Not(firstInstance) Then       ' Open remoting channel exposed from initial instance       ' NOTE:port (1313) and channel (mainForm) must match below       Dim formUrl As String = "tcp://localhost:1313/mainForm"       Dim otherMainForm As MainForm = _           CType(RemotingServices.Connect(GetType(MainForm), _             formUrl), MainForm)       ' Send arguments to initial instance and exit this one       otherMainForm.OnOtherInstance(args)       Exit Sub   End If   ' Expose remoting channel to accept arguments from other instances   ' NOTE: port (1313) and channel name (mainForm) must match above   ChannelServices.RegisterChannel(New TcpChannel(1313))   RemotingServices.Marshal(mainForm, "mainForm")   ' Open file from command line   If args.Length = 1 Then mainForm.OpenFile(args(0))   ' Show main form   Application.Run(mainForm) End Sub Public Sub OnOtherInstance(args() As String)   ... End Sub 

The details of .NET Remoting are beyond the scope of this book, and threading isn't covered until Chapter 14: Multithreaded User Interfaces, but the details are less important than the concepts. Basically what's happening is that the first instance of the application opens a named communication channel (mainForm) on a well-known, unique port (1313) in case another instance of the application ever comes along. If one does, the other instance opens the same channel and retrieves the MainForm type from it so that it can call the OnOtherInstance method. For this to work, the .NET Remoting assembly, System.Runtime.Remoting, must be referenced in the project. The Remoting infrastructure then requires that the type being retrieved by the other instance from the first instance be marshal-by-reference . This means that it can be called from another application domain , which is basically the .NET equivalent of a process. Because the MainForm class, along with all UI classes in .NET, derives from the MarshalByRef base class, this condition is met.

Another .NET Remoting requirement is that the methods called on the marshal-by-ref type called from another app domain be instance and public, and that's why the OnOtherInstance method is defined as it is:

 
 ' Called via remoting channel from other instances ' NOTE: This is a member of the MainForm class Sub OnOtherInstance(args() As String)   ' Transition to the UI thread   If mainForm.InvokeRequired Then       Dim callback As OtherInstanceCallback = _           New OtherInstanceCallback(AddressOf OnOtherInstance)       mainForm.Invoke(callback, New Object() { args })       Exit Sub   End If   ' Open file from command line   If args.Length = 1 Then Me.OpenFile(args(0))   ' Bring window to the front   mainForm.Activate() End Sub 

When OnOtherInstance is called, it will be called on a non-UI thread. This requires a transition to the UI thread before any methods on the MainForm are called that access the underlying window (perhaps to create an MDI child to show the file being opened or to activate the window). That's why we include the check on the InvokeRequired required property and the call to BeginInvoke. [4]

[4] All these threading details are discussed in Chapter 14: Multithreaded User Interfaces.

Finally, OnOtherInstance does what it likes with the command line arguments and then activates the main form to bring it to the foreground. The details of this are somewhat complicated for the service that they provide, and this makes it a good candidate for encapsulation. The only real variables that can't be handled automatically are which method to call and what the main form is. This means that we can boil down the code to use the InitialInstanceActivator class provided in the sample code included with this book:

 
 ' Make main form accessible from other instance event handler Shared mymainForm As MainForm = New MainForm() <STAThread()> _ Shared Sub Main(args() As String)   ' Check for initial instance   Dim callback As OtherInstanceCallback = _       New OtherInstanceCallback(AddressOf OnOtherInstance)   If InitialInstanceActivator.Activate(mymainForm, callback, args) Then       Exit Sub   End If   ' Open file from command line   If args.Length = 1 Then mymainForm.OpenFile(args(0))   ' Show main form   Application.Run(mymainForm) End Sub ' Called from other instances Share Sub OnOtherInstance(args() As String)   ' Open file from command line   If args.Length = 1 Then mymainForm.OpenFile(args(0))   ' Activate the main window   mymainForm.Activate() End Sub 

InitialInstanceActivator takes three things: a reference to the main form, a delegate indicating which method to call when another instance is detected, and the arguments from Main in case this is another instance. If the Activate method returns true, it means that this is another instance and the arguments have been passed to the initial instance. The application bails, safe in the knowledge that the initial instance has things well in hand.

When another instance activates the initial instance, the delegate is invoked, letting the initial instance handle the command line arguments and activate itself as appropriate.

The underlying communication and threading requirements are handled by InitialInstanceActivator using other parts of .NET that have nothing whatever to do with WinForms. This is one of the strengths of WinForms. Unlike forms packages of old, WinForms is only one part of a much larger, integrated whole. When its windowing classes don't meet your needs, you've still got all the rest of the .NET Framework Class Library to fall back on.

Multi-SDI Applications

A multi- SDI application is like an MDI application in that it has multiple windows for content, but, unlike an MDI application, each window in a multi-SDI app is a top-level window. The Internet Explorer and Office XP applications are popular examples of multi-SDI applications. [5] Figure 11.4 shows a multi-SDI sample.

[5] Internet Explorer can be configured to show each top-level window in its own process, making it an SDI application, or to share all windows in a single process, making it a multi-SDI application.

Figure 11.4. A Sample Multi-SDI Application

A multi-SDI application typically has the following features:

  • A single instance of the application is running.

  • Multiple top-level windows are running independently of each other.

  • When the last window goes away, so does the application.

  • A Window menu allows a user to see and select from the currently available windows.

The single-instance stuff we've already got licked with the InitialInstanceActivator class. Having multiple top-level windows running independently of each other is a matter of using the modeless Form.Show method:

 
 Class TopLevelForm   Inherits Form   ...   Sub fileNewWindowMenuItem_Click(sender As Object, e As EventArgs)       NewWindow(Nothing)   End Sub   Shared Sub NewWindow(fileName As String)       ' Create another top-level form       Dim myform As TopLevelForm = New TopLevelForm()       If Not (fileName Is Nothing) AndAlso (fileName.Length > 0) Then           myform.OpenFile(fileName)       End If       myform.Show()   End Sub End Class 

Because the default application context depends on a there being only one main window, managing the lifetime of a multi-SDI application requires a custom application context. One simple way to leverage the ApplicationContext base class is to derive from it, swapping the "main" form in our multi-SDI application until there are no more top-level windows left in the application, thereby causing the application to shut down:

 
 Class MultiSdiApplicationContext   Inherits ApplicationContext   Sub AddTopLevelForm(myform As Form)       ' Initial main form may add itself twice, but that's OK       If topLevelForms.Contains(myform) Then Exit Sub       ' Add form to collection of forms and       ' watch for it to activate and close       topLevelForms.Add(myform)       AddHandler myform.Activated, _         New EventHandler(AddressOf Form_Activated)       AddHandler myform.Closed, _ New EventHandler(AddressOf Form_Closed)       ' Set initial main form to activate       If topLevelForms.Count = 1 Then MyBase.MainForm = myform   End Sub   Sub Form_Activated(sender As Object, e As EventArgs)       ' Whichever form activated last is the "main" form       MyBase.MainForm = CType(sender, Form)   End Sub   Sub Form_Closed(sender As Object, e As EventArgs)       ' Remove form from the list       topLevelForms.Remove(sender)       ' Set a new "main" if necessary       If (CType(sender, Form) = MyBase.MainForm) And _           Me.topLevelForms.Count > 0) Then           Me.MainForm = CType(topLevelForms(0), Form)       End If   End Sub   ...   Public Property TopLevelForms() As Form()       ' Expose list of top-level forms for building Window menu       Get           Return CType(topLevelForms.ToArray(GetType(Form)), _             Form())       End Get   End Property   Dim topLevelForms As ArrayList = New ArrayList() End Class 

The MultiSdiApplicationContext class uses the AddTopLevelForm method to keep track of a list of top-level forms as they are added. Each new form is kept in a collection and is watched for Activated and Closed events. When a top-level form is activated, it becomes the new "main" form, which is the one that the base ApplicationContext class will watch for the Closed event. When a top-level form closes, it's removed from the list. If the closed form was the main form, another form is promoted to main. When the last form goes away, the base ApplicationContext class notices and exits the application.

With this basic functionality in place, we can use the application context in Main as the argument to Application.Run:

 
 ' Need application context to manage top-level forms Shared context As MultiSdiApplicationContext = _   New MultiSdiApplicationContext() <STAThread()> _ Share Sub Main(args() As String)   ' Add initial form   Dim initialForm As TopLevelForm = New TopLevelForm()   context.AddTopLevelForm(initialForm)   ' Let initial instance show another top-level form (if necessary)   Dim callback As OtherInstanceCallback = _       New OtherInstanceCallback(AddressOf OnOtherInstance)   If InitialInstanceActivator.Activate(context, callback, args) Then       Exit Sub   End If   ' Open file from command line   If args.Length = 1 Then initialForm.OpenFile(args(0))   ' Run application   Application.Run(context) End Sub 

Because we're using the application context instead of the initial form as the argument to Application.Run, it will be used to control the lifetime of the application, even as the "main" form cycles. Similarly, we're using a context to the Activate method of the InitialInstanceActivator helper class, and this means that if another instance of the application starts, the activator can ask the context for the "current" main form to use in transitioning to the UI thread, even if the initial form has been closed.

To keep the context up-to-date with the current list of top-level forms, the custom context watches for the Closed event on all forms. In addition, the custom context needs to be notified when a new top-level form has come into existence, a task that is best handled by the new form itself:

 
 Public Sub New()   ' Required for Windows Form Designer support   InitializeComponent()   ' Add new top-level form to the application context   context.AddTopLevelForm(Me) End Sub 

The only thing left to do is to designate and populate the Window menu with the current list of top-level forms. The forms themselves can do this by handling the pop-up event on the Window MenuItem object, using that opportunity to build the list of submenu items based on the names of all the forms (as exposed via the TopLevelForms property of the MultiSdiApplicationContext helper object). However, this code is pretty boilerplate , so it's a good candidate to be handled by the custom application context in the AddWindowMenu method:

 
 Class MultiMdiApplicationContext   Inherits ApplicationContext   Sub AddWindowMenu(menu As MenuItem)       ' Add at least one dummy menu item to get pop-up event       If menu.MenuItems.Count = 0 Then menu.MenuItems.Add("dummy")       ' Subscribe to pop-up event       AddHandler menu.Popup, _         New EventHandler(AddressOf WindowMenu_Popup)   End Sub   ... End Class 

Each top-level form with a Window menu can add it to the context, along with itself, when it's created:

 
 Public Sub New()   ' Required for Windows Form Designer support   InitializeComponent()   ' Add new top-level form to the application context   context.AddTopLevelForm(Me)   ' Add Window MenuItem to the application context   context.AddWindowMenu(Me.windowMenu) End Sub 

Now, when the Window menu is shown on any top-level window, the pop-up event fires and a new menu is built on-the-fly to show the current list of top-level menus :

 
 ' Current Window menu items and the map to the appropriate form Dim windowMenuMap As Hashtable = New Hashtable() Sub WindowMenu_Popup(sender As Object, e As EventArgs)       ' Build menu from list of top-level windows       Dim menu As MenuItem = CType(sender, MenuItem)       menu.MenuItems.Clear()       windowMenuMap.Clear()       Dim theform As Form       For Each theform In Me.topLevelForms           Dim item As MenuItem = _             menu.MenuItems.Add(theform.Text)           AddHandler item.Click, _               New EventHandler(AddressOf WindowMenuItem_Click)           ' Check currently active window           If theform = Form.ActiveForm Then item.Checked = True           ' Associate each menu item back to the form           windowMenuMap.Add(item, theform)       Next End Sub 

As each menu item is added to the Window menu, a handler is added to the Click event so that the appropriate form can be activated when it's selected. Because a MenuItem doesn't have a Tag property, we're using a Hashtable collection to map menu items to each form. The hash table is used in the Click handler to find the form that corresponds to the selected menu item:

 
 Sub WindowMenuItem_Click(sender As Object, e As EventArgs)   ' Activate top-level form based on selection   CType(windowMenuMap(sender), Form).Activate() End Sub 

That's it. The extensible lifetime management of WinForms applications via a custom application context, along with a helper to find and activate application instances already running, provides all the help we need to build a multi-SDI application in only a few lines of code.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

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