共用方式為


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 範例 OCLIENT HIERSVR

MFC 檔/檢視架構很重要

如果您的應用程式未使用 MFC 的檔/檢視架構,而且您想要將 OLE 2 支援新增至您的應用程式,現在是移至檔/檢視的時候了。 MFC OLE 2 類別的許多優點只有在您的應用程式使用 MFC 的內建架構和元件之後才會實現。

您可以實作伺服器或容器而不使用 MFC 架構,但不建議這麼做。

使用 MFC 實作,而不是您自己的實作

MFC「罐裝實作」類別,例如 CToolBarCStatusBarCScrollView 具有 OLE 2 支援的內建特殊案常式序代碼。 因此,如果您在應用程式中使用這些類別,您將受益于投入到這些類別中,讓他們知道 OLE。 同樣地,基於這些目的,您可以在這裡「推出您自己的」類別,但不建議使用。 如果您需要實作類似的功能,MFC 原始程式碼是處理 OLE 某些更精細點的絕佳參考(特別是在就地啟用時)。

檢查 MFC 範例程式碼

有數個 MFC 範例包含 OLE 功能。 這些應用程式都會從不同的角度實作 OLE:

  • HIERSVR 主要用來作為伺服器應用程式。 它包含在 MFC 2.0 中作為 MFC/OLE1 應用程式,並已移植到 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 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 的 「attribute 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 '

上述錯誤是由於 COleClientItem::CreateXXXX MFC/OLE1 中的所有函式都需要傳遞唯一名稱來表示專案。 這是基礎 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 專案,而不是 classname 字串。 最終結果看起來應該像這樣

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::GetBounds 用來 SetBounds 查詢和操作專案的範圍( lefttop 成員一律為零)。 在 MFC/OLE 2 中,與 會處理 SIZE 或 改為更直接地支援 COleClientItem::GetExtent SetExtentCSize

新 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 呼叫中,從容器對伺服器 進行模擬 ,因為在許多情況下,OLE1 原本就是非同步。 在處理使用者的命令之前,必須先檢查進行中未處理的非同步呼叫。 MFC/OLE1 提供 COleClientItem::InWaitForRelease 函式來進行此動作。 在 MFC/OLE 2 中,這並非必要,因此您可以一起移除 CMainFrame 中 OnCommand 的覆寫。

此時,OCLIENT 將會編譯和連結。

其他必要的變更

不過,沒有完成的動作會讓 OCLIENT 無法執行。 現在最好是修正這些問題,而不是稍後的問題。

首先,必須初始化 OLE 程式庫。 這是藉由從 InitInstance 呼叫 AfxOleInit 來完成:

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。 所提供的其中一個功能 COleDocument COleClientDoc 是標準命令訊息對應專案。 如此一來,除非伺服器應用程式是容器/伺服器應用程式,否則伺服器應用程式也會使用 (間接使用 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 。 如需這些分隔符號的意義,以及伺服器和容器功能表合併方式的詳細資訊,請參閱 功能表和資源:功能表合併

建立這些功能表之後,您必須讓架構知道這些功能表。 這會先呼叫 CDocTemplate::SetContainerInfo 檔範本,再將它新增至 InitInstance 中的檔範本清單。 要註冊檔範本的新程式碼如下所示:

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) 衍生類別以及 COleClientItem 衍生類別 (CRectItem) 中 CView ,有一些專案需要變更。 所有這些覆寫都是由 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::ConnectTemplate 來「連接」物件 COleTemplateServer 至檔範本。

當您的應用程式獨立執行時,請更新系統登錄。 如此一來,如果使用者移動應用程式的 .EXE,則從其新位置執行它將會更新 Windows 系統註冊資料庫,以指向新的位置。

根據 AppWizard 針對 InitInstance 所建立的內容套用所有這些變更之後, InitInstance HIERSVR 的 (和相關 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;
}

您會注意到上述程式碼指的是新的資源識別碼,IDR_HIERSVRTYPE_SRVR_EMB。 這是編輯內嵌在另一個容器中的檔時要使用的功能表資源。 在 MFC/OLE1 中,編輯內嵌專案的特定功能表項目會即時修改。 在編輯內嵌專案而不是編輯以檔案為基礎的檔時,使用完全不同的功能表結構,可讓您更輕鬆地為這兩種不同的模式提供不同的使用者介面。 如您稍後所見,在就地編輯内嵌物件時,會使用完全獨立的功能表資源。

若要建立此資源,請將資源腳本載入 Visual C++,並複製現有的IDR_HIERSVRTYPE功能表資源。 將新資源重新命名為 IDR_HIERSVRTYPE_SRVR_EMB (這是 AppWizard 所使用的相同命名慣例)。 接下來將 「檔案儲存」變更為「檔案更新」;提供命令識別碼ID_FILE_UPDATE。 同時將 「檔案另存新檔」變更為「檔案另存新檔」;提供命令識別碼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 類型。 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 中。 未記載的 ' m_rectBounds' 成員不存在(它已部分由 m_sizeExtent 取代,但在 OLE 2 中,這個成員 COleServerItem 的使用方式 與 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 函式所寫的資料一律會複製,因此您可以移除第一個參數。 此外, CopyToClipboard 會在發生錯誤時擲回例外狀況,而不是傳回 FALSE。 變更 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識別碼。

衍生自 COleIPFrameWnd 的類別也可以從具有伺服器支援之 AppWizard 產生的應用程式複製。 複製這兩個檔案 IPFRAME。CPP 和 IPFRAME。H 並將其新增至專案。 請確定 LoadBitmap 呼叫參考IDR_HIERSVRTYPE_SRVR_IP,這是在上一個步驟中建立的點陣圖。

現在已建立所有新的資源和類別,請新增必要的程式碼,讓架構知道這些內容(而且知道此應用程式現在支援就地編輯)。 這可藉由在 函式中 InitInstance 將更多參數新增至 SetServerInfo 呼叫來完成:

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,樹 狀結構中的每個節點都是 a COleServerItemCOleServerItem 具有比每個這些節點絕對必要的額外負荷一點,但 COleServerItem 每個作用中連結都需要 。 但在大多數情況下,在任何指定時間都有很少的作用中連結。 為了讓這個更有效率,此 MFC 版本中的 HIERSVR 會將節點與 區隔開 COleServerItem 。 它同時具有 CServerNode 和 類別 CServerItemCServerItem(衍生自 COleServerItem ) 只會視需要建立。 一旦容器(或容器)停止使用該特定節點的特定連結,就會刪除與 CServerNode 相關聯的 CServerItem 物件。 此設計更有效率且更有彈性。 處理多個選取連結時,其彈性也隨之而來。 這兩個版本的 HIERSVR 都不支援多重選取,但是使用 MFC 3.0 版 HIERSVR 來新增或支援這類選取專案的連結會比較容易,因為 COleServerItem 與原生資料分開。

另請參閱

依編號顯示的技術提示
依分類區分的技術提示