TN041:MFC/OLE1 迁移到 MFC/OLE 2

注释

自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。

与迁移相关的一般问题

MFC 2.5 及更高版本 OLE 2 类的设计目标之一是保留 MFC 2.0 中为 OLE 1.0 支持的相同体系结构。 因此,MFC 2.0 中的许多相同的 OLE 类仍存在于此版本的 MFC(COleDocumentCOleServerDocCOleClientItemCOleServerItem) 中。 此外,这些类中的许多 API 完全相同。 但是,OLE 2 与 OLE 1.0 大相径庭,因此可以预期某些详细信息已更改。 如果你熟悉 MFC 2.0 的 OLE1 支持,你将拥有 MFC 的 2.0 支持。

如果要获取现有的 MFC/OLE1 应用程序并向其添加 OLE 2 功能,则应先阅读此说明。 此说明介绍了将 OLE1 功能移植到 MFC/OLE 2 时可能会遇到的一些常规问题,然后讨论移植 MFC 2.0 中包含的两个应用程序时发现的问题:MFC OLE 示例 OCLIENTHIERSVR

MFC 文档/视图体系结构非常重要

如果应用程序不使用 MFC 的文档/视图体系结构,并且想要向应用程序添加 OLE 2 支持,现在是移动到文档/视图的时候了。 MFC 的 OLE 2 类的许多优点只有在应用程序使用 MFC 的内置体系结构和组件后才实现。

无需使用 MFC 体系结构即可实现服务器或容器,但不建议这样做。

使用 MFC 实现而不是你自己的实现

MFC“罐装实现”类(例如 CToolBarCStatusBarCScrollView 具有内置特殊用例代码,用于 OLE 2 支持)。 因此,如果你可以在应用程序中使用这些类,你将受益于投入到这些类中,使其能够识别 OLE。 同样,出于这些目的,可以在此处“滚动自己的”类,但不建议使用。 如果需要实现类似的功能,MFC 源代码是处理 OLE 的某些更精细点(尤其是在就地激活方面)的优秀参考。

检查 MFC 示例代码

有许多 MFC 示例包括 OLE 功能。 其中每个应用程序都从不同的角度实现 OLE:

  • HIERSVR 主要用于用作服务器应用程序。 它作为 MFC/OLE1 应用程序包含在 MFC 2.0 中,并已移植到 MFC/OLE 2,然后进行了扩展,以便它实现 OLE 2 中可用的许多 OLE 功能。

  • OCLIENT 这是一个独立容器应用程序,旨在从容器的角度演示许多 OLE 功能。 它还从 MFC 2.0 移植,然后进行了扩展以支持许多更高级的 OLE 功能,例如自定义剪贴板格式和嵌入项的链接。

  • DRAWCLI 此应用程序实现的 OLE 容器支持与 OCLIENT 非常类似,不同之处在于它在现有面向对象的绘图程序的框架中这样做。 其中介绍了如何实现 OLE 容器支持并将其集成到现有应用程序中。

  • SUPERPAD 此应用程序也是一个 OLE 服务器,也是一个优秀的独立应用程序。 它实现的服务器支持非常极简。 特别感兴趣的是,它如何使用 OLE 剪贴板服务将数据复制到剪贴板,但使用 Windows“编辑”控件中内置的功能来实现剪贴板粘贴功能。 这显示了传统 Windows API 用法的有趣组合,以及与新的 OLE API 的集成。

有关示例应用程序的详细信息,请参阅“MFC 示例帮助”。

案例研究:MFC 2.0 中的 OCLIENT

如上所述, OCLIENT 包含在 MFC 2.0 中,并使用 MFC/OLE1 实现 OLE。 下面介绍了最初将此应用程序转换为使用 MFC/OLE 2 类的步骤。 完成初始端口后添加了许多功能,以更好地说明 MFC/OLE 类。 此处不会介绍这些功能;有关这些高级功能的详细信息,请参阅示例本身。

注释

编译器错误和分步过程是使用 Visual C++ 2.0 创建的。 Visual C++ 4.0 可能会更改特定的错误消息和位置,但概念信息仍然有效。

启动并运行

将 OCLIENT 示例移植到 MFC/OLE 的方法首先生成并修复将导致的明显编译器错误。 如果从 MFC 2.0 获取 OCLIENT 示例并将其编译在此版本的 MFC 下,你会发现没有那么多的错误要解决。 下面描述了错误发生的顺序。

编译和修复错误

