Resize and ActiveX
support for WIN32 dialogs
This article provides a few samples to enable features in WIN32 dialogs such
as support for dialog resizing and ActiveX controls. VC++6 / VC++7.x projects
are provided.
1. The story
As an employee in a software company, I have been given the task to add
features to an old-fashioned Win32-based software program. Since last year I
have been using almost exclusively the latest technologies including .NET, and
ironically only to fall into projects dealing with 10 years old technologies. Oh
my goddish!
So I have been facing Win32 dialogs, global callback-based procedures that do
not natively provide a way to be resized on will, and I find it very poor that
Win32 dialogs cannot host ActiveX controls either. All of this seems to me like
using prehistoric tool for todays' business needs. And don't get me started on
ergonomic UI. These amazing limitations have allowed poor managerial technical
decisions like using VB instead of C++, opening the path for blood.
So it all started by implementing the features as described in the specs, and
finally came to the point that the two mentioned constraints on the UI were so
lousy that the whole project was looking like a student project. I knew that MFC
CDialogs had native ActiveX support so I began searching for ways
to fill the gap.
In fact, with MFC, ActiveX support is almost scattered in the entire source
code tree. Since it is more than annoying to have to bring all that MFC mess
within the app, along with being forced to either statically link with the
libraries (huge dlls), or have to distribute it (MFC42 is probably the biggest
mess in setup issues one can think of. Thanks MS for upgrading the (likely to
be) system locked MFC dlls without changing the name of the dlls.), I have
decided to find other ways. One of them was to extract the relevant code from
the MFC. I gave up after a few hours, it required such an amazing amount of work
that there was no point in doing so, except for sadistic reasons.
The original MSPRESS ActiveX inside out[^] book also gave me no idea about how I
could accomplish this simple task. Coincidentally, I stumped on one of Michael
Dunn's articles[^]
(ATL GUI classes), and found that, after a few minutes browsing the MSDEV
ATL source code, that it was providing the blocks I needed to come up with the
features I wanted for my WIN32 dialogs. Fortunately, unlike MFC, ATL is
statically linked (by default) and is small in size, really small, providing
against all odds an incredible framework to start with. Don't be afraid with the
ATL acronym, I won't annoy you with COM wrappers. ATL GUI classes is an almost
separate code which has enough implementation and simplicity to develop well
standing light apps, in no matter of time.
The remainder of this article shows how to bring resizing and ActiveX support
to Win32 dialogs. We are talking general features, and this doesn't preclude the
fact that, most of the time, when people want to add ActiveX controls to
dialogs, it's often because they want to add the Windows Media player to their
app, or the Web browser. Those are things we are going to address too.
2. Adding resizing to WIN32 dialogs
Here is the agenda :
- definition of the feature
- details over the helper grip object
- standard ATL dialog object
- integration of the grip into the ATL dialog
- initialisation steps
- we are done!
Adding resizing to Win32 dialogs is to allow dialogs to be resized using the
right-bottom corner grip, along with moving/resizing controls living inside the
dialog. Even though this feature is now default with common controls on W2K and
higher (for instance the Open file dialog), if you want to provide it, there is
more than starting the app on a W2K box, or providing a manifest file. Just to
make it clear, the dialog resize feature has to be coded somehow.
People used to deal with global Win32 dialog callbacks will be shocked with
the opportunity they have to bring code which is as small than standard Win32
dialog wnd procs, along with full object orientation. That makes a great
difference in practice. For instance, the resize feature can be added to an ATL
dialog just by adding a specialized member to it, the grip object. There is no
need to deal with global state variables.
To have a code skeleton to start with, just create a new Win32 application
using MSDEV. Then, add references to the following headers :
#include <atlbase.h>
#include <atlwin.h>
#include <atlcom.h>
#include <atlhost.h>
Create a new file and paste this code into it :
#include "resource.h"
#include "AboutDialog.h"
CComModule _Module;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwICC = ICC_LISTVIEW_CLASSES;
InitCtrls.dwSize = sizeof(INITCOMMONCONTROLSEX);
BOOL bRet = InitCommonControlsEx(&InitCtrls);
_Module.Init(NULL, hInstance, &LIBID_ATLLib);
CAboutDialog dlg;
dlg.DoModal();
_Module.Term();
return 0;
}
And then paste the actual about dialog implementation :
class CAboutDialog : public CDialogImpl<CAboutDialog>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled)
{
CenterWindow();
return TRUE; }
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
EndDialog(IDCANCEL);
return 0;
}
LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(IDOK);
return 0;
}
LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(IDCANCEL);
return 0;
}
};
As you can see, the code above is going to load the IDD_ABOUT
dialog resource when it starts. Just make sure you have such a dialog ready. The
global DialogProc callback is hidden, which is fine.
If you need to process some dialog messages, just add an entry to the message
map macro, and provide a handler to it. In case you are interested in some
notification messages instead, use the NOTIFY_HANDLER(control_id,
notification, func) macro. Look atlwin.h for further
info.
Now that we have the dialog framework to play with, let's clone this code,
call the dialog CSampleDialog and simply add the grip object as a
member of the class. Just like this :
class CSampleDialog : public CDialogImpl<CSampleDialog>
{
protected:
CResizableGrip m_grip;
...
};
The resizable grip is the resulting code from Paolo Messina's excellent
article about MFC's CResizableDialog[^]. I have extracted the only relevant
portion from this code, added my own features, and then made it MFC-free so it
ended as a reusable object without any run-time dependency. The resizable grip
is a stripped diagonal-looking scrollbar standing in the right-bottom corner of
the dialog. It mimics a standard resize grip. That said, what we do in the
dialog is create an instance of a grip, and then pass references to all dialog
controls to it, along with custom resizing rules. Resizing rules describe what
is to be done with a given control when the dialog is resized. Namely, should it
be moved, resized, both, or even remain as is (default rule) ? The grip provides
a simple API for this. Here is how we use it in our dialog :
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
m_grip.InitGrip( m_hWnd );
m_grip.ShowSizeGrip();
HWND hTreeView = GetDlgItem(IDC_TREE1);
{
CResizableControl *pListDynamics = m_grip.AddDynamicControls();
if (pListDynamics)
{
pListDynamics->Init(hTreeView);
pListDynamics->AllowResizeOnResize();
}
}
HWND hOk = GetDlgItem(IDOK);
{
CResizableControl *pListDynamics = m_grip.AddDynamicControls();
if (pListDynamics)
{
pListDynamics->Init(hOk);
pListDynamics->AllowMoveXOnResize();
}
}
...
return TRUE; }
Since those initialisation steps are clear code patterns, I am going to
introduce helping macros for this purpose :
#define RX 1
#define RY 2
#define RXY RX | RY
#define MX 4
#define MY 8
#define MXY MX | MY
#define BEGIN_SIZINGRULES(grip, hParent) \
grip.InitGrip( hParent ); \
grip.ShowSizeGrip();
#define ADDRULE(grip, item, rule) \
{ \
HWND hObject##item = GetDlgItem( item ); \
if ( hObject##item ) \
{ \
CResizableControl *pListDynamics = grip.AddDynamicControls(); \
if (pListDynamics) \
{ \
pListDynamics->Init(hObject##item); \
if ((rule)&RX) pListDynamics->AllowResizeXOnResize(); \
if ((rule)&RY) pListDynamics->AllowResizeYOnResize(); \
if ((rule)&MX) pListDynamics->AllowMoveXOnResize(); \
if ((rule)&MY) pListDynamics->AllowMoveYOnResize(); \
} \
} \
}
#define END_SIZINGRULES
#define DORESIZE(grip) \
if (grip.GetSafeHwnd()) \
{ \
grip.UpdateGripPos(); \
grip.MoveAndResize(); \
}
#define MINMAX(x,y) \
LPRECT pRect = (LPRECT) lParam; \
\
int nWidth = pRect->right - pRect->left; \
if (nWidth<x) pRect->right = pRect->left + x; \
\
int nHeight = pRect->bottom - pRect->top; \
if (nHeight<y) pRect->bottom = pRect->top + y;
Thanks to the macros, the code above simplifies as :
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
BEGIN_SIZINGRULES(m_grip, m_hWnd)
ADDRULE(m_grip, IDC_TREE1, RXY)
ADDRULE(m_grip, IDOK, MX)
END_SIZINGRULES
...
return TRUE; }
Then, we override the WM_SIZE and WM_SIZING message handlers to provide
actual support for the feature. Namely, WM_SIZE is sent by Windows whenever the
dialog is being resized. WM_SIZING is also sent by Windows and provides a unique
opportunity to resize the passed bounding rect on will, allowing us to apply
predefined min/max rules. A sample code is as follows :
BEGIN_MSG_MAP(CSampleDialog)
...
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_SIZING, OnSizing)
...
END_MSG_MAP()
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
int cx, cy;
cx = LOWORD(lParam);
cy = HIWORD(lParam);
if (m_grip.GetSafeHwnd())
{
m_grip.UpdateGripPos();
m_grip.MoveAndResize();
}
return 0;
}
LRESULT OnSizing(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
LPRECT pRect = (LPRECT) lParam;
int nWidth = pRect->right - pRect->left;
if ( nWidth < 150 ) pRect->right = pRect->left + 150;
int nHeight = pRect->bottom - pRect->top;
if ( nHeight < 150 ) pRect->bottom = pRect->top + 150;
return 0;
}
The min/max bounding rect is of course highly correlated to the layout and to
what controls the dialog is made of. Which means that the
OnSizing() actual implementation is likely to be different for each
dialog, unlike OnSize()'s.
Before we compile the code, we still need to do a few things :
- add the border resizing style to the dialog resource (Dialog
properties, Styles tab, Border dropdown)
- add the clip children style
- in fact, those styles are automatically forced at run-time by the grip
object, so if I get into some of those details, it's because I want you to be
able to build the process from scratch. Forcing the styles at run-time goes with
code like this :
::SetWindowLong(hParent, GWL_STYLE,
::GetWindowLong(hParent, GWL_STYLE) | WS_THICKFRAME | WS_CLIPCHILDREN);
add a maximize box, to support double-clicks in the dialog title (the grip is
removed when we are in zoomed mode)
- remember to initialize the common controls by code in case the dialog
declares one or more common controls (and we need to link with the
comctl32.lib library as well).
When compiled and run, here is what we get :

