[ Home | FAQ | OCX Book | Sites ]  

OLE Controls Frequently Asked Questions
Subclassing Windows Controls

Last Update: June 5, 1996

What is the purpose of a reflector window?

Standard Windows controls (e.g., EDIT, LISTBOX, etc. ) are typically associated with a parent window. In most cases the parent window is a Windows dialog. The dialog window acts in many ways like an OLE Control container. It coordinates the behavior of the child windows within it, maintains the tabbing order of the controls, notifies them of changes in the environment, and accepts messages, or notifications, from the child controls when something occurs.

When a Windows control is subclassed for use within an OLE Control, this dialog based environment does not exist. OLE controls have their own techniques of interacting with the container. The container and controls work together to establish the tabbing order, ambient properties provide a way for the control to retrieve information from the container, and the control can fire events to notify the container of internal changes. The functionality is similar to that in a parent/child window environment, but the implementation is quite different. Instead of Window messages going back and forth, we use OLE Automation.

What I'm getting at is this. There is no parent window for the subclassed controls to post and receive the window messages that define their behavior. To solve this problem, the OLE Control standard describes a reflector window. A default reflector window is created by MFC's COleControl implementation, but the container (e.g., a VB form) can also provide a reflector window. Containers that provide a reflector window reduce overhead as they use one window that acts as the parent for all contained OLE controls. If the control container provides this feature, it must set the MessageReflect ambient to TRUE, and if not, MFC will create a reflector window for each OLE control that subclasses a Windows control.

The purpose of the reflector window is to reflect back to the OLE control certain window messages that would normally go to the parent of a child window. A control notification message such as BN_CLICKED, EN_CHANGED, etc. will be reflected back to the OLE control, so that it can be implemented as an OLE event. For example, the WM_CTLCOLOR message is sent to the parent to get information about how the child window should paint itself, but is now reflected back to the OLE control to handle itself. The OLE control can then get the container's ambient color properties and paint itself appropriately.

Back to top


Is there anything special to subclassing the Windows 95 common controls?

There are a couple of things that you need to be aware of, but its pretty simple. I've successfully subclassed all of the common controls except the RichEdit control, but I'm still working on it. One thing that you'll have to do is route and handle the WM_NOTIFY messages through the reflector window. Luckily, MFC 4.0 added an OCM_NOTIFY message just for this case. The older Window controls provided notification via the WM_COMMAND message, but most of the Win95 controls now use the special WM_NOTIFY message. It passes along a control specific structure that reduces the number of messages required to service notifications messages. To learn more check out Tech Notes 61 and 62. Just don't forget to trap the OCM_NOTIFY. Here's some quick example code from a subclass of TreeView.

////////////////////////////////////////////////////////////////////////////
// Message map
BEGIN_MESSAGE_MAP(CAWDTreeCtrl, COleControl)
   //{{AFX_MSG_MAP(CAWDTreeCtrl)
   ....
   //}}AFX_MSG_MAP
   ...
   ON_MESSAGE(OCM_NOTIFY, OnOcmNotify)
   ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()

LRESULT CMyTreeCtrl::OnOcmNotify( WPARAM wParam, LPARAM lParam )
{
   switch( ((LPNMHDR)lParam)->code )
   {
      // Trap the TVN_BEGINDRAG notify message from the
      // TreeView control
      case TVN_BEGINDRAG:
         BeginDrag( (NM_TREEVIEW*) lParam );
         return TRUE;

      default:
         break;
   }
   return 0;
}

You can also use the MFC common control wrapper classes to make thinks easier. See the next question.

Back to top


Can I use the MFC 4.0 common control classes when I subclass a Win95 common control?

Absolutely. Since COleControl derives from CWnd, and points to an HWND of the actual control itself, all you have to do is cast your this pointer to the appropriate class. Here's an example of how I did it in a control that subclasses the Win95 TreeView control:

int CMyTreeCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   ...
   // If not in run mode, don't waste our time.
   if ( ! AmbientUserMode() )
      return 0;

   // build an image list
   m_ImageList.Add(...);
   ...

   // Cast ourselves to the appropriate MFC class
   // and call the required method. In this case its SetImageList
   // of the CTreeCtrl class
   // Warning! this cast only works because SetImageList is an
   // inline function, watch out.
   ((CTreeCtrl*) this)->SetImageList(  &m_ImageList, TVSIL_NORMAL );
   ...
}

You may also have to include the MFC common control class definitions in your control's STDAFX.H file like this:

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#define VC_EXTRALEAN            // Exclude rarely-used stuff from Windows headers

#include <afxctl.h>         // MFC support for OLE Controls

// Include support for the Win95 common controls
#include <afxcmn.h>

Back to top


I can't get the colors of my (subclassed) control to paint correctly?

As described in answer to the "What is the purpose of a reflector window?" question, subclassed OLE controls must implement handlers for those events and messages that would typically be handled by a parent window. An important message that must be handled is the WM_CTLCOLOR message. This message is sent by a standard windows control to its parent to allow it to set the correct DC attributes before it paints the control. For an OLE control this message is basically sent to itself via the reflector windows. So, you need to add a handler for the OnOcmCtlColor message. Here's some code from my EEDIT example control.

BEGIN_MESSAGE_MAP(CEeditCtrl, COleControl)
...
   ON_MESSAGE(OCM_CTLCOLOREDIT, OnOcmCtlColor)