\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters

第一个错误问题 COleClientItem::Draw。 在 MFC/OLE1 中,它占用的参数比 MFC/OLE 版本占用的参数多。 额外的参数通常不是必需的,通常为 NULL(如本例所示)。 当绘制到的 CDC 是图元文件 DC 时,此版本的 MFC 可以自动确定 lpWBounds 的值。 此外,不再需要 pFormatDC 参数,因为框架将从传入的 pDC 的“属性 DC”生成一个参数。 因此,若要解决此问题,只需删除 Draw 调用的两个额外的 NULL 参数。

\oclient\mainview.cpp(273) : error C2065: 'OLE_MAXNAMESIZE' : undeclared identifier
\oclient\mainview.cpp(273) : error C2057: expected constant expression
\oclient\mainview.cpp(280) : error C2664: 'CreateLinkFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(286) : error C2664: 'CreateFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(288) : error C2664: 'CreateStaticFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '

上述错误是由于 MFC/OLE1 中的所有 COleClientItem::CreateXXXX 函数都要求传递唯一名称来表示项。 这是基础 OLE API 的要求。 MFC/OLE 2 中不需要这样做,因为 OLE 2 不使用 DDE 作为基础通信机制(DDE 对话中使用了名称)。 若要解决此问题,可以删除函数 CreateNewName 以及对其的所有引用。 只需将光标放在调用上并按 F1,即可轻松了解此版本中每个 MFC/OLE 函数的预期内容。

另一个明显不同的区域是 OLE 2 剪贴板处理。 使用 OLE1 时,你使用了 Windows 剪贴板 API 与剪贴板交互。 使用 OLE 2 时,这是使用不同的机制完成的。 MFC/OLE1 API 假定剪贴板在将对象复制到 COleClientItem 剪贴板之前处于打开状态。 这不再需要,将导致所有 MFC/OLE 剪贴板作失败。 编辑代码以删除依赖项 CreateNewName时,还应删除打开并关闭 Windows 剪贴板的代码。

\oclient\mainview.cpp(332) : error C2065: 'AfxOleInsertDialog' : undeclared identifier
\oclient\mainview.cpp(332) : error C2064: term does not evaluate to a function
\oclient\mainview.cpp(344) : error C2057: expected constant expression
\oclient\mainview.cpp(347) : error C2039: 'CreateNewObject' : is not a member of 'CRectItem'

这些错误由 CMainView::OnInsertObject 处理程序导致。 处理“插入新对象”命令是另一个方面发生了很大变化的区域。 在这种情况下,只需将原始实现与 AppWizard 为新的 OLE 容器应用程序提供的原始实现进行合并是最简单的。 事实上,这是一种可以应用于移植其他应用程序的技术。 在 MFC/OLE1 中,通过调用 AfxOleInsertDialog 函数显示“插入对象”对话框。 在此版本中,将构造一个 COleInsertObject 对话框对象并调用 DoModal。 此外,使用 CLSID 而不是类名字符串创建新的 OLE 项。 最终结果应如下所示

COleInsertDialog dlg;
if (dlg.DoModal() != IDOK)
    return;

BeginWaitCursor();

CRectItem* pItem = NULL;
TRY
{
    // First create the C++ object
    pItem = GetDocument()->CreateItem();
    ASSERT_VALID(pItem);

    // Initialize the item from the dialog data.
    if (!dlg.CreateItem(pItem))
        AfxThrowMemoryException();
            // any exception will do
    ASSERT_VALID(pItem);

    // run the object if appropriate
    if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)
        pItem->DoVerb(OLEIVERB_SHOW, this);

    // update right away
    pItem->UpdateLink();
    pItem->UpdateItemRectFromServer();

    // set selection to newly inserted item
    SetSelection(pItem);
    pItem->Invalidate();
}
CATCH (CException, e)
{
    // clean up item
    if (pItem != NULL)
        GetDocument()->DeleteItem(pItem);

    AfxMessageBox(IDP_FAILED_TO_CREATE);
}
END_CATCH

EndWaitCursor();

注释

插入新对象可能不同于应用程序):

还需要包括 <afxodlgs.h>,其中包含对话类的 COleInsertObject 声明以及 MFC 提供的其他标准对话。

\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters

这些错误是由于某些 OLE1 常量在 OLE 2 中发生了更改,尽管在概念上相同。 在这种情况下 OLEVERB_PRIMARY ,已更改为 OLEIVERB_PRIMARY。 在 OLE1 和 OLE 2 中,当用户双击某个项时,通常由容器执行主谓词。

