Subclassing

by Murray Cox - GUI Computing

You don't have to have been programming long with Visual Basic to realise that as far as Windows programming goes, you're not seeing the full picture. Practically all of the inner workings of Windows are hidden from us.

I will introduce to you the concept of subclassing, a technique that allows you to handle any Windows message that your window may receive. It involves writing a DLL in C. I will use as an example a DLL function that can be called from VB to control the minimum size that a form may be resized. The only other way that this may be achieved is by using products such as the freeware message blaster, or Desaware's SpyWorks (if you think you can put code in the Form_Resize event, try it yourself).

Every window class has a procedure that handles the messages that are sent to it. Subclassing intercepts the messages before they reach this procedure, and allows you to process any window message before passing them back to the original window procedure. Visual Basic controls are already subclassed.

A requirement of subclassing a control is that in our new Window Procedure, we must pass any messages that we do not handle to the original Window Procedure. Therefore before we subclass the control, we must store the address of the existing Window Procedure. We could store the procedure address in memory, but once a message was received, we would have to check which window the message was sent to, then fetch the original procedure address associated with that window from memory. Remember that potentially this DLL will be used by many applications, and we don't want to be storing a procedure address for every window that we are subclassing, along with the data that will allow our window procedure to process a window's message individually.

An ideal place to store the original address, and associated data, is in the window's own property list. Think of a property list as the tag property in VB with a few differences. We can define our own properties for a window and associate an integer to that property. For example, in my example, I will store the high word address of the original window procedure in a property called aSubClassHigh, the low word address in a property called aSubClassLow, the minimum form width in the property aSubClassMinimumWidth, and the minimum form height in aSubClassMinimumHeight.

Lets take a look at our function, SetMinimumSize. It's declaration is

  extern "C" int _export FAR PASCAL SetMinimumSize(HWND hWndControl, 
  int nMinimumWidth, int nMinimumHeight)
As you can see, it takes as parameters, the handle to the window that we want to subclass, and the minimum width and height in pixels of the window we wish to subclass. The first line of my function (refer to code) checks just to make sure that we aren't subclassing this window again. It does this by checking the properties aSubClassHigh and aSubClassLow to verify that they are NULL.

Next we get the address of the present window procedure, and store it in the window's property list. There are two ways that a property may be stored for a window. The first, the slowest, is by passing the name of the property as a string. For example GetProp(hwnd, "SubClassHigh") will retrieve the integer value of the property SubClassHigh from the window hwnd. The second, is by creating a global "atom" that we will use to reference that property. For example the following adds an atom called SubClassMinimumWidth which is set and retrieved by using the atom rather than the string. The atom must be deleted to ensure the resources it uses are released.

  aSubClassMinimumWidth = GlobalAddAtom("SubClassMinimumWidth");
  SetProp(hWndControl, (LPCSTR)aSubClassMinimumWidth, nMinimumWidth);
  nMinimumWidth = GetProp(hwnd,(LPCSTR)aSubClassMinimumWidth);
  atomReturn = GlobalDeleteAtom(aSubClassMinimumWidth);
After the properties are stored in the window property list, the address of the new window procedure is retrieved and replaced as the default window procedure for the window. This is all that needs to be done to set up our subclassing function.

To handle the messages, the window procedure needs to be declared as follows.

  extern "C" long _export FAR PASCAL WindowProcedure(HWND 
  hwnd,WORD wMsg,WORD wParam,DWORD lParam)
This function will be called every time a message is received by any of the windows we have subclassed. The parameters of the function define the window that the message is being sent to, and other information describing the type of message being sent.

To implement our DLL, we need to process only the message WM_GETMINMAXINFO. All other messages should be sent to the previous window function, whose address is extracted from the window's property list. When our function receives the WM_GETMINMAXINFO message, we retrieve the minimum width and height from the property list for the window, and fill in the MINMAXINFO structure that is passed to the function. By returning zero, we let Windows know that we have processed the message.

To call our function from VB, we need the following declaration in a code module ...

  Declare Function SetMinimumSize lib "Subclass.dll" (byval hwnd 
  as integer, byval nMinimumWidth as integer, byval nMinimumHeight as 
  integer) as integer
and the following in the Form_Load event of the form we wish to subclass
  sub FormLoad()
  dim nReturn as integer
    nReturn = SetMinimumSize(me.hwnd, 200, 200)
  end sub

Files in Subclass.zip

Visual C++ files for DLL (use subclass.cpp for code listing) subclass.rc
recource.h
subclass.def
subclass.cpp
subclass.mak

Visual Basic files for demo

demo.frm
demo.mak
demo.exe

DLL file

subclass.dll
Written by: Murray Cox
Feb 1996



[HOME] [TABLE OF CONTENTS] [SEARCH]