W hether you're an experienced Win32® guru or a junior Windows® wanna-be, you've got to love MFC. It removes the drudgery from Windows development by encapsulating many of the menial parts of an application without sacrificing speed. In addition, it provides a very powerful, but often misunderstood, program model called the document/view model. The basic idea behind the document/view architecture is to separate data from its visual representation—the object-oriented programming model. A side benefit of its implementation in MFC is a multitude of built-in features, including toolbars, status bars, and File Save/Open.![]() ![]() Document/View, Meet ActiveX ![]() ![]() ![]() ![]() Document/View 101: The Basics ![]() ![]() |
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_SCRIBBTYPE, RUNTIME_CLASS(CScribbleDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CScribbleView)); AddDocTemplate(pDocTemplate); ••• if (!ProcessShellCommand(cmdInfo)) return FALSE; |
The creation of a CMultiDocTemplate object basically serves two purposes:
![]() ![]() CActiveXDocTemplate and CActiveXDocControl ![]()
|
class CMyOCXCtrl : public CActiveXDocControl |
|
#include "stdafx.h" #include "ActivDoc.h" #include "MyOCX.h" #include "MyOCXCtl.h" #include "MyOCXPpg.h" |
|
AddDocTemplate(new CActiveXDocTemplate( RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CFrameWnd), RUNTIME_CLASS(CMyView))); |
![]() |
CActiveXDocTemplate::CActiveXDocTemplate(CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) : CSingleDocTemplate(IDR_NOTUSED, pDocClass, pFrameClass, pViewClass) { ASSERT(pFrameClass); } |
![]() ![]() A Look Under the Hood ![]() |
ASSERT(m_pDocTemplate); // Set in call to AddDocTemplate m_pFrameWnd = m_pDocTemplate->CreateDocViewFrame(this); ASSERT_KINDOF(CFrameWnd, m_pFrameWnd); |
The call to CActiveXDocTemplate::CreateDocViewFrame kick-starts the document, frame, and view classes in much the same way that CWinApp::ProcessShellCommand does, except that CreateDocViewFrame always calls OpenDocumentFile with a NULL pointer. The reason for this is that an ActiveX control is just a DLL on steroids, so you can't pass it a file name as a command-line argument as you would with a regular application. ![]() ![]() |
virtual CDocument* CreateNewDocument(); virtual CFrameWnd* CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther); virtual void InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc, BOOL bMakeVisible = TRUE); virtual BOOL SaveAllModified(); // for all documents virtual void CloseAllDocuments(BOOL bEndSession); virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0; virtual void SetDefaultTitle(CDocument* pDocument) = 0; |
By making these functions virtual, the classes that inherit from CDocTemplate—namely CMultiDocTemplate and CSingleDocTemplate—can have significantly different functionality without changing the way the framework deals with them. What's even better is that you can keep all the code in CSingleDocTemplate that applies to an ActiveX control and only rewrite the functions that don't apply. As it turns out, there are only two functions that you need to override, as shown in Figure 2.![]() |
void CActiveXDocTemplate::SaveDocumentFile() { if (m_pOnlyDoc != NULL) { if (!m_docFile.IsEmpty()) m_pOnlyDoc-> OnSaveDocument(m_docFile); else m_pOnlyDoc-> SetModifiedFlag(FALSE); } } |
![]() |
void CActiveXDocControl::OnDestroy() { AfxGetApp()->m_pMainWnd = NULL; m_pDocTemplate->SaveDocumentFile(); COleControl::OnDestroy(); } |
While this doesn't give your users the ability to discard any changes they've made to the current document, it seems like an appropriate choice. Users dabbling with the control while browsing your Web page won't want to have to confirm saving the control's document before being able to move to the next page! Active Scribble ![]() |
![]() |
![]() |
![]() |
void ScribbleCtrl::OnDocumentNameChanged() { GetDocTemplate()->OpenDocumentFile(m_documentName); SetModifiedFlag(); } |
![]() |
BOOL CScribbleBar::Create(CWnd* pParentWnd) { if (!CToolBar::Create(pParentWnd) || !LoadToolBar(IDR_MAINFRAME)) return FALSE; SetBarStyle(GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED); SetButtonStyle(0, TBBS_CHECKGROUP); SetButtonStyle(1, TBBS_CHECKGROUP); SetButtonStyle(2, TBBS_CHECKGROUP); SetButtonStyle(3, TBBS_CHECKGROUP); GetToolBarCtrl().CheckButton(IDB_BLACK_MARKER); return TRUE; } |
![]() |
void CScribbleDoc::OnUpdateEraser(CCmdUI* pCmdUI) { pCmdUI->Enable(!m_strokeList.IsEmpty()); } void CScribbleDoc::OnEraser() { OnNewDocument(); UpdateAllViews(NULL); } |
![]() |
// send WM_IDLEUPDATECMDUI to the main window CWnd* pMainWnd = m_pMainWnd; if (pMainWnd != NULL && pMainWnd->m_hWnd != NULL && pMainWnd->IsWindowVisible()) { AfxCallWndProc(pMainWnd, pMainWnd->m_hWnd, WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0); pMainWnd->SendMessageToDescendants(WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0, TRUE, TRUE); } |
My control wasn't receiving the WM_IDLEUPDATECMDUI message because it wasn't being sent.![]() |
void CActiveXDocControl::OnTimer(UINT nIDEvent) { // Since we're in an OCX, we don't control the message loop, // so CWinThread::OnIdle is never called. That means we have // to periodically pump the ON_UPDATE_COMMAND_UI messages // by hand. SendMessageToDescendants(WM_IDLEUPDATECMDUI, TRUE); COleControl::OnTimer(nIDEvent); } |
![]() |
LRESULT CScribbleBar::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { MSG msg; msg.hwnd = m_hWnd; msg.message = message; msg.wParam = wParam; msg.lParam = lParam; GetCursorPos(&msg.pt); FilterToolTipMessage(&msg); return CToolBar::WindowProc(message,wParam,lParam); } |
Looking Ahead ![]() Conclusion ![]() |