...
END_MESSAGE_MAP()
...
LRESULT CYourCtrl::OnOcmCtlColor( WPARAM wParam, LPARAM lParam )
{
   // m_pBackBrush is an instance variable that stores the
   // current BackColor brush. If this is the first time through
   // we need to create it. The brush is deleted in the destructor
   if ( m_pBackBrush == NULL )
      m_pBackBrush = new CBrush( TranslateColor( GetBackColor() ));

   // Get the DC provided and set the appropriate attributes
   CDC* pdc = CDC::FromHandle( (HDC) wParam );
   pdc->SetBkMode( TRANSPARENT );
   pdc->SetBkColor( TranslateColor( GetBackColor() ));
   pdc->SetTextColor( TranslateColor( GetForeColor() ));

   // Return a handle to the BackColor brush
   HBRUSH far* hbr = (HBRUSH far*) m_pBackBrush->GetSafeHandle();
   return ((DWORD) hbr);
}

// We need to create a new brush whenever the BackColor
// Changes
void CEeditCtrl::OnBackColorChanged()
{
   delete m_pBackBrush;
   m_pBackBrush = new CBrush( TranslateColor( GetBackColor() ));
   InvalidateControl();
}

Back to top


I'm subclassing an EDIT control. Why don't the arrow keys work?

Containers may trap many of the virtual keys before they make it to your control's code. To grab them before the container gets them, you must override CWnd::PreTranslateMessage, intercept the messages, and send them to yourself. Here's a quick example that will enable the use of the left and right arrow keys in a subclassed EDIT control:

BOOL CEeditCtrl::PreTranslateMessage( MSG *pMsg )
{
   switch( pMsg->message )
   {
   case WM_KEYDOWN:
   case WM_KEYUP:
      switch( pMsg->wParam )
      {
         case VK_LEFT:
         case VK_RIGHT:
            // Send the message to ourselves
            SendMessage( pMsg->message, pMsg->wParam, pMsg->lParam );
            return TRUE;

         default:
            break;

      }
      break;
   }
   return COleControl::PreTranslateMessage( pMsg );
}

This technique works with many other controls as well. Anytime that your control doesn't appear to be receiving a message that you need, override this method and check for its occurrence.

Back to top


How do I change the default font on a standard EDIT control?

All you have to do is add the stock Font property to your subclassed control. The COleControl implementation handles setting the actual EDIT control font using the WM_SETFONT message. If you don't want to use the stock Font property, check out the MFC source code for SetFont and OnFontChanged in CTLPROP.CPP

Back to top


Why can't I set the colors on my subclassed BUTTON control?

Button controls do not pay attention to the reflected OCM_CTLCOLORBTN message. If you want to createa button control that provides custom color capabilities, you will have to owner draw the control.

Back to top


When calling 'PrintForm', my subclassed EDIT control doesn't print correctly. Why not?

Controls that subclass existing windows use the DoSuperClassPaint method for rendering of the control. This method does a very poor job of providing a good representation during the container's design-mode and when the control is asked to draw into a metafile device context. When printing, the latter occurs, and subclassed controls that use the default DoSuperClassPaint method just don't provide a good print representation. What I've done for my controls that subclass existing windows, such as EDIT, is to develop a DrawDesign method that I use for both design-mode drawing and for rendering when the OnDrawMetafile method is called by the container. In Designing and Using OLE Custom Controls, I develop an expression evaluation control that subclasses and EDIT window. The technique described above is fully implemented here. For details, download the Chapter 10 source.

Back to top


I want to develop and OCX that works like the MessageBlaster VBX. Can you help?

Here's the original question:
I want to make an OLE Custom Control in VC++ 4.0 that works like the MessageBlaster, Subclassing another window an fires events when messages arrive to that window. Can you help me?

Here's my response:
It shouldn't be too hard to develop such a control. Off the top of my head, here's how I would go about it. First, the control will need to accept an HWND from the user. A runtime only property would work just fine for this. At runtime the user would set this property to the HWND of the control that they want to intercept messages. Within the control, I would use a CWnd-derived class, say "CBlasterWnd" that would implement all of the basic functionality. It would basically fire an event or events on receipt of user-specified messages. To do this, though, you will need to subclass the original HWND with a CBlasterWnd instance. The main control class will have an instance of the CBlasterWnd class, and when the HWND property is set it will subclass the provided HWND using MFC's SubclassWindow method. The CBlasterWnd class should also maintain a pointer to the COleControl-derived class so that it can fire events as messages are received. Those are my thoughts. Let me know how the implementation goes. I'd love to post some of the code here so others can benefit.

Back to top


The Appearance property doesn't work with my subclassed EDIT control. Why not?

Here's the gist of the original question:
I'm using Windows NT 3.51, and developing a control that subclasses the standard EDIT control. I've added the Appearance property, and the control draws 3D during the design phase, but I cannot for the life of me get it to draw 3D at runtime. What gives?

Here's my response:
The stock Appearance property basically just toggles the WS_EX_CLIENTEDGE windows style bit. On Windows NT 3.x and Windows 3.x, this will basically do nothing. The WS_EX_CLIENTEDGE style only works on Windows 95 and Windows NT 4.x. Your control draws correctly in design mode because COleControl calls AfxDrawBorder before calling OnDrawMetafile, thus directly drawing the 3D border when the Appearance property is set to 3D. To provide a 3D appearance on Windows NT 3.51 (at runtime) you will need to draw the border yourself. You should also detect the OS version so that you *only* do it on NT 3.51. You can use the Win32 DrawEdge function, the standard DC drawing calls, or even the CTL3D*.DLL routines. For details on how the Appearance property is implemented, check out CTLPROP.CPP and CTLCORE.CPP.

Back to top


Typical Disclaimer: This FAQ is copyright (c) 1996 by Tom Armstrong (toma@sky.net). You have the right to copy and distribute this file in any way you see fit as long as this paragraph is included with the distribution. No warranties or claims are made as to the validity of the information contained herein. So use it at your own risk


Send mail to Tom - toma@sky.net