Q. Position Message Boxes Precisely
I'm not happy with the default Windows positioning of message boxesdead center in the screen. How can I control where a message box will be displayed, either exactly or by reference, on a specified form?
ABOUT THIS COLUMN
Ask the VB Pro provides you with free advice on programming obstacles, techniques, and ideas. Read more answers from our crack VB pros. You can submit your questions, tips, or ideas on the site, or access a comprehensive database of previously answered questions.
|
|
A. In a previous column, I showed how you could set a timer to dismiss a message box after a given interval of time had elapsed (see Resources). You can use roughly the same trick to position a new message box, but the box tends to be jerky in display. You get similar results when you restrict a form's size by setting new values in the Resize event.
The cleanest way to position a message box is to watch for its activation by setting a computer-based training (CBT) hook, so named because it's useful
in CBT applications. Set the hook by calling SetWindowsHookEx, specifying a hook of type WH_CBT, and pointing to the desired callback procedure that sinks this hook's events. You can use the App.hInstance and App.ThreadID values for the third and fourth parameters of SetWindowsHookEx.
After setting the CBT hook, call the MsgBox function as you would normally. Just prior to message box activation, Windows calls the specified CBTProc callback procedure. The first parameter to CBTProc tells you what sort of event is happening; you need to watch for HCBT_ACTIVATE. The wParam parameter contains the message box window's hWnd value.
Once you have this window handle, you're free to manipulate the nascent window at will. Call GetWindowRect to determine its width and height, then use either MoveWindow or SetWindowPos to assign new coordinates. Destroy the Windows hook at the end of the CBTProc callback procedure by calling UnhookWindowsHookEx.
I worked up routines that accept the first three standard MsgBox parameters, and optionally a new Left/Top value pair or the hWnd, over which the message box should be centered (see Listing 1). I also felt it best to validate the passed and calculated coordinates to ensure the message box is actually visible within the screen boundaries. You can take this foundation, add a system timer callback, and create a more robust timed message box routine similar to the one I wrote up two years ago.
Q. Uncover Obscured Message Boxes
Message boxes displayed by my application are sometimes hidden behind windows from other applications when these foreign windows have been set to be "always on top." It's an annoying problem, because it makes it look as if my program crashed when it's simply waiting for the user to click on a button on the message box. Is there a way to prevent this and make sure my message box is always visible?
A. There's a little-known and poorly documented message box style constant that'll do the trick for you. When you specify vbSystemModal, in addition to whatever other style flags you need to set, your message box appears above all visible windows:
MsgBox "Message text", vbSystemModal
It seems the usefulness of this constant is now reduced simply to this single task. In the distant 16-bit past, making a message box system modal would prevent the user from interacting with any other window until he or she responded to the message box. Amusingly enough, you can still achieve this effect by using VB4/16 on Windows 9x.
Q. Clone Objects
How do I copy an object? I have two instances of a class, and I want to copy one into the other quickly. Using Set gives me only a reference, essentially creating two pointers to the same data, whereas I need a completely separate instance with the same values. I've tried using CopyMemory with ObjPtr, but I don't seem to be getting anywhere. Any ideas?
A. Unfortunately, the simple answer is that there are no truly simple answers. What most developers have settled on is to add a Clone method to their classes. Clone creates a new instance of the class and populates all its properties with the values of the current instance.
This is a fairly straightforward approach if all the properties are read/write. However, if the class exposes read-only properties, you need to add a "backdoor" Friend method. This step allows the cloning and other code within the same project to set normally read-only properties from outside the cloned class (see Listing 2).
To test this design, you might use code such as this:
Private Sub Command1_Click()
Dim obj1 As CClonable
Dim obj2 As CClonable
' Create first instance.
Set obj1 = New CClonable
obj1.PropertyA = 123456
Set obj1.PropertyB = Me
' Wait one second.
Call Sleep(1000)
' Create second instance.
Set obj2 = obj1.Clone
Set obj2.PropertyB = Command1
' Compare properties.
Debug.Print obj1.PropertyA, _
obj2.PropertyA
Debug.Print obj1.PropertyB.Name, _
obj2.PropertyB.Name
Debug.Print obj1.PropertyC, _
obj2.PropertyC
End Sub
Here, PropertyA remains constant; PropertyB is appropriately different because you've assigned a new object reference to it in the second class instance; and PropertyC (a timestamp issued in Class_
Initialize) is normally different, because you created the classes one full second apart. Here's an illustration of how this technique differs from direct assignment of one object reference to another:
' Create second instance.
Set obj2 = obj1
Set obj2.PropertyB = Command1
In this case, the values returned by all three properties remain identical, reflecting whatever was last assigned to either object reference. For example, both objects return a reference to Command1 as PropertyB.
Karl E. Peterson is a GIS analyst with a regional transportation planning agency and serves as a member of the Visual Basic Programmer's Journal Technical Review and Editorial Advisory Boards. Online, he's a Microsoft MVP and a section leader on several VBPJ forums. Find more of Karl's VB samples at
www.mvps.org/vb.