此外, DoVerb 现在采用额外的参数 - 指向视图的指针(CView*)。 此参数仅用于实现“视觉编辑”(或就地激活)。 现在,请将该参数设置为 NULL,因为目前未实现此功能。

若要确保框架从未尝试就地激活,应按如下所示替代 COleClientItem::CanActivate

BOOL CRectItem::CanActivate()
{
    return FALSE;
}
\oclient\rectitem.cpp(53) : error C2065: 'GetBounds' : undeclared identifier
\oclient\rectitem.cpp(53) : error C2064: term does not evaluate to a function
\oclient\rectitem.cpp(84) : error C2065: 'SetBounds' : undeclared identifier
\oclient\rectitem.cpp(84) : error C2064: term does not evaluate to a function

在 MFC/OLE1 中, COleClientItem::GetBoundsSetBounds 用于查询和作项的范围( lefttop 成员始终为零)。 在 MFC/OLE 2 中,它更直接地受COleClientItem::GetExtentSetExtent支持,它处理 SIZECSize相反。

新的 SetItemRectToServer 和 UpdateItemRectFromServer 调用的代码如下所示:

BOOL CRectItem::UpdateItemRectFromServer()
{
    ASSERT(m_bTrackServerSize);
    CSize size;
    if (!GetExtent(&size))
        return FALSE;    // blank

    // map from HIMETRIC to screen coordinates
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.LPtoDP(&size);
    }
    // just set the item size
    if (m_rect.Size() != size)
    {
        // invalidate the old size/position
        Invalidate();
        m_rect.right = m_rect.left + size.cx;
        m_rect.bottom = m_rect.top + size.cy;
        // as well as the new size/position
        Invalidate();
    }
    return TRUE;
}

BOOL CRectItem::SetItemRectToServer()
{
    // set the official bounds for the embedded item
    CSize size = m_rect.Size();
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.DPtoLP(&size);
    }
    TRY
    {
        SetExtent(size);    // may do a wait
    }
    CATCH(CException, e)
    {
        return FALSE;  // links will not allow SetBounds
    }
    END_CATCH
    return TRUE;
}
\oclient\frame.cpp(50) : error C2039: 'InWaitForRelease' : is not a member of 'COleClientItem'
\oclient\frame.cpp(50) : error C2065: 'InWaitForRelease' : undeclared identifier
\oclient\frame.cpp(50) : error C2064: term does not evaluate to a function

在 MFC/OLE1 同步 API 调用中,从容器到服务器的同步 API 调用是 模拟的,因为在许多情况下,OLE1 本质上是异步的。 在处理用户的命令之前,必须检查正在进行的未完成异步调用。 MFC/OLE1 提供了 COleClientItem::InWaitForRelease 用于执行此作的函数。 在 MFC/OLE 2 中,不需要这样做,因此可以一起删除 CMainFrame 中 OnCommand 的重写。

此时,OCLIENT 将编译和链接。

其他必要的更改

但是,没有完成的一些作将使 OCLIENT 无法运行。 最好现在而不是以后解决这些问题。

首先,需要初始化 OLE 库。 这是通过从以下调用AfxOleInitInitInstance完成的:

if (!AfxOleInit())
{
    AfxMessageBox("Failed to initialize OLE libraries");
    return FALSE;
}

最好检查虚拟函数是否有参数列表更改。 一个此类函数是在 COleClientItem::OnChange每个 MFC/OLE 容器应用程序中重写的。 通过查看联机帮助,你将看到添加了额外的“DWORD dwParam”。 新的 CRectItem::OnChange 如下所示:

void
CRectItem::OnChange(OLE_NOTIFICATION wNotification, DWORD dwParam)
{
    if (m_bTrackServerSize && !UpdateItemRectFromServer())
    {
        // Blank object
        if (wNotification == OLE_CLOSED)
        {
            // no data received for the object - destroy it
            ASSERT(!IsVisible());
            GetDocument()->DeleteItem(this);
            return; // no update (item is gone now)
        }
    }
    if (wNotification != OLE_CLOSED)
        Dirty();
    Invalidate();
    // any change will cause a redraw
}

