|
|
|
|
|
About this page
This article describes how to subclass controls using library
'SmartSubclass' and gives an example on how to change a
Progressbar gauge.
Downloads
|
|
Every window has a function that handles its messages. This function is called
a windowproc. If we were coding in C++ we would be able to modify this windowproc
but in VB we have to rely on the implementation of each control to get access
to its messages (events). The problem comes when we find that not all messages
are passed from the windowproc to VB or, even worse, when we discover that there
isn't a way to modify such messages.
The solution to this problem is called subclassing: we can set our own VB function
to act as a windowproc and trap all messages before these are processed. There
are very good articles on the net that describe how to subclass a window from
VB and you can read them if you need more information. I would suggest "Subclassing
without the crashes" from Steve McMahon, "Intercepting
Windows Messages in Visual Basic" from Deborah L. Cooper or "HOWTO:
Subclass a UserControl" from MSDN.
I will now focus on how to use my library SmartSubClass.dll so you can subclass
your controls without having to worry about the implementation.
The steps you need to follow to install the library are:
1. Download SmartSubClass.dll
2. Use RegSvr32.exe to register the class
3. Open your VB project
4. Click on Project>References
5. Click on "VBSmart SubClass" or click on 'Browse' and find SmartSubClass.dll
6. You're ready now to use the library
Once you have added a reference to the library you have a new class available:
SmartSubClass. You can define variables of this class and use them to control
your windows. Every instance of the class can take control of as many windows
as you want. It is up to you to whether have one variable per window or a single
variable for more than one window.
The class has a single function SubClassHwnd() and a single event NewMessage().
You will use the function to both subclass and unsubclass your windows and you
will use the event to detect when a new message for your subclassed window is
available.
I will use the following example to explain in more detail how to use the library:
you all know that VB, through its library mscomctl.ocx (comctl32.ocx in VB5)
provides a control named Progressbar. Microsoft describes this control saying
"(...) shows the progress of a lengthy operation by filling a rectangle
with chunks from left to right".
In fact it provides two ways of displaying the progress status: ccScrollingStandard
and ccScrollingSmooth, available through its property 'Scrolling'. We are going
to subclass two Progressbars and make them show its progress using a gradient
style.
Open a new VB project, add a reference to 'VBSmart SmartSubClass' as described
above, add two progressbars and name them Progressbar1 and Progressbar2.
The first thing you will need to do is to declare a SmartSubClass variable.
Dim WithEvents
oSniff As SmartSubClass
Make sure you use the keyword 'WithEvents' when you declare the variable. Now,
if you open the code window and you click on 'oSniff' you will see there's an
event for this variable. This is where you will trap the progressbar messages.
Now we have to create our variable and tell it to subclass both progressbars.
We could do that on the Form_Load event. Let's try something like:
Private Sub Form_Load()
Set oSniff = New
SmartSubClass
oSniff.SubClassHwnd ProgressBar1.hWnd, True
oSniff.SubClassHwnd ProgressBar2.hWnd, True
End Sub
As you can see the function SubClassHwnd has two parameters: a window handle
and a boolean flag. The window handle is the identifier of the main window of
a control and it is normal accesible through its property Hwnd. Most controls
have this property, although there are exceptions like the Label, the Shape
or the Line. The second parameter is a flag that indicates whether to subclass
or unsubclass the window. It is good practice to restore the original windowproc
when our application unloads. If we don't, it shouldn't be a problem because
the class does it when its 'Finalize' event is fired but... it is good practice!
So that's what we're going to do next: make sure that both progressbars are
unsubclassed before our form unloads. We need to type the following code:
Private Sub Form_QueryUnload(Cancel
As Integer, UnloadMode As Integer)
oSniff.SubClassHwnd ProgressBar1.hWnd, False
oSniff.SubClassHwnd ProgressBar2.hWnd, False
End Sub
At this stage we are ready to receive all Windows messages sent to both
progressbars through the event oSniff_NewMessage(). To change the behaviour
of the progressbar and have the gradient effect we will have to listen to the
message WM_PAINT. This is the message that Windows sends to a progressbar when
its client area needs repainting.
You will see that the event NewMessage has 5 parameters:
ByVal hWnd
As Long
ByRef uMsg
As Long
ByRef wParam
As Long
ByRef lParam
As Long
ByRef Cancel
As Boolean
The first 4 parameters are typical of a Windows message: 'hWnd' is the window
handler of the target window, 'uMsg' is the message identifier, 'wParam' and
'lParam' will vary depending on the message and you will have to access Microsoft's
documentation (or any other source of information) to get their possible values
and description. Finally, 'Cancel' is a boolean flag that will allow you to
decide whether you want Windows to handle your message after you trap it or
not. 'Cancel' comes with a default value of False. You will normally set 'Cancel'
to True only for messages that you modify by adding code to the function. Another
interesting thing of NewMessage is the fact that all message parameters are
passed by reference. You can change their values before sending them back to
Windows.
In order to modify the WM_PAINT message you will have to declare a constant
for it. You can normally find this constant using the 'API Text Viewer' that
comes with VB. Not all APIs are included in this tool but most of them are.
Another interesting source of information is AllAPI
Network where you will find not only an updated version of the API library
but a better tool to copy and paste to VB.
Add this line to your VB project:
Private Const WM_PAINT
= &HF
The final step is to add code to the NewMessage to handle the WM_PAINT, something
like:
Private Sub oSniff_NewMessage( _
ByVal hWnd As Long, _
ByVal uMsg As Long, _
wParam As Long, _
lParam As Long, _
Cancel As Boolean)
Select Case uMsg
Case WM_PAINT
If
hWnd = ProgressBar1.hWnd Then
pFillWithGradient1
hWnd
Else
pFillWithGradient2
hWnd
End
If
Cancel
= True
End Select
End Sub
As you can see, we only have to select the message and call our own painting
function depending on the window handle (the same variable handles two different
windows and we want different progressbars to have different gradients). It
is also important to set the flag Cancel to True because otherwise the original
windowproc would continue and would paint the progressbar - we would think that
nothing is working!
If you want the code for pFillWithGradient1 and
pFillWithGradient2 you can download the test project from the top of
this page...
Finally I would like to explain why SmartSubClass.dll has been implemented as
an external DLL rather than a simple class module. Subclassing in VB has only
one problem: if you press the 'End' button from your
VB toolbar and your class (or any subclassing code that you implement) belongs
to your project, your VB session wil crash! This problem has been reported by
Microsoft and they give a solution which I think is only partial. They provide
the 'Debug
Object for AddressOf' that can be downloaded from Visual Basic's download
center. The solution is a DLL (Dbgwproc.dll) that allows VB projects to know
whether they are running in debugging mode (IDE) or not. The solution works
fine but they've missed one scenario: if your application executes the 'End'
statement the program will still crash. This is because the 'End' statement
stops code execution abruptly, without invoking the Unload, QueryUnload, or
Terminate event, or any other Visual Basic code. Unfortunatelly, many programmers
use the 'End' statement to close their applications...
So what's the solution then? well, the solution is to have your class in a separate
DLL. This allows you to use the library from VB and press the 'End' button without
getting one of those ugly GPF windows. You will also be able to use the 'End'
statement in your applications. You have access to the source code of the SmartSubClass
class so you could include it as a class module in your project but... you do
so at your own risk!
|
|