A resizable dialog from a generic framework
3. Adding ActiveX support to WIN32 dialogs
In case you didn't know, ActiveX controls are not supported in Win32 dialogs.
If you try to drop say, the Web browser, onto a Win32 dialog, then the class
wizard aborts the process. In fact, ActiveX support requires a OLE control
container to be internally implemented, something Win32 dialogs are lacking.
Fortunately the MS guys went such that they provide an
CAxDialogImpl ATL class for us. This class is also a root WTL
class, but the good news is the Windows Template Library is not required. The
only thing we have to do from the code posted above is to replace
CDialogImpl by CAxDialogImpl.
We still need to solve the ActiveX insertion issue, since the class wizard
does not allow us to do so. There are several to solve it :
- While editing the dialog, right-click on it, select "Insert ActiveX
control", and choose one.
- Create a fake MFC dialog-based application, add one or more ActiveX controls
to a dialog, then import the resulting .rc file into your app. You are done.
- Manually grab the
CONTROL tags from the .rc file
This is enough to play with the ActiveX control, but what now if we, like in
most use cases, we need to communicate with it other than with the UI-based
property pages, and actually either :
- call one or more methods, or get one or more property values
- subscribe events
The dispatch driver wrapper provided by the MFC class wizard is of no help
since, like already said, it requires a great portion of MFC source code within
your app. What we are going to do first is import the ActiveX type-library so
that we have the public API to play with. The imported type-library is reflected
by a pair of files and are dynamically generated at compile-time, in the Debug
folder, for instance msdxm.tlh and msdxm.tli. We are
going to address two use cases :
- playing a Windows Media player video file
- navigating a URL using the web browser
Playing with the Windows Media player
Let's import the Windows Media player type-library (smart pointer wrappers)
with this code :
#import "c:\winnt\system32\msdxm.ocx"
Doing so, we are provided with the entire object model in the
Debug\msdxm.tlh + msdxm.tli files. It's such that if you take a few
minutes to browse it (I know this is rude!), you'll figure out there are
coclasses and interfaces. Coclasses are entry points when we need to create new
instances, while interfaces are binders we can rely on. On the other hand, the
ATL CAxDialogImpl inherits lower level classes, one of which
exposes the GetDlgControl(int nID, REFIID iid, /*out*/void**
ppDispatch) accessor, giving us a handy way to bind the interfaces with
the running player instance (inserted in the dialog's .rc file as described
above).
The code which does the binding is then straight forward. To set a given
video filename to play, just do the following, for instance in the dialog's
OnInitDialog() implementation :
MediaPlayer::IMediaPlayerPtr pMediaPlayer = NULL;
HRESULT hr = GetDlgControl(IDC_MEDIAPLAYER1,
__uuidof(MediaPlayer::IMediaPlayer),
(void**)&pMediaPlayer);
pMediaPlayer->FileName = _bstr_t("e:\\videos\\01.avi");
pMediaPlayer->Play();
That's all. The working code is provided in
WindowsMediaAxDialog.h.
Navigating a URL using the web browser
Using the web browser consistently is like for any other ActiveX control.
But, because it's interesting to show how to get notified of navigation events,
we are going to see how this works.
Just like the Windows Media player, insert this line of code to import the
Web browser type-libraries :
#pragma warning( disable : 4192 )
#import "c:\winnt\system32\shdocvw.dll" #import "c:\winnt\system32\mshtml.tlb"
To navigate an url, just add this code (for instance in
OnInitDialog()'s dialog method) :
SHDocVw::IWebBrowserAppPtr pWebBrowser = NULL;
HRESULT hr = GetDlgControl(IDC_EXPLORER1,
__uuidof(SHDocVw::IWebBrowserAppPtr),
(void**)&pWebBrowser);
pWebBrowser->Navigate( _bstr_t("http://www.codeproject.com") );
So far so good. Now we'd like to get notified of navigation events such like
when the web page is transferred from the web and rendered. We need to subscribe
to the web browser event source (in fact there are two,
DWebBrowserEvents and DWebBrowserEvents2 for
versioning reasons). In order to do this, we have to enumerate
IConnectionPoint interfaces (which are the technical names of event
sources), and call advise() on it. That said, we could use ATL
EventSink macros to make our lives easier. But just for the beauty of doing it
"by hand", let's do it with real code :
LPCONNECTIONPOINTCONTAINER pCPC = NULL;
LPCONNECTIONPOINT pCP = NULL;
pWebBrowser->QueryInterface(IID_IConnectionPointContainer, (LPVOID*)&pCPC);
pCPC->FindConnectionPoint(__uuidof(SHDocVw::DWebBrowserEventsPtr), &pCP);
DWORD dwCookie;
pCP->Advise((LPUNKNOWN)&m_events, &dwCookie);
m_events is a class member which implements the
DWebBrowserEvents interface, a IDispatch interface :
class CWebBrowserAxDialog : public CAxDialogImpl<CWebBrowserAxDialog>
{
protected:
DWebBrowserEventsImpl m_events;
...
}
class DWebBrowserEventsImpl : public DWebBrowserEvents
{
STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(GetTypeInfoCount)(UINT* pctinfo);
STDMETHOD(GetTypeInfo)(UINT iTInfo,
LCID lcid,
ITypeInfo** ppTInfo);
STDMETHOD(GetIDsOfNames)(REFIID riid,
LPOLESTR* rgszNames,
UINT cNames,
LCID lcid,
DISPID* rgDispId);
STDMETHOD(Invoke)(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS __RPC_FAR *pDispParams,
VARIANT __RPC_FAR *pVarResult,
EXCEPINFO __RPC_FAR *pExcepInfo,
UINT __RPC_FAR *puArgErr);
HRESULT BeforeNavigate (
_bstr_t URL,
long Flags,
_bstr_t TargetFrameName,
VARIANT * PostData,
_bstr_t Headers,
VARIANT_BOOL * Cancel );
HRESULT NavigateComplete ( _bstr_t URL );
HRESULT StatusTextChange ( _bstr_t Text );
HRESULT ProgressChange (
long Progress,
long ProgressMax );
HRESULT DownloadComplete();
HRESULT CommandStateChange (
long Command,
VARIANT_BOOL Enable );
HRESULT DownloadBegin ();
HRESULT NewWindow (
_bstr_t URL,
long Flags,
_bstr_t TargetFrameName,
VARIANT * PostData,
_bstr_t Headers,
VARIANT_BOOL * Processed );
HRESULT TitleChange ( _bstr_t Text );
HRESULT FrameBeforeNavigate (
_bstr_t URL,
long Flags,
_bstr_t TargetFrameName,
VARIANT * PostData,
_bstr_t Headers,
VARIANT_BOOL * Cancel );
HRESULT FrameNavigateComplete (
_bstr_t URL );
HRESULT FrameNewWindow (
_bstr_t URL,
long Flags,
_bstr_t TargetFrameName,
VARIANT * PostData,
_bstr_t Headers,
VARIANT_BOOL * Processed );
HRESULT Quit (
VARIANT_BOOL * Cancel );
HRESULT WindowMove ( );
HRESULT WindowResize ( );
HRESULT WindowActivate ( );
HRESULT PropertyChange (
_bstr_t Property );
CWebBrowserAxDialog *m_cpParent;
public:
void SetParent(CWebBrowserAxDialog *pParent) { m_cpParent = pParent; }
};
What remains is the implementation of the IDispatch::Invoke()
which is really the entry point for all subscribed events. By contract, it's our
job in the Invoke implementation to dispatch the event as an appropriate method
call. In the example, we have only implemented the dispatch of the
OnBeforeNavigate event, which is one of the main events programmers
are interested about. Code for the implementation is as follows :
HRESULT __stdcall DWebBrowserEventsImpl::Invoke(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS __RPC_FAR *pDispParams,
VARIANT __RPC_FAR *pVarResult,
EXCEPINFO __RPC_FAR *pExcepInfo,
UINT __RPC_FAR *puArgErr)
{
if (dispIdMember == DISPID_BEFORENAVIGATE)
{
BeforeNavigate( _bstr_t( pDispParams->rgvarg[5].bstrVal ),
0,
_bstr_t( pDispParams->rgvarg[3].bstrVal ),
NULL,
_bstr_t(""),
NULL);
}
else if (dispIdMember == DISPID_NAVIGATECOMPLETE)
{
NavigateComplete( _bstr_t( pDispParams->rgvarg[0].bstrVal ) );
}
else
{
... }
return NOERROR;
}
That's all. The working code is provided in WebBrowserAxDialog.h
and WebBrowserAxDialog.cpp.
Last but not least, here is how the get the current html document : (made
possible by importing the mshtml type-library)
HRESULT DWebBrowserEventsImpl::NavigateComplete ( _bstr_t URL )
{
SHDocVw::IWebBrowserAppPtr pWebBrowser = NULL;
HRESULT hr = m_cpParent->GetDlgControl(IDC_EXPLORER1,
__uuidof(SHDocVw::IWebBrowserAppPtr),
(void**)&pWebBrowser);
MSHTML::IHTMLDocument2Ptr doc( pWebBrowser->Document );
MSHTML::IHTMLElementPtr htmlbody( doc->body );
BSTR content = NULL;
htmlbody->get_innerHTML(&content);
_bstr_t bcontent(content);
return S_OK;
}
As a side note, there is an amazing article[^] by Dino Esposito developers often refer to when
they are trying to integrate the web browser into MFC dialogs. Dino has a hard
time trying to fit a CHtmlView class (CView derived
class) as if it was a mere dialog control. In the end, this works, although this
requires a lot of overhead, including degrading the MFC document/view model.
Update history
April 12 - initial release
April 19 - update :
- removed flickering (
WS_CLIPCHILDREN style)
- added helper macros
- added ATL7 support
Stéphane Rodriguez - April 19, 2003.