在 MFC/OLE1 中,容器应用程序从中 COleClientDoc派生文档类。 在 MFC/OLE 2 中,此类已被删除并替换为 COleDocument (此新组织使生成容器/服务器应用程序更容易)。 有一个 #define 映射到COleClientDocCOleDocument简化将 MFC/OLE1 应用程序移植到 MFC/OLE 2,例如 OCLIENT。 未提供COleDocumentCOleClientDoc的功能之一是标准命令消息映射条目。 这样,服务器应用程序(也间接使用 COleDocument )就不会携带这些命令处理程序的开销,除非它们是容器/服务器应用程序。 需要将以下条目添加到 CMainDoc 消息映射:

ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdatePasteMenu)
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE_LINK, OnUpdatePasteLinkMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS, OnUpdateEditLinksMenu)
ON_COMMAND(ID_OLE_EDIT_LINKS, COleDocument::OnEditLinks)
ON_UPDATE_COMMAND_UI(ID_OLE_VERB_FIRST, OnUpdateObjectVerbMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_CONVERT, OnUpdateObjectVerbMenu)
ON_COMMAND(ID_OLE_EDIT_CONVERT, OnEditConvert)

所有这些命令的实现位于文档的基类中 COleDocument

此时,OCLIENT 是一个功能 OLE 容器应用程序。 可以插入任何类型的项(OLE1 或 OLE 2)。 由于未实现启用就地激活所需的代码,因此项目在单独的窗口中编辑得非常类似于 OLE1。 下一部分讨论启用就地编辑所需的更改(有时称为“视觉编辑”)。

添加“视觉编辑”

OLE 最有趣的功能之一是就地激活(或“视觉编辑”)。 此功能允许服务器应用程序接管容器用户界面的某些部分,为用户提供更无缝的编辑界面。 若要实现对 OCLIENT 的就地激活,需要添加一些特殊资源,以及一些额外的代码。 这些资源和代码通常由 AppWizard 提供,事实上,此处的大部分代码都是直接从具有“容器”支持的全新 AppWizard 应用程序借来的。

首先,当有一个处于就地活动状态的项时,必须添加要使用的菜单资源。 可以通过复制IDR_OCLITYPE资源并删除除“文件和窗口”弹出窗口外的所有菜单资源,在 Visual C++ 中创建此额外的菜单资源。 在“文件和窗口”弹出窗口之间插入两个分隔条,以指示组的分离(应如下所示: File || Window 有关这些分隔符的含义以及如何合并服务器和容器菜单的详细信息,请参阅 菜单和资源:菜单合并

创建这些菜单后,需要让框架了解它们。 这是通过在 InitInstance 中将其添加到文档模板列表之前调用 CDocTemplate::SetContainerInfo 文档模板来完成的。 用于注册文档模板的新代码如下所示:

CDocTemplate* pTemplate = new CMultiDocTemplate(
    IDR_OLECLITYPE,
    RUNTIME_CLASS(CMainDoc),
    RUNTIME_CLASS(CMDIChildWnd), // standard MDI child frame
    RUNTIME_CLASS(CMainView));

pTemplate->SetContainerInfo(IDR_OLECLITYPE_INPLACE);

AddDocTemplate(pTemplate);

IDR_OLECLITYPE_INPLACE资源是在 Visual C++ 中创建的特殊就地资源。

若要启用就地激活,需要在 (CMainView) 派生类和CView派生类 (CRectItem) 中COleClientItem更改一些内容。 所有这些替代都由 AppWizard 提供,大部分实现将直接来自默认 AppWizard 应用程序。

在此端口的第一步中,通过重写 COleClientItem::CanActivate完全禁用就地激活。 应删除此替代以允许就地激活。 此外,NULL 已传递给所有调用 DoVerb (其中两个调用),因为提供视图只是就地激活所必需的。 若要完全实现就地激活,必须在调用中 DoVerb 传递正确的视图。 其中一个调用位于 CMainView::OnInsertObject

pItem->DoVerb(OLEIVERB_SHOW, this);

另一个是:CMainView::OnLButtonDblClk

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

必须重写 COleClientItem::OnGetItemPosition。 这会告知服务器在项就地激活时,将窗口相对于容器窗口放置的位置。 对于 OCLIENT,实现是微不足道的:

void CRectItem::OnGetItemPosition(CRect& rPosition)
{
    rPosition = m_rect;
}

大多数服务器还实现所谓的“就地调整大小”。这允许在用户编辑项目时调整和移动服务器窗口的大小。 容器必须参与此作,因为移动或调整窗口的大小通常会影响容器文档本身中的位置和大小。 OCLIENT 的实现将m_rect维护的内部矩形与新位置和大小同步。

BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
    ASSERT_VALID(this);

    if (!COleClientItem::OnChangeItemPosition(rectPos))
        return FALSE;

    Invalidate();
    m_rect = rectPos;
    Invalidate();
    GetDocument()->SetModifiedFlag();

    return TRUE;
}

