We help IT Professionals succeed at work.

We've partnered with Certified Experts, Carl Webster and Richard Faulkner, to bring you a podcast all about Citrix Workspace, moving to the cloud, and analytics & intelligence. Episode 2 coming soon!Listen Now

x

OLE control subclassing SysDateTimePick32 in comctl32.dll

Medium Priority
1,597 Views
Last Modified: 2013-11-25
I'm attempting to use VC++ 4.0's OLE ControlWizard to subclass the SysDateTimePick32 control in comctl32.dll.  This DLL comes with IE3.0, and also ships with IE4.0.  I found out about the SysDateTimePick from a Visual Basic Programmer's Journal article, but I don't have any documentation on the control.  It's a standard Windows control that looks like a combo box, where the part that drops down is a calendar instead of a list.  The text part contains the current date (time) selected.

In a non-OLE control project, calling CreateWindowEx with SysDateTimePick32 as the window class correctly creates and displays the window.  In the OLE control project, I am not calling CreateWindowEx.  Instead, in MyControl::PreCreateWindow, I'm setting the lpszClass member of the CREATESTRUCT parameter to SysDateTimePick32, and performing other initialization there.  You can duplicate this exactly by instructing ControlWizard to subclass any Windows control and then modifying PreCreateWindow as follows:

BOOL CDateTimePickCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
      HINSTANCE hinstLib;
      MYPROC ProcAdd;

      hinstLib = LoadLibrary("comctl32");
      ASSERT (hinstLib != NULL);

      ProcAdd = (MYPROC) GetProcAddress(hinstLib, "InitCommonControlsEx");
      ASSERT (ProcAdd != NULL);

      tagInitCommonControlsEx      iccex;
      iccex.lngSize = sizeof(iccex);
      iccex.lngICC = ICC_DATE_CLASSES;
      (ProcAdd) (&iccex);

      cs.lpszClass = _T("SysDateTimePick32");
      cs.cx = 120;
      cs.cy = 20;
//      cs.style = 0x4;

      return COleControl::PreCreateWindow(cs);
}

These declarations are in the application's header file:

typedef struct tagInitCommonControlsEx {
    long lngSize;
    long lngICC;
} tagInitCommonControlsEx;

#define ICC_DATE_CLASSES 0x100 // month picker, date picker, time picker, updown

typedef VOID (*MYPROC)(struct tagInitCommonControlsEx *);

The problem is that the OLE control does not display the text part at all.  It responds to a mouse click on the right end of the control by dropping down the calendar.  If you set cs.style to 0 (the default is WS_CHILD) in PreCreateWindow, interestingly enough the text part does show, but in what looks to be a caption area; the window will not be sited on the container, however, which is "wrong".  I don't know if the solution is in setting the window style appropriately, or something else.

Please help!
Comment
Watch Question

Author

Commented:
Edited text of question

Author

Commented:
I just realized that the control does respond to the mouse click on the combo-box part of the control, to drop down the calendar.

But it does not draw the current date in the text area, which is also what I need.

Author

Commented:
Edited text of question

Author

Commented:
Adjusted points to 200

Author

Commented:
Adjusted points to 240

Author

Commented:
Adjusted points to 250

Commented:
The fundamental problem with your code is that you change the cs structure content and then call the base class implementation of PreCreateWindow(), which causes the changes you made to the create struct to be reset.

You should code your routine more like this:

BOOL CDateTimePickCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
   if (!COleControl::PreCreateWindow(cs))
      return FALSE;

   HINSTANCE hinstLib;
   MYPROC ProcAdd;

   hinstLib = LoadLibrary("comctl32");
   if (hInstLib == NULL)
      return FALSE;

   ProcAdd = (MYPROC) GetProcAddress(hinstLib, "InitCommonControlsEx");
   if (ProcAdd == NULL)
      return FALSE;

   tagInitCommonControlsEx iccex;
   iccex.lngSize = sizeof(iccex);
   iccex.lngICC = ICC_DATE_CLASSES;
   if (! (ProcAdd)(&iccex))
      return FALSE;

   cs.lpszClass = _T("SysDateTimePick32");
   cs.cx = 120;
   cs.cy = 20;
   // cs.style = 0x4;

   return TRUE;
}

By comparing this to what you originally posted, you'll realize
that your code has some other problems: mainly, bad error detection. If LoadLibrary() fails, you must fail the PreCreateWindow() call. Performing an ASSERT() here isn't enough. The same advice goes for your GetProcAddress() call as well as your actual call to the InitCommonControlsEx() function.

