ActiveX控件使用SDI特性(上)

大家都知道,使用微软的MFC ActiveX控件向导生成的控件类,是基于COleControl的,而COleControl是基于CWnd类。这样很多MFC的特性,如CFrameWnd的特性,CSingleDocTemplate(SDI)的特性等就无法使用了。如果要把用到这些类的MFC程序封装为ActiveX插件,就会有不少麻烦。

其实这个问题,在上个世纪MFC诞生不久,就有人解决了,并提供了完整的示例代码。微软官方的这篇文章《Designing ActiveX Components with the MFC Document/View Model》,在原地址http://www.microsoft.com/mind/0497/mfc.asp已经无法找到了。

但好在这里还有完整的备份《Designing ActiveX Components with the MFC Document/View Model》

原文的作者,实现了CActiveXDocTemplate和CActiveXDocControl两个类,而用户只需要将控件的基类从COleControl换为CActiveXDocControl,并做简单修改,就可以方便的使用SDI的特性了。

整体思路其实是使用CActiveXDocControl扩展了COleControl。在CActiveXDocControl中,作者初始化了CDocManager,使用CActiveXDocTemplate控制了document、view、frame的生成,处理了OnSize事件,并使用Timer定时获取消息处理更新。

我们使用的时候,主要就是用这两个类,代码如下(两个类相互引用,所以放到了同一文件中):

1、ActivDoc.h

class CActiveXDocTemplate : public CSingleDocTemplate
{
    enum { IDR_NOTUSED = 0x7FFF };

    CWnd* m_pParentWnd;
    CFrameWnd* m_pFrameWnd;
    CString m_docFile;

public:
    CActiveXDocTemplate(CRuntimeClass* pDocClass,
        CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);

    CFrameWnd* CreateDocViewFrame(CWnd* pParentWnd);
    void SaveDocumentFile();

    virtual CFrameWnd* CreateNewFrame(CDocument* pDoc,
        CFrameWnd* pOther);
    virtual CDocument* OpenDocumentFile(
        LPCTSTR lpszPathName, BOOL bVerifyExists = TRUE);
};

//===================================================

class CActiveXDocControl : public COleControl
{
    enum { WM_IDLEUPDATECMDUI = 0x0363 };

    static BOOL m_bDocInitialized;
    CActiveXDocTemplate* m_pDocTemplate;
    CFrameWnd* m_pFrameWnd;

    DECLARE_DYNAMIC(CActiveXDocControl)

protected:
    void AddDocTemplate(CActiveXDocTemplate* pDocTemplate);
    CDocTemplate* GetDocTemplate() { return m_pDocTemplate; }

    //{{AFX_MSG(CActiveXDocControl)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnTimer(UINT nIDEvent);
    afx_msg void OnDestroy();
    //}}AFX_MSG
    //{{AFX_DISPATCH(CActiveXDocControl)
    //}}AFX_DISPATCH
    //{{AFX_EVENT(CActiveXDocControl)
    //}}AFX_EVENT

    DECLARE_MESSAGE_MAP()
    DECLARE_DISPATCH_MAP()
    DECLARE_EVENT_MAP()

public:
    CActiveXDocControl();
    virtual ~CActiveXDocControl();

    enum {
    //{{AFX_DISP_ID(CActiveXDocControl)
    //}}AFX_DISP_ID
    };
};

2、ActivDoc.cpp

// ActivDoc.cpp : implementation file
//

#include "stdafx.h"
#include "ActivDoc.h"

CActiveXDocTemplate::CActiveXDocTemplate(CRuntimeClass* pDocClass,
    CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)
        : CSingleDocTemplate(IDR_NOTUSED, pDocClass, pFrameClass,
            pViewClass)
{
    ASSERT(pFrameClass);
}

CFrameWnd* CActiveXDocTemplate::CreateDocViewFrame(CWnd* pParentWnd)
{
    ASSERT(pParentWnd && IsWindow(*pParentWnd));
    ASSERT_KINDOF(CActiveXDocControl, pParentWnd);
    m_pParentWnd = pParentWnd;
    m_pFrameWnd = NULL;

    // OpenDocumentFile is a virtual method (implemented in
    // the CSingleDocTemplate base class) that creates
    // the document (either empty or loaded from the specified
    // file) and calls our CreateNewFrame function. Passing
    // NULL to the function creates a new document. Incidentally,
    // the view is created by the CFrameWnd's OnCreateClient()
    // method.

    if (!OpenDocumentFile(NULL))
        return NULL;

    // Since OpenDocumentFile sets m_pFrame, we can now use it.

    ASSERT(m_pFrameWnd);
    ASSERT_KINDOF(CFrameWnd, m_pFrameWnd);
    m_pFrameWnd->ShowWindow(SW_SHOWNORMAL);
    return m_pFrameWnd;
}

