Search:
Locator+ Code:
FTP Home   VSM Home   Product Catalog   Archives   Customer Service   Site Map
 
    Q & A


How's Your Form?

Use Windows.Forms the VB way; implement a custom-build step in C#.

by Bill McCarthy, Don Preuninger, and Joe Dour

 

QReference Forms by Name
How do you reference a control on another form within your application? Every time I attempt to do this, I receive the message, "This reference to a non-shared member requires an object reference, and none is supplied." The help page for this message is blank.

Technology Toolbox

VB.NET
C#
VB6
VB5
Note: Requires VB.NET beta 2

A. VB6 lets you reference a default instance of a form by referring to the form's name. For example, if you create a form and name it Form1, you can refer to its caption as Form1.Caption. When you refer to the form by its name, you refer to a single instance of that form, not all instances. Assume you write this code:

Dim myForm1 As Form1
Set myForm1 = New Form1
myForm1.Caption = "hello world"
Form1.Caption = "goodbye world"
Debug.Print myForm1.Caption

The output is "hello world," not "goodbye world." The reason: myForm1 is a different instance of Form1.

VB.NET doesn't automatically create a default instance of a form you can reference by name; attempting to do so triggers the error message you mentioned. Instead, you must create an instance of the form and refer to that instance. Fortunately, you can simulate what VB does by creating a public property in a module to refer to. For example, this code gives you a Form1 property you can access throughout your project, although you must refer to the property by its full name, globals.Form1:

Module globals
   Private m_Form1 As Form1

   Public Property Form1() As Form1
      Get
         If m_Form1 Is Nothing Then
            m_Form1 = New Form1()
         End If
         Return m_Form1
      End Get
      Set(ByVal Value As Form1)
         m_Form1 = Value
      End Set
   End Property
End Module

Note that a new instance is created if the private variable is "Nothing" in the property get; this works in VB.NET just as it does in VB6. Even if you Set Form1 = Nothing, it always returns False when you test to see whether Form1 Is Nothing.

Every time you reference the property, the code tests whether the object is Nothing. If so, it creates a new instance. This undesirable behavior is often referred to as "evil auto-generation." The simplest solution: Avoid using a form's name to refer to the default instance. Instead, use a Sub Main() as your project's startup point and declare the form variables yourself:

Module StartHere
   Public defaultForm1 As Form1

   Public Sub Main()
      defaultForm1 = New Form1()
      Application.Run(deafultForm1)
   End Sub
End Module

One more caveat: When you use the VB6-to-VB.NET upgrade wizard, it creates a property in your form called DefInstance, which is the default instance of that form. Code that referred to the default instance in VB6, such as Form1.Label1, is changed to Form1.DefInstance.Label1. The upgrade wizard also adds code in the form's constructor that uses reflection to see whether the form was set as the project startup point. If so, it sets the DefInstance property to that instance of the form. Download the online source for an example of the upgrade wizard's output. The online code also includes an example that shows you how to create your own Form1 property. —Bill McCarthy

QDetermine a Form's UnloadMode
Does VB.NET include a functional equivalent to the UnloadMode parameter of VB6's Form_QueryUnload event? Form_Closing fires in VB.NET and I can cancel it using e.Cancel=True, but I don't see any way to distinguish between an explicit Close call and the user clicking on the "X" or shutting down windows.

A. System.Windows.Forms.Form, the default base for a Windows Form in .NET, doesn't include a QueryUnload event. However, you can create your own QueryUnload event in a form, then use that class as the base class for all your other forms.

In VB6, the QueryUnload event includes the UnloadMode parameter as an integer. In .NET, you can make that parameter a bit more user-friendly and declare it an enum:

Public Enum UnloadMode
   ControlMenu = 0
   Code = 1
   SessionClosing = 2
   TaskManager = 3
   MDIParentClosing = 4
   OwnerClosing = 5
End Enum

You can use the form's base WndProc to watch for messages that attempt to close the form. This is similar to subclassing in VB6, but much easier. The messages you need to look for in the WndProc are WM_QUERYENDSESSION, WM_SYSCOMMAND, and WM_CLOSE.

The system sends the WM_QUERYENDSESSION message when the Windows session is closing down. This message is sent to all top-level forms. When your form receives this message, you need only to propagate it to each MDIChild form's QueryUnload event.

 
Figure 1 | Close the Window, Please. Click here.

If your form receives a WM_SYSCOMMAND message, you must check the wParam of the message structure to see whether it's equal to SC_CLOSE. This combination, WM_SYSCOMMAND and wParam = SC_CLOSE, signifies that a user clicked on the "X" in the form's top corner or control menu to close the form.