The next big problem you have is that you never call FreeLibrary() on the COMCTL32 handle you've loaded for yourself.

.B ekiM


Author

Commented:
I thought that you should make changes to the CREATESTRUCT
*before* calling COleControl::PreCreateWindow - if you look at the MFC source, you'll see that CreateWindowEx is called from around there with the CREATESTRUCT parameters.

Anyway, I tried your code, and it gives me exactly the same result - the window is 'empty', but displays the button on the right end when you click on it, and drops down the date/time picker.

Author

Commented:
I'm aware that the ASSERTs were poor error-handling code; however, at the time I didn't care at all about this - first of all I want the control to be drawn properly.

I would like this method to work.  However, an alternative that I tried was to create another window with CreateWindow in PreCreateWindow, making the parent the same as that in the CREATESTRUCT.  This works fine.  The problem here is that I need my ActiveX control to subclass that window.  If I use SubclassWindow in PreCreateWindow or OnCreate, I get failed ASSERTS in MFC, and I can't figure a way around this.

If anybody can get either technique to work, it would be much appreciated!

Commented:

Sorry, I somehow got the impression that you were subclassing the C++ object, not the actual Windows control. And I'm sorry it took so long to get back to you; every time I try to use Experts Exchange, I end up spending the rest of the evening reporting bugs to problems@experts-exchange.com.

Anwyway, we kind of regret putting the feature to subclass existing Windows controls into the OLE Control wizard. It makes it appear that it's trivially easy to subclass an existing windows control in an OLE control, but it really isn't. The problem is getting the control to render into an arbitrary DC.

OLE Controls need to render into whatever DC their container provides.  Some Windows controls are completely inept at doing this, as you've found with the SysDateTime control. You can get some of the way there by writing your own OnDraw() override, like this:

void CSubCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
   if (m_hWnd == NULL)
      CreateWindowForSubclassedControl();

   if (m_hWnd != NULL)
   {
      CRect rcClient;
      GetClientRect(&rcClient);

      if (rcClient.Size() != rcBounds.Size())
      {
         pdc->SetMapMode(MM_ANISOTROPIC);
         pdc->SetWindowExt(rcClient.right, rcClient.bottom);
         pdc->SetViewportExt(rcBounds.Size());
      }
      pdc->SetWindowOrg(0, 0);
      pdc->SetViewportOrg(rcBounds.left, rcBounds.top);
      ::CallWindowProc(
         *GetSuperWndProcAddr(),
         m_hWnd, WM_PAINT, (WPARAM)(pdc->m_hDC),
         (LPARAM)(0));
   }
}

You'll find that this code works in Test Container, but won't work if you add the control to a MFC dialog box (including the Visual C++ IDE's dialog box editor itself). That's because Test Container has the control paint into a metafile and then renders the metafile, while MFC containment doesn't use metafiles if the control has a window.  Some other containers (like VB or Access) will probably paint the control correctly.

The bug in the Windows SysDateTime control is that it should be able to respond to WM_PRINT (not WM_PAINT!) properly, but it doesn't. It only works with WM_PAINT, and only (therefore) against a compatible DC... which is the source of the problems you're seeing.

The best way to handle this scenario is to create the control as a child window of your control instead of subclassing it.  This'll change your architecture a little, and may force you to subclass the control by stealing its window proc directly--depending on how you wanted to react to user input.  (Eg, use SetWindowLong() instead of subclassing the control with MFC--this is how you'll get around the assertions.)

By the way, you do need to call base class implementations of PreCreateWindow() first, as PreCreateWindow() for any class will set the options that it wants. As it sets those options, it's likely to be undoing the changes that you yourself wanted. So, if you want to be sure that your changes are in effect, you'll need to call the base class first and then set the information you want afterwards so your changes aren't undone.  The Wizard does sometimes emit code that calls the base class first--and that's a bug in the Wizards.

.B ekiM

Not the solution you were looking for? Getting a personalized solution is easy.

Ask the Experts

Author

Commented:
Thanks.  I couldn't get this code to work - it wasn't showing anything in Test Container or VB, so I just went with the other method of creating another child window in PreSubclassWindow and subclassing that with SetWindowLong.  This works.

How many points is that... 1120.  Whoa!  You deserve it.
Access more of Experts Exchange with a free account
Thanks for using Experts Exchange.

Create a free account to continue.

Limited access with a free account allows you to:

  • View three pieces of content (articles, solutions, posts, and videos)
  • Ask the experts questions (counted toward content limit)
  • Customize your dashboard and profile

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.