WIN32 dialog helpersResize 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 storyAs 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 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 procs that do not natively provide a way to be resized on will, and I find very poor the fact 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 boss technical decisions like using VB instead of C++, opening 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 dialogsHere is the agenda :
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 : // ATL dialogs #include <atlbase.h> extern CComModule _Module; #include <atlwin.h> // ATL ActiveX support #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); // show the dialog // CSampleDialog dlg; // resizable dialog // CSampleAxDialog dlg; // sample dialog // CWindowsMediaAxDialog dlg; // windows media player // CWebBrowserAxDialog dlg; // web browser CAboutDialog dlg; dlg.DoModal(); _Module.Term(); return 0; }And then paste the actual about dialog implementation : // About dialog : simple 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; // let the system set the focus } 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 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 Now that we have the dialog framework to play with, let's clone this code, call the dialog 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(); // allow treeview x-y resize when the dialog is resized HWND hTreeView = GetDlgItem(IDC_TREE1); { CResizableControl *pListDynamics = m_grip.AddDynamicControls(); if (pListDynamics) { pListDynamics->Init(hTreeView); pListDynamics->AllowResizeOnResize(); } } // allow OK button x-only move (no resize) when the dialog is resized HWND hOk = GetDlgItem(IDOK); { CResizableControl *pListDynamics = m_grip.AddDynamicControls(); if (pListDynamics) { pListDynamics->Init(hOk); pListDynamics->AllowMoveXOnResize(); } } ... return TRUE; // let the system set the focus } 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 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; // let the system set the focus } 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() // called by framework while resizing 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(); } // you can use the following macro instead of the code above : // DORESIZE(m_grip) return 0; } // called by framework while resizing, to allow min/max bound adjustement LRESULT OnSizing(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LPRECT pRect = (LPRECT) lParam; // min width is 150 pixels int nWidth = pRect->right - pRect->left; if ( nWidth < 150 ) pRect->right = pRect->left + 150; // min height is 150 pixels int nHeight = pRect->bottom - pRect->top; if ( nHeight < 150 ) pRect->bottom = pRect->top + 150; // you can use the following macro instead of the code above : // MINMAX(150,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 Before we compile the code, we still need to do a few things :
When compiled and run, here is what we get :
3. Adding ActiveX support to WIN32 dialogsIn 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 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 :
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 :
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
Playing with the Windows Media playerLet's import the Windows Media player type-library (smart pointer wrappers) with this code : // put the following line in your precompiled headers #import "c:\winnt\system32\msdxm.ocx" Doing so, we are provided with the entire object model in the 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 // Windows media player specific code 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 browserUsing 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 : // put the following line in your precompiled headers #pragma warning( disable : 4192 ) #import "c:\winnt\system32\shdocvw.dll" // web browser control #import "c:\winnt\system32\mshtml.tlb" // web browser dom To navigate an url, just add this code (for instance in // Web browser specific code 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, // subscribe the web browse event source 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);
class CWebBrowserAxDialog : public CAxDialogImpl What remains is the implementation of the 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) { // proces OnBeforeNavigate if (dispIdMember == DISPID_BEFORENAVIGATE) { // call BeforeNavigate // (parameters are on stack, thus on reverse order) BeforeNavigate( /*url*/ _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 { ... // implement all event handlers of interest to you } return NOERROR; } That's all. The working code is provided in 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); // get the html document 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
Update historyApril 12 - initial releaseApril 19 - update :
Stéphane Rodriguez - April 19, 2003.
|
Home Blog |