此时,有足够的代码允许项目就地激活,并在项目处于活动状态时处理大小调整和移动项目,但任何代码都不允许用户退出编辑会话。 尽管某些服务器通过处理转义键来提供此功能本身,但建议容器提供两种方法来停用项:(1)通过单击项外部,然后按 ESCAPE 键(2)。

对于 ESCAPE 键,请使用 Visual C++ 添加一个加速器,用于将VK_ESCAPE键映射到命令,ID_CANCEL_EDIT添加到资源。 此命令的处理程序如下所示:

// The following command handler provides the standard
// keyboard user interface to cancel an in-place
// editing session.void CMainView::OnCancelEdit()
{
    // Close any in-place active item on this view.
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->Close();
    ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
}

若要处理用户单击项外部的情况,请将以下代码添加到开头 CMainView::SetSelection

if (pNewSel != m_pSelection || pNewSel == NULL)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL&& pActiveItem != pNewSel)
        pActiveItem->Close();
}

当某个项处于就地活动状态时,它应具有焦点。 若要确保这是处理 OnSetFocus 的情况,以便在视图收到焦点时,焦点始终传输到活动项:

// Special handling of OnSetFocus and OnSize are required
// when an object is being edited in-place.
void CMainView::OnSetFocus(CWnd* pOldWnd)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);

    if (pActiveItem != NULL &&
        pActiveItem->GetItemState() == COleClientItem::activeUIState)
    {
        // need to set focus to this item if it is same view
        CWnd* pWnd = pActiveItem->GetInPlaceWindow();
        if (pWnd != NULL)
        {
            pWnd->SetFocus();   // don't call the base class
            return;
        }
    }

    CView::OnSetFocus(pOldWnd);
}

调整视图大小后,需要通知活动项剪辑矩形已更改。 为此,请为以下项 OnSize提供处理程序:

void CMainView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->SetItemRects();
}

案例研究:MFC 2.0 的 HIERSVR

HIERSVR 也包含在 MFC 2.0 中,并使用 MFC/OLE1 实现 OLE。 此说明简要介绍了最初转换此应用程序以使用 MFC/OLE 2 类的步骤。 完成初始端口后添加了许多功能,以更好地说明 MFC/OLE 2 类。 此处不会介绍这些功能;有关这些高级功能的详细信息,请参阅示例本身。

注释

编译器错误和分步过程是使用 Visual C++ 2.0 创建的。 Visual C++ 4.0 可能会更改特定的错误消息和位置,但概念信息仍然有效。

启动并运行

将 HIERSVR 示例移植到 MFC/OLE 的方法首先生成并修复将导致的明显编译器错误。 如果从 MFC 2.0 获取 HIERSVR 示例并将其编译在此版本的 MFC 下,你会发现要解决的错误不多(尽管 OCLIENT 示例多)。 下面描述了它们通常发生的顺序的错误。

编译和修复错误

\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'

第一个错误指出服务器函数存在更大的问题 InitInstance 。 OLE 服务器所需的初始化可能是对 MFC/OLE1 应用程序进行的最大更改之一,才能使其运行。 最好的做法是查看 AppWizard 为 OLE 服务器创建的内容,并根据需要修改代码。 下面是要记住的一些要点:

必须通过调用初始化 OLE 库 AfxOleInit

对文档模板对象调用 SetServerInfo 以设置服务器资源句柄和运行时类信息,而不能使用 CDocTemplate 构造函数进行设置。

如果命令行上存在 /Embedding,则不要显示应用程序的主窗口。

需要文档的 GUID 。 这是文档类型(128 位)的唯一标识符。 AppWizard 将为你创建一个应用程序,因此,如果使用此处所述的从新的 AppWizard 生成的服务器应用程序复制新代码的技术,只需从该应用程序“窃取”GUID 即可。 否则,可以在 BIN 目录中使用 GUIDGEN.EXE 实用工具。

必须通过调用COleTemplateServer“连接”对象COleTemplateServer::ConnectTemplate到文档模板。

在应用程序独立运行时更新系统注册表。 这样,如果用户移动应用程序的.EXE,则从其新位置运行它会更新 Windows 系统注册数据库以指向新位置。