CFrameWnd* CActiveXDocTemplate::CreateNewFrame(CDocument* pDoc,
        CFrameWnd* pOther)
{
    ASSERT(pOther == NULL);
    ASSERT(m_pFrameClass != NULL);
    if (pDoc != NULL)
        ASSERT_VALID(pDoc);

    // Create a frame wired to the specified document

    CCreateContext context;
    context.m_pCurrentFrame = pOther;
    context.m_pCurrentDoc = pDoc;
    context.m_pNewViewClass = m_pViewClass;
    context.m_pNewDocTemplate = this;

    m_pFrameWnd = (CFrameWnd*)m_pFrameClass->CreateObject();
    if (m_pFrameWnd == NULL)
    {
        TRACE1("Warning: Dynamic create of frame %hs failed.\n",
            m_pFrameClass->m_lpszClassName);
        return NULL;
    }
    ASSERT_KINDOF(CFrameWnd, m_pFrameWnd);

    if (context.m_pNewViewClass == NULL)
        TRACE0("Warning: creating frame with no default view.\n");

    // The frame is created as a menu-less child of the
    // CActiveXDocControl in which it will reside.

    ASSERT_KINDOF(CActiveXDocControl, m_pParentWnd);
    if (!m_pFrameWnd->Create(NULL, "", WS_CHILD|WS_VISIBLE,
        CFrameWnd::rectDefault, m_pParentWnd, NULL, 0, &context))
    {
        TRACE0("Warning: CDocTemplate couldn't create a frame.\n");
        return NULL;
    }

    return m_pFrameWnd;
}

CDocument* CActiveXDocTemplate::OpenDocumentFile(
    LPCTSTR lpszPathName, BOOL bVerifyExists)
{
    SaveDocumentFile();
    m_docFile = lpszPathName;

    if (bVerifyExists)
    {
        DWORD dwAttrib = GetFileAttributes(m_docFile);
        if (dwAttrib == 0xFFFFFFFF ||
            dwAttrib == FILE_ATTRIBUTE_DIRECTORY)
        {
            lpszPathName = NULL;
        }
    }

    return CSingleDocTemplate::OpenDocumentFile(
        lpszPathName, TRUE);
}

void CActiveXDocTemplate::SaveDocumentFile()
{
    if (m_pOnlyDoc != NULL)
    {
        if (!m_docFile.IsEmpty())
            m_pOnlyDoc->OnSaveDocument(m_docFile);
        else
            m_pOnlyDoc->SetModifiedFlag(FALSE);
    }
}

//===================================================
// CActiveXDocControl

IMPLEMENT_DYNAMIC(CActiveXDocControl, COleControl)
BEGIN_MESSAGE_MAP(CActiveXDocControl, COleControl)
    //{{AFX_MSG_MAP(CActiveXDocControl)
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_WM_TIMER()
    ON_WM_DESTROY()
    //}}AFX_MSG_MAP
    ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CActiveXDocControl, COleControl)
    //{{AFX_DISPATCH_MAP(CActiveXDocControl)
    //}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
BEGIN_EVENT_MAP(CActiveXDocControl, COleControl)
    //{{AFX_EVENT_MAP(COleFrameCtrl)
    //}}AFX_EVENT_MAP
END_EVENT_MAP()

int CActiveXDocControl::m_bDocInitialized = FALSE;

CActiveXDocControl::CActiveXDocControl()
{
    m_pDocTemplate = NULL;
    m_pFrameWnd = NULL;

    // Since we're in an OCX, CWinApp::InitApplication() is
    // not called by the framework. Unfortunately, that method
    // performs CDocManager initialization that is necessary
    // in order for our DocTemplate to clean up after itself.
    // We simulate the same initialization by calling the
    // following code the first time we create a control.

    if (!m_bDocInitialized)
    {
        CDocManager docManager;
        docManager.AddDocTemplate(NULL);
        m_bDocInitialized = TRUE;
    }
}

CActiveXDocControl::~CActiveXDocControl()
{
    // Note that the frame, the document, and the view are
    // all deleted automatically by the framework!
    
    delete m_pDocTemplate;
}

void CActiveXDocControl::AddDocTemplate(CActiveXDocTemplate* pDocTemplate)
{
    // I've decided to call this function AddDocTemplate to
    // be consistent with naming of CWinApp::AddDocTemplate.
    // However, only one DocTemplate is allowed per control.
    
    ASSERT(pDocTemplate);
    ASSERT(m_pDocTemplate == NULL);
    m_pDocTemplate = pDocTemplate;
}

int CActiveXDocControl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (COleControl::OnCreate(lpCreateStruct) == -1)
        return -1;

    // The CActiveXDocTemplate object will create the
    // document, the view, and the frame inside the
    // control. The reason we need a handle to the frame
    // is so that we can resize it when the control is
    // resized.
    
    ASSERT(m_pDocTemplate);    // Set in call to AddDocTemplate
    m_pFrameWnd = m_pDocTemplate->CreateDocViewFrame(this);
    ASSERT_KINDOF(CFrameWnd, m_pFrameWnd);

    // By default, we'll create the control with a border,
    // since it looks better for frames containing a toolbar.

    SetBorderStyle(TRUE);
    SetTimer(WM_IDLEUPDATECMDUI, 300, NULL);
    return 0;
}

void CActiveXDocControl::OnSize(UINT nType, int cx, int cy) 
{
    COleControl::OnSize(nType, cx, cy);

    // The CFrameWnd should always fill up the entire client
    // area of the control.

    if (m_pFrameWnd != NULL)
    {
        ASSERT(IsWindow(*m_pFrameWnd));
        CRect area;
        GetClientRect(area);
        m_pFrameWnd->MoveWindow(area.left, area.top, area.right,
            area.bottom);
    }
}

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);
}


void CActiveXDocControl::OnDestroy() 
{
    AfxGetApp()->m_pMainWnd = NULL;
    m_pDocTemplate->SaveDocumentFile();
    COleControl::OnDestroy();
}

困了,回去再写。。。

Comments are closed.