Q: Paint MDIForm
Backgrounds
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
I need to determine the name of the EXE that loads my DLLs and OCXs. How
can I do this without asking the EXE itself?
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
Karl E. Peterson is a GIS analyst with a regional
transportation-planning agency and serves as a member of the VSM
Technical Review and Editorial Advisory boards. Online, he's a Microsoft
MVP and a section leader on several VSM forums. Find more of
Karl's VB samples at www.mvps.org/vb.
|