根据 AppWizard 创建 InitInstance的内容应用所有这些更改后,HIERSVR 的 InitInstance (和相关 GUID)应如下所示:

// this is the GUID for HIERSVR documents
static const GUID BASED_CODE clsid =
{ 0xA0A16360L, 0xC19B, 0x101A, { 0x8C, 0xE5, 0x00, 0xDD, 0x01, 0x11, 0x3F, 0x12 } };

/////////////////////////////////////////////////////////////////////////////
// COLEServerApp initialization

BOOL COLEServerApp::InitInstance()
{
    // OLE 2 initialization
    if (!AfxOleInit())
    {
        AfxMessageBox("Initialization of the OLE failed!");
        return FALSE;
    }

    // Standard initialization
    LoadStdProfileSettings();   // Load standard INI file options

    // Register document templates
    CDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(IDR_HIERSVRTYPE,
        RUNTIME_CLASS(CServerDoc),
        RUNTIME_CLASS(CMDIChildWnd),
        RUNTIME_CLASS(CServerView));
    pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB);
    AddDocTemplate(pDocTemplate);

    // create main MDI Frame window
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
        return FALSE;
    m_pMainWnd = pMainFrame;

    SetDialogBkColor(); // gray look

    // enable file manager drag/drop and DDE Execute open
    m_pMainWnd->DragAcceptFiles();
    EnableShellOpen();

    m_server.ConnectTemplate(clsid, pDocTemplate, FALSE);
    COleTemplateServer::RegisterAll();

    // try to launch as an OLE server
    if (RunEmbedded())
    {
        // "short-circuit" initialization -- run as server!
        return TRUE;
    }
    m_server.UpdateRegistry();
    RegisterShellFileTypes();

    // not run as OLE server, so show the main window
    if (m_lpCmdLine[0] == '\0')
    {
        // create a new (empty) document
        OnFileNew();
    }
    else
    {
        // open an existing document
        OpenDocumentFile(m_lpCmdLine);
    }

    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();

    return TRUE;
}

你会注意到上面的代码引用新的资源 ID,IDR_HIERSVRTYPE_SRVR_EMB。 这是编辑另一个容器中嵌入的文档时要使用的菜单资源。 在 MFC/OLE1 中,特定于编辑嵌入项的菜单项是即时修改的。 在编辑嵌入项而不是编辑基于文件的文档中时,使用完全不同的菜单结构可以更轻松地为这两种不同的模式提供不同的用户界面。 稍后将看到,在就地编辑嵌入对象时会使用完全独立的菜单资源。

若要创建此资源,请将资源脚本加载到 Visual C++,并复制现有的IDR_HIERSVRTYPE菜单资源。 将新资源重命名为IDR_HIERSVRTYPE_SRVR_EMB(这是 AppWizard 使用的相同命名约定)。 接下来将“文件保存”更改为“文件更新”;为它提供命令 ID ID_FILE_UPDATE。 此外,将“文件另存为”更改为“文件另存为”;为它提供命令 ID ID_FILE_SAVE_COPY_AS。 框架提供这两个命令的实现。

\hiersvr\svritem.h(60) : error C2433: 'OLESTATUS' : 'virtual' not permitted on data declarations
\hiersvr\svritem.h(60) : error C2501: 'OLESTATUS' : missing decl-specifiers
\hiersvr\svritem.h(60) : error C2146: syntax error : missing ';' before identifier 'OnSetData'
\hiersvr\svritem.h(60) : error C2061: syntax error : identifier 'OLECLIPFORMAT'
\hiersvr\svritem.h(60) : error C2501: 'OnSetData' : missing decl-specifiers

由于它指的是 OnSetData 类型,因此重写导致许多错误。 OLESTATUS 是 OLE1 返回错误的方式。 这已更改为 OLE 2 中的 HRESULT,尽管 MFC 通常会将 HRESULT 转换为包含错误的 COleException。 在这种情况下,不再需要重写 OnSetData ,因此最简单的方法是删除它。

\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters

构造 COleServerItem 函数采用额外的“BOOL”参数。 此标志确定对 COleServerItem 对象执行内存管理的方式。 通过将它设置为 TRUE,框架将处理这些对象的内存管理 , 在不再需要这些对象时将其删除。 HIERSVR 使用 CServerItem (派生自 COleServerItem) 对象作为其本机数据的一部分,因此将此标志设置为 FALSE。 这允许 HIERSVR 确定何时删除每个服务器项。