In all other cases, you can assume code triggered the WM_CLOSE message. For MDIChild and Owned forms, you propagate the QueryUnload event to each of them, specifying the unload mode as MDIParentClosing and OwnerClosing, respectively. Do this using recursion and iterating a form's OwnedForms and MDIChildren properties.

The hard part is determining whether Task Manager closed an application. Task Manager tells a window to close by posting a WM_CLOSE message. This is no different from the message your WndProc receives when it's about to be closed programmatically. VB6 determines whether it's being closed programmatically or by Task Manager depending on which window receives the message. To do this, it creates a hidden window as your root application's root window. When the hidden window receives a WM_CLOSE message, VB6 assumes it must be from Task Manager.

All your VB6 application's forms are owned by this hidden window or by forms that are also owned by this hidden window. If you open Task Manager, a VB6 application shows one instance of the application's icon, regardless of how many top-level windows it has open or what their icons are. A VB.NET or C# application shows each unowned top-level window's icon.

In VB.NET and C#, the form you show using Application.Run(form) is set as the application context. When that form is closed, the message loop is exited, and all other forms on that thread, owned or not, are closed without any closing event firing. You can emulate VB6's approach in .NET by creating your own hidden window. This enables you to keep the message loop open as long as the hidden window has any owned forms (see Figure 1). This technique allows you to swap top-level windows easily.

You can create a hidden window in .NET by overriding the CreateParams Property Get and specifying the window style, exstyle, width, and height:

Dim cp As System.Windows.Forms.CreateParams
cp = MyBase.CreateParams
With cp
   .ExStyle = WS_EX_TOOLWINDOW
   .Style = WS_POPUP Or WS_VISIBLE _
      Or WS_SYSMENU Or WS_MAXIMIZEBOX
   .Height = 0
   .Width = 0
End With
Return cp

Bill McCarthy

QImplement Custom-Build Steps
Here's an issue I've struggled with under .NET, without finding anything in the documentation. How do I implement a custom-build step in C#?

A. The beta 2 version of C# in Visual Studio .NET includes no facility to specify a custom-build step. This seems like an oversight, and we would expect Microsoft to address this shortcoming in a future version of C#. In the meantime, if you need to add a custom-build step, here is a workaround. Include a dummy C++ project in your solution; you can take advantage of the fact that C++ projects provide this feature.

 
Figure 2 | Create a Post-Build Step. Click here.

Adding this project requires several steps. Begin by opening your existing solution, right-clicking on it, and choosing Add and New Project. This brings up the Add New Project dialog. Select the Visual C++ Projects project type and the Win32 Project template, specify a name and location, and click on OK. This brings up the Win32 Application Wizard. Click on Application Settings and choose an application type. (This is a dummy project, so it doesn't really matter, but we always use Console Application because it generates a lightweight project.) Next, click on Finish to generate the project and add it to the solution.

Only a couple steps remain. Choose Project Dependencies… from the Project menu. In the next dialog, select the dummy C++ project from the Project dropdown and check the appropriate C# project(s) in the "Depends on" list. Next, click on OK to apply the change. This ensures the C++ project will be built last. Finally, right-click on the dummy C++ project in the Solution Explorer and choose Properties. Select Build Events, then Post-Build Event when the dialog appears and add whatever buildset commands you need (see Figure 2). —Don Preuninger and Joe Dour




Bill McCarthy is an Australian. He reckons that says it all. He is also a Microsoft MVP and runs a small consulting firm, Total Environment. Contact Bill at or visit his VB.NET Web site at .

Don Preuninger is vice president and senior architect at Infragistics, and he was a cofounder of ProtoView Development Corp. He created and developed many of ProtoView's early products for Windows 2.0 using C and C++, and he's been instrumental in the design and implementation of the forthcoming Infragistics line of .NET components. Reach Don at .

Joe Dour, also a vice president and senior architect at Infragistics, was a cofounder of Sheridan Software Systems and has spent more than 25 years designing and developing software. He too has helped design and implement the forthcoming line of Infragistics' .NET components. E-mail Joe at .

 
  Get the code for this issue .
  Download the code from each article individually. Get the code for this article here.
 



• "Subclass Forms to Create New Events" by Francesco Balena [Visual Basic Programmer's Journal January 1998]
• Black Belt Programming, "Extend Objects With Subclassing," by Matt Hart [VBPJ December 1998]
• Black Belt Programming, "Create Your Own Control Windows," by Matthew Curland and William Storage [VBPJ June 1997]
• 


 






<