Add Images to MDIForm Backgrounds
by Karl E. Peterson
Technology Toolbox: VB6
Q: Paint MDIForm
Over the years, I've seen various solutions to provide an image or custom paint routines for MDIForm backgrounds. WinForms (.NET) promises this ability, but I'd like to implement it in classic VB. The methods I'm aware of generally involve either a hackneyed child form that monitors its z-order constantly, or some messy subclassing and time-consuming responses to WM_ERASEBKGND and WM_PAINT. Aren't there any easy answers?
A. My (current) favorite answer to this age-old problem, often overlooked due to perceived shortcomings, is to make good use of the MDIForm's Picture property. Simply assign graphics created at run time to this property. Your needs are few for this solution to be fully effective. The first is notification of changes in the form's size, so you can re-create your background graphic to fit the client space. Second, you need a canvas on which to paint your desired background. The canvas must be assignable to the form's Picture property.
That leaves the question of how best to implement these requirements. Whenever practical, I like to wrap up these sorts of functionalities into drop-in-ready classes—set 'em and forget 'em. VB5 provided one key element of the solution with the addition of the WithEvents object declaration. Declaring a class-level variable as type MDIForm and using WithEvents in the declaration notifies your class whenever the form's Resize event is fired. VB6 provides dynamic control addition, which is the final piece of what turns out to be an elegant solution.
Normally, VB forces controls placed on an MDIForm to align to one of the edges of the client space. Although these controls don't interfere with child forms as long as they're invisible, you have no control over their size. However, the old alignment rules no longer apply if you add a PictureBox dynamically at run time. This feature (bug?) provides a means to write a fully encapsulated class that uses "pure VB" almost exclusively, rather than a boatload of API tricks.
I've created a class, CMdiBackground, that's drop-in-ready and provides a good number of standard painting effects, such as displaying a logo graphic, stretching or tiling a bitmap, or painting a gradient (see Figure 1). To use it within an MDIForm, you simply set a few properties in the form's Load event, then forget about it:
Private m_BGnd As CMdiBackground Private Sub MDIForm_Load() ' Set up background painter Set m_BGnd = New CMdiBackground With m_BGnd Set .Client = Me .Color = vbBlue .Color(mdiColorBottom) = vbBlack .BackStyle = mdiGradient .AutoRefresh = True End With End Sub
CMdiBackground does nothing other than set some default values when instantiated. The action begins on assignment of a client form. When you pass an MDIForm reference to the Client property, the code in the Client property calls code to tear down the old client (if one exists) and to initialize the new client (see Listing 1). Client Set calls ClientSetup, which adds a new PictureBox (invisible by default) to the client's Controls collection and sets its properties as needed.
After creation, and whenever the client form is resized, code in ClientResize matches the hidden canvas (PictureBox) to MDIForm's client space dimensions. I chose to add an extra margin equal to the height of a horizontal scrollbar and the width of a vertical scrollbar, to avoid leaving an area unpainted occasionally when a scrollbar is destroyed. Finally, the Refresh code produces the desired image based on whatever class settings the user has chosen, and it assigns the resulting image to the client's Picture property. The class then has no more work until you alter its properties or the user resizes its client.
One remaining precaution deserves mention. If you compile your application to native code, the form-level reference to CMdiBackground doesn't terminate until after the MDIForm is unloaded. At this point, the Controls collection is quite naturally in a (technically speaking) hosed state. Avoid popping an untrappable error in ClientTeardown by testing the hWnd of the created PictureBox using the IsWindow API before referencing the Controls collection. The alternative is to build CMdiBackground using the Dispose design advocated for .NET classes, requiring your client to initiate teardown and rendering the class far less robust.
Q: Retrieve Name
of Running EXE
A. The simplest method is to make a quick call to the GetCommandLine API. The only precaution you must take is to note that the executable name is surrounded with double quotes. Use the quotes as delimiters. Optional command-line arguments follow the second quote; for example:
"E:\VSM\Qa0201\CmdLine\Project1.exe" /test /options
GetCommandLine is implemented as both ANSI and Unicode on all 32-bit Windows platforms, so you can pick which return type you prefer. Both implementations return a pointer to the string data, so you can take advantage of VB's automatic API string conversions (a "feature" otherwise known as UniMess in many circles) to assign GetCommandLineA's return directly to a String variable (see Listing 2).
About the Author