\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class
\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class

正如这些错误所暗示的那样,CServerItem 中没有重写某些“纯虚拟”函数。 最有可能的原因是 OnDraw 的参数列表已更改。 若要修复此错误,请按如下方式进行更改 CServerItem::OnDraw (以及 svritem.h 中的声明):

BOOL CServerItem::OnDraw(CDC* pDC, CSize& rSize)
{
    // request from OLE to draw node
    pDC->SetMapMode(MM_TEXT); // always in pixels
    return DoDraw(pDC, CPoint(0, 0), FALSE);
}

新参数为“rSize”。 这样,可以填充绘图的大小(如果方便)。 此大小必须位于 HIMETRIC 中。 在这种情况下,填充此值并不方便,因此框架调用 OnGetExtent 以检索范围。 为此,必须实现 OnGetExtent

BOOL CServerItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
    if (dwDrawAspect != DVASPECT_CONTENT)
        return COleServerItem::OnGetExtent(dwDrawAspect, rSize);

    rSize = CalcNodeSize();
    return TRUE;
}
\hiersvr\svritem.cpp(104) : error C2065: 'm_rectBounds' : undeclared identifier
\hiersvr\svritem.cpp(104) : error C2228: left of '.SetRect' must have class/struct/union type
\hiersvr\svritem.cpp(106) : error C2664: 'void __pascal __far DPtoLP(struct ::tagPOINT __far *,
    int)__far const ' : cannot convert parameter 1 from 'int __far *' to 'struct ::tagPOINT __far *'

在 CServerItem::CalcNodeSize 函数中,项大小将转换为 HIMETRIC 并存储在 m_rectBounds中。 未记录的COleServerItem”成员不存在(已部分替换为m_sizeExtent,但在 OLE 2 中,此成员的用法与 OLE1 中的m_rectBounds略有不同)。 你将返回它,而不是将此成员变量设置为 HIMETRIC 大小。 此返回值用于 OnGetExtent以前实现的 。

CSize CServerItem::CalcNodeSize()
{
    CClientDC dcScreen(NULL);

    m_sizeNode = dcScreen.GetTextExtent(m_strDescription,
        m_strDescription.GetLength());
    m_sizeNode += CSize(CX_INSET * 2, CY_INSET * 2);

    // set suggested HIMETRIC size
    CSize size(m_sizeNode.cx, m_sizeNode.cy);
    dcScreen.SetMapMode(MM_HIMETRIC);
    dcScreen.DPtoLP(&size);
    return size;
}

CServerItem 也重写 COleServerItem::OnGetTextData。 此函数在 MFC/OLE 中已过时,并替换为其他机制。 MFC 3.0 版本的 MFC OLE 示例 HIERSVR 通过重写 COleServerItem::OnRenderFileData来实现此功能。 此功能对于此基本端口并不重要,因此可以删除 OnGetTextData 替代。

svritem.cpp中没有解决更多错误。 它们不是“真正的”错误, 只是以前错误导致的错误。

\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters

COleServerItem::CopyToClipboard 不再支持该 bIncludeNative 标志。 本机数据(服务器项的 Serialize 函数写入的数据)始终被复制,因此删除第一个参数。 此外,当发生错误而不是返回 FALSE 时, CopyToClipboard 将引发异常。 更改 CServerView::OnEditCopy 的代码,如下所示:

void CServerView::OnEditCopy()
{
    if (m_pSelectedNode == NULL)
        AfxThrowNotSupportedException();

    TRY
    {
        m_pSelectedNode->CopyToClipboard(TRUE);
    }
    CATCH_ALL(e)
    {
        AfxMessageBox("Copy to clipboard failed");
    }
    END_CATCH_ALL
}

尽管编译 MFC 2.0 版本的 HIERSVR 比同一版本的 OCLIENT 存在更多的错误,但实际上更改更少。

此时,HIERSVR 将编译和链接并充当 OLE 服务器,但没有就地编辑功能,接下来将实现该功能。

添加“视觉编辑”

若要将“视觉编辑”(或就地激活)添加到此服务器应用程序,只需注意以下几点:

  • 当项处于就地活动状态时,需要使用特殊的菜单资源。

  • 此应用程序具有一个工具栏,因此需要一个工具栏,其中只有普通工具栏的子集才能匹配服务器提供的菜单命令(与上面提到的菜单资源匹配)。

  • 你需要一个派生自 COleIPFrameWnd 的新类,该类提供就地用户界面(非常类似于派生自 CMDIFrameWnd的 CMainFrame)提供 MDI 用户界面。

  • 需要告知框架这些特殊资源和类。

