Introduction
A client recently asked me for a component that could be used in a Win32 application,
as well as embedded in a web page. My solution was to write an ActiveX control
that I could embed in a CDialog
for the Win32 app and use the <OBJECT>
tag in IE to embed the control in a web page. When they asked me if I could
do docking windows like in Visual Studio, that lead me to The
Code Project, where I found Cristi Posea's excellent docking window code.
There was one problem, though - the docking code relied on having a CFrameWnd
to dock to. I made the assumption that CFrameWnd
could only be a top-level window
with a title bar, but that turns out not to be true. CFrameWnd
can be created
with the WS_CHILD
style, rather than WS_OVERLAPPEDWINDOW
(the default) and you
can make it a child of any other window, including an ActiveX control!
The result is a control that allows you to have docking windows, even inside
Internet Explorer. I've included and HTML file called index.htm to illustrate
this, which you can open in IE after building the control.
Additionally, I have also figured out how to embed ActiveX controls in the
docking windows themselves, so you can now have ActiveX controls inside ActiveX
controls, etc. My example code uses the ActiveMovie control.
Here’s what you need to do:
- derive your own class from
CFrameWnd
(ie. CEmbeddedFrame
)
- derive a class from the docking window code (I used Cristi Posea’s
CMyBar
as a basis). I called it CDockingWnd
.
- create a member variable in the control of your frame class
CEmbeddedFrame m_Frame;
- call
Create()
on the frame in the OnCreate()
override of your control.
int CDockCtrlCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
CRect r( 0, 0, 100, 100 );
if( !m_Frame.Create( NULL, _T("Embedded Frame"),
WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN, r,
this,
NULL,
NULL,
NULL ) )
{
TRACE( "**** Creation of embedded frame failed\n" );
return -1;
}
return 0;
}
- Add member variables to the frame for the client area object and the docking
window
CEdit m_wndClient;
CDockingWnd m_wndDockingWnd;
- In
OnCreate()
of the frame control, create the docking window and any other
client-area controls you need, then dock the docking windows as you see fit.
int CEmbeddedFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
SetTimer(0, 100, NULL);
m_wndClient.Create( WS_VISIBLE|WS_CHILD,
CRect(0,0,0,0),
this,
AFX_IDW_PANE_FIRST );
m_wndClient.ModifyStyleEx(0, WS_EX_CLIENTEDGE);
if (!m_font.CreateStockObject(DEFAULT_GUI_FONT))
if (!m_font.CreatePointFont(80, "MS Sans Serif"))
return -1;
m_wndClient.SetFont(&m_font);
m_wndClient.SetWindowText(
_T( "This window is a child of the frame." ) );
if (!m_wndDockingWnd.Create(_T("Docking Window"), this,
IDC_DOCKING_WND))
{
TRACE0( "Failed to create Docking Windown" );
return -1;
}
m_wndDockingWnd.SetBarStyle(m_wndDockingWnd.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
EnableDocking(CBRS_ALIGN_ANY);
#ifdef _SCB_REPLACE_MINIFRAME
m_pFloatingFrameClass = RUNTIME_CLASS(CSCBMiniDockFrameWnd);
#endif //_SCB_REPLACE_MINIFRAME
m_wndDockingWnd.EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndDockingWnd, AFX_IDW_DOCKBAR_LEFT);
return 0;
}
- In the
OnCreate()
handler of your docking window, create the control(s)
you want to be children of the docking window. My sample code shows how to create
a simple edit control or how to embed ActiveX controls in your docking window.
int CDockingWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (baseCDockingWnd::OnCreate(lpCreateStruct) == -1)
return -1;
SetSCBStyle(GetSCBStyle() | SCBS_SHOWEDGES | SCBS_SIZECHILD);
#ifdef _USE_EDIT_CTRL_
if (!m_wndChild.Create( WS_CHILD|WS_VISIBLE|
ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL,
CRect(0,0,0,0),
this,
IDC_FRAME_EDIT) )
return -1;
m_wndChild.ModifyStyleEx(0, WS_EX_CLIENTEDGE);
if (!m_font.CreateStockObject(DEFAULT_GUI_FONT))
if (!m_font.CreatePointFont(80, "MS Sans Serif"))
return -1;
m_wndChild.SetFont(&m_font);
m_wndChild.SetWindowText(
_T( "This docking window is embedded in the control" ) );
#else // _USE_ACTIVE_MOVIE_CTRL_
AfxEnableControlContainer();
if( !m_AMControl.CreateControl( _T("AMOVIE.ActiveMovie Control.2"),
_T("Active Movie Ctrl"),
WS_CHILD | WS_VISIBLE,
CRect(0,0, 50, 50),
this,
IDC_ACTIVE_MOVIE_CTRL ) )
{
TRACE( "Unable to create Active Movie control. "
"GetLastError() == %dn", GetLastError() );
return -1;
}
IUnknownPtr pUnk = m_AMControl.GetControlUnknown();
if( pUnk )
{
pUnk->QueryInterface( IID_IMediaPlayer,
(void**)&m_pMediaPlayer );
if( m_pMediaPlayer )
{
}
}
#endif // _USE_ACTIVE_MOVIE_CTRL_
return 0;
}
- In your control’s
OnSize()
handler, make CEmbeddedFrame
as big as the window
(of course, you don’t have to, but I will make this assumption here).
- You also have to simulate the
WM_IDLEUPDATECMDUI
behavior that the MFC doc-view
architecture gives you. This is the mechanism in doc-view that updates the state
of toolbar buttons and deletes temporary objects from memory when the app isn't
busy. The docking window code uses a lot of calls to DelayRecalcLayout()
, where
the RecalcLayout()
call for the CMiniFrameWnd
of the docking window is
deferred until idle processing. If you don't do this, the frame and the control will
not get updated properly. In the ActiveX control, we fake it with the timer
we set in CEmbeddedFrame::OnCreate()
. Again, I wish to thank Cristi for his
help on this behavior.
void CEmbeddedFrame::OnTimer(UINT nIDEvent)
{
CFrameWnd::OnTimer(nIDEvent);
if (nIDEvent != 0)
return;
if (m_hWnd != NULL)
{
if (m_nShowDelay == SW_HIDE)
ShowWindow(m_nShowDelay);
if (IsWindowVisible() || m_nShowDelay >= 0)
{
AfxCallWndProc(this, m_hWnd,
WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);
SendMessageToDescendants( WM_IDLEUPDATECMDUI,
(WPARAM)TRUE,
0,
TRUE,
TRUE);
}
if (m_nShowDelay > SW_HIDE)
ShowWindow(m_nShowDelay);
m_nShowDelay = -1;
POSITION pos = m_listControlBars.GetHeadPosition();
while (pos != NULL)
{
CControlBar* pBar =
(CControlBar*) m_listControlBars.GetNext(pos);
ASSERT(pBar != NULL);
if (pBar->m_hWnd == NULL ||
pBar->GetDlgCtrlID() != AFX_IDW_DOCKBAR_FLOAT)
continue;
CFrameWnd* pFrameWnd = pBar->GetParentFrame();
if (pFrameWnd->m_hWnd != NULL && pFrameWnd != this)
{
if (pFrameWnd->m_nShowDelay == SW_HIDE)
pFrameWnd->ShowWindow(pFrameWnd->m_nShowDelay);
if (pFrameWnd->IsWindowVisible()
|| pFrameWnd->m_nShowDelay >= 0)
{
AfxCallWndProc(pFrameWnd, pFrameWnd->m_hWnd,
WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);
pFrameWnd->SendMessageToDescendants(
WM_IDLEUPDATECMDUI,
(WPARAM)TRUE, 0,
TRUE, TRUE);
}
if (pFrameWnd->m_nShowDelay > SW_HIDE)
pFrameWnd->ShowWindow(pFrameWnd->m_nShowDelay);
pFrameWnd->m_nShowDelay = -1;
}
}
bool bActive = (GetTopLevelParent() == GetForegroundWindow());
if (bActive != m_bActive)
{
NotifyFloatingWindows(bActive ? FS_ACTIVATE : FS_DEACTIVATE);
m_bActive = bActive;
}
}
}
If you have any questions on embedding docking windows inside ActiveX controls,
please don’t hesitate to email me at docking_windows@intelligentsystems.net.
Any problems with the behavior of the docking windows should be directed to
Cristi Posea (cristi@datamekanix.com).
Greg Winkler
Intelligent Systems Inc.
Greg is a Microsoft Certified developer and runs Intelligent Systems, Inc, a consulting company specializing in user interface design and progamming in Visual C++.