菜单资源易于创建。 运行 Visual C++,将菜单资源IDR_HIERSVRTYPE复制到名为“IDR_HIERSVRTYPE_SRVR_IP”的菜单资源。 修改菜单,以便只剩下“编辑”和“帮助”菜单弹出窗口。 在“编辑”和“帮助”菜单之间向菜单添加两个分隔符(应如下所示): Edit || Help 有关这些分隔符的含义以及如何合并服务器和容器菜单的详细信息,请参阅 “菜单和资源:菜单合并”。

可以通过从已选中“服务器”选项的新 AppWizard 生成的应用程序复制子集工具栏的位图,轻松创建子集工具栏的位图。 然后,可以将此位图导入 Visual C++。 请务必为位图提供IDR_HIERSVRTYPE_SRVR_IP的 ID。

派生自 COleIPFrameWnd 的类也可以从具有服务器支持的 AppWizard 生成的应用程序复制。 复制这两个文件 IPFRAME。CPP 和 IPFRAME。H 并将其添加到项目。 请确保 LoadBitmap 调用引用IDR_HIERSVRTYPE_SRVR_IP,即在上一步中创建的位图。

创建所有新的资源和类后,请添加必要的代码,以便框架了解这些代码(并且知道此应用程序现在支持就地编辑)。 为此,将更多参数添加到 SetServerInfo 函数中的 InitInstance 调用:

pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
    IDR_HIERSVRTYPE_SRVR_IP,
    RUNTIME_CLASS(CInPlaceFrame));

现在,它已准备好在任何也支持就地激活的容器中就地运行。 但是,代码中仍有一个小 bug 潜伏。 HIERSVR 支持上下文菜单,当用户按下鼠标右键时显示。 此菜单在 HIERSVR 完全打开时有效,但在就地编辑嵌入时不起作用。 原因可以固定到 CServerView::OnRButtonDown 中的这一行代码:

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetApp()->m_pMainWnd);

请注意对 . 的 AfxGetApp()->m_pMainWnd引用。 当服务器就地激活时,它具有主窗口并设置了m_pMainWnd,但它通常不可见。 此外,此窗口是指应用程序 的主 窗口,即服务器完全打开或运行独立时出现的 MDI 框架窗口。 它不引用活动框架窗口 ,当就地激活时,该窗口是派生自 COleIPFrameWnd的框架窗口。 若要获取正确的活动窗口,即使就地编辑,此版本的 MFC 也会添加新函数 AfxGetMainWnd。 通常,应使用此函数而不是 AfxGetApp()->m_pMainWnd。 此代码需要更改,如下所示:

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetMainWnd());

现在,已最少启用 OLE 服务器,以便进行就地激活功能。 但是,MFC/OLE 2 中仍有许多功能在 MFC/OLE1 中不可用。 有关你可能想要实现的功能的更多想法,请参阅 HIERSVR 示例。 下面列出了 HIERSVR 实现的一些功能:

  • 缩放,对于容器而言,对于真正的 WYSIWYG 行为。

  • 拖放和自定义剪贴板格式。

  • 更改所选内容时滚动容器窗口。

MFC 3.0 中的 HIERSVR 示例也对其服务器项使用略有不同的设计。 这有助于节省内存,并使链接更加灵活。 使用 2.0 版本的 HIERSVR,树中的每个节点 均为 aCOleServerItemCOleServerItem 每个节点的开销比每个节点的开销要高一点,但每个活动链接都需要一个 COleServerItem 开销。 但在大多数情况下,在任何给定时间都有很少的活动链接。 为了提高此效率,此版本的 MFC 中的 HIERSVR 将节点与节点 COleServerItem分开。 它具有 CServerNode 和类 CServerItemCServerItem (派生自COleServerItem)仅在必要时创建。 容器(或容器)停止使用该特定节点的链接后,将删除与 CServerNode 关联的 CServerItem 对象。 此设计更高效、更灵活。 处理多个选择链接时,其灵活性也随之而来。 这两个版本的 HIERSVR 都不支持多个选择,但使用 MFC 3.0 版本的 HIERSVR 添加(并支持指向此类选择的链接)将更加容易,因为与 COleServerItem 本机数据分离。

另请参阅

按编号列出的技术说明
按类别列出的技术说明