次の方法で共有


TN041: MFC/OLE1 MFC/OLE 2 への移行

次のテクニカル ノートは、最初にオンライン ドキュメントに含まれてから更新されていません。 その結果、一部の手順やトピックが古くなっているか、正しくない可能性があります。 最新情報については、オンライン ドキュメント インデックスで関心のあるトピックを検索することをお勧めします。

移行に関する一般的な問題

MFC 2.5 (以降) の OLE 2 クラスの設計目標の 1 つは、OLE 1.0 のサポートのために MFC 2.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 に含まれる 2 つのアプリケーション (MFC OLE サンプル OCLIENTHIERSVR) を移植するときに明らかになった問題について説明します。

MFC ドキュメント/ビュー アーキテクチャが重要

アプリケーションで MFC のドキュメント/ビュー アーキテクチャを使用せず、OLE 2 のサポートをアプリケーションに追加する場合は、次にドキュメント/ビューに移動します。 MFC の OLE 2 クラスの利点の多くは、アプリケーションで MFC の組み込みアーキテクチャとコンポーネントを使用した場合にのみ実現されます。

MFC アーキテクチャを使用せずにサーバーまたはコンテナーを実装することは可能ですが、推奨されません。

独自の MFC 実装の代わりに使用する

CToolBarCStatusBarCScrollViewなどの MFC の "canned 実装" クラスには、OLE 2 サポート用の特殊なケース コードが組み込まれています。 そのため、アプリケーションでこれらのクラスを使用できる場合は、OLE 対応にするための取り組みの恩恵を受けることができます。 ここでも、これらの目的でクラスを "ロール Your Own" することもできますが、推奨されません。 同様の機能を実装する必要がある場合、MFC ソース コードは、OLE のより細かい点 (特にインプレース アクティブ化に関しては) を処理するための優れたリファレンスです。

MFC サンプル コードを調べる

OLE 機能を含む MFC サンプルが多数あります。 これらの各アプリケーションは、異なる角度から OLE を実装します。

  • HIERSVR 主にサーバー アプリケーションとして使用することを意味します。 MFC/OLE1 アプリケーションとして MFC 2.0 に含まれ、MFC/OLE 2 に移植された後、OLE 2 で使用できる多くの OLE 機能を実装するように拡張されています。

  • OCLIENT これは、コンテナーの観点から OLE 機能の多くを示すために使用されるスタンドアロン コンテナー アプリケーションです。 MFC 2.0 からも移植され、カスタム クリップボード形式や埋め込みアイテムへのリンクなど、より高度な OLE 機能の多くをサポートするように拡張されました。

  • DRAWCLI このアプリケーションは、既存のオブジェクト指向描画プログラムのフレームワーク内で行う点を除き、OCLIENT と同様に OLE コンテナー のサポートを実装します。 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 に移植する方法は、まず OCLIENT サンプルをビルドし、発生する明白なコンパイラ エラーを修正することです。 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 です (この例のように)。 このバージョンの MFC では、描画対象の CDC がメタファイル DC である場合、lpWBounds の値を自動的に決定できます。 さらに、フレームワークは渡された pDC の "属性 DC" からビルドするため、pFormatDC パラメーターは不要になります。 したがって、この問題を解決するには、Draw 呼び出しに対する 2 つの追加の 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 の要件でした。 OLE 2 は基になる通信メカニズムとして DDE を使用しないため、MFC/OLE 2 ではこれは必要ありません (名前は DDE 会話で使用されました)。 この問題を解決するには、 CreateNewName 関数と、それに対するすべての参照を削除します。 このバージョンでは、呼び出しにカーソルを置いて F1 キーを押すだけで、各 MFC/OLE 関数が期待している内容を簡単に確認できます。

OLE 2 クリップボードの処理が大きく異なるもう 1 つの領域です。 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 ハンドラーによって発生します。 「新しいオブジェクトの挿入」コマンドを処理することは、物事がかなり変わったもう1つの領域です。 この場合、新しい OLE コンテナー アプリケーション用に AppWizard によって提供される実装と元の実装をマージするのが最も簡単です。 実際、これは他のアプリケーションの移植に適用できる手法です。 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();

Insert New Object may be different for your application):

また、 <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_PRIMARYOLEIVERB_PRIMARY に変更されました。 OLE1 と OLE 2 の両方で、主動詞は通常、ユーザーが項目をダブルクリックしたときにコンテナーによって実行されます。

さらに、 DoVerb はビューへのポインター (CView*) という追加のパラメーターを受け取るようになりました。 このパラメーターは、"Visual Editing" (またはインプレース アクティブ化) を実装するためにのみ使用されます。 現時点では、この機能を実装していないため、このパラメーターを 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 を使用して項目の範囲を照会および操作しました ( left メンバーと top メンバーは常に 0 でした)。 MFC/OLE 2 では、これは、代わりに SIZE またはCSizeを扱うCOleClientItem::GetExtentSetExtentで直接サポートされます。

新しい 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;
}

また、パラメーター リストの変更について仮想関数を確認することをお勧めします。 このような関数の 1 つは 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 に置き換えられました (この新しい組織により、コンテナー/サーバー アプリケーションの構築が容易になります)。 をにマップして、MFC/OLE1 アプリケーションを MFC/OLE 2 (OCLIENT など) に簡単に移植できるようにする #define があります。 COleClientDocによって提供されたCOleDocumentによって提供されない機能の 1 つは、標準のコマンド メッセージ マップ エントリです。 これは、 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 の最も興味深い機能の 1 つは、インプレース アクティブ化 (または "ビジュアル編集") です。 この機能により、サーバー アプリケーションはコンテナーのユーザー インターフェイスの一部を引き継ぎ、ユーザーによりシームレスな編集インターフェイスを提供できます。 OCLIENT へのインプレース ライセンス認証を実装するには、いくつかの特別なリソースと追加のコードを追加する必要があります。 これらのリソースとコードは、通常、AppWizard によって提供されます。実際、ここでのコードの多くは、"Container" をサポートする新しい AppWizard アプリケーションから直接借用されています。

まず、インプレースアクティブな項目がある場合に使用するメニュー リソースを追加する必要があります。 Visual C++ でこの追加のメニュー リソースを作成するには、IDR_OCLITYPE リソースをコピーし、ファイルとウィンドウ以外のポップアップをすべて削除します。 グループの分離を示すために、[ファイル] ポップアップと [ウィンドウ] ポップアップの間に 2 つの区切り線が挿入されます ( 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++ で作成された特殊なインプレース リソースです。

インプレース アクティブ化を有効にするには、 CView (CMainView) 派生クラスと、 COleClientItem 派生クラス (CRectItem) の両方で変更する必要があります。 これらのオーバーライドはすべて AppWizard によって提供され、ほとんどの実装は既定の AppWizard アプリケーションから直接取得されます。

このポートの最初の手順では、 COleClientItem::CanActivateをオーバーライドすることで、インプレース アクティブ化が完全に無効になりました。 このオーバーライドは、インプレース アクティブ化を許可するために削除する必要があります。 さらに、ビューの提供はインプレース アクティブ化にのみ必要であったため、 DoVerb のすべての呼び出し (そのうちの 2 つ) に NULL が渡されました。 インプレース アクティブ化を完全に実装するには、 DoVerb 呼び出しで正しいビューを渡す必要があります。 これらの呼び出しの 1 つは CMainView::OnInsertObjectです。

pItem->DoVerb(OLEIVERB_SHOW, this);

もう 1 つは 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) 項目の外側をクリックして項目を非アクティブ化する方法と、(2) ESCAPE キーを押して非アクティブ化する方法の 2 つをコンテナーで提供することをお勧めします。

ESCAPE キーの場合は、VK_ESCAPE キーをコマンドにマップする Visual C++ を使用してアクセラレータを追加します。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 アプリケーションを実行するために行う必要がある最大の変更の 1 つです。 最善の方法は、AppWizard が OLE サーバー用に作成する内容を確認し、必要に応じてコードを変更することです。 次の点にご注意ください。

を呼び出して OLE ライブラリを初期化する必要があります。 AfxOleInit

ドキュメント テンプレート オブジェクトで SetServerInfo を呼び出して、 CDocTemplate コンストラクターで設定できないサーバー リソース ハンドルとランタイム クラス情報を設定します。

コマンド ラインに /Embedding が存在する場合は、アプリケーションのメイン ウィンドウを表示しないでください。

ドキュメントの GUID が 必要です。 これは、ドキュメントの種類 (128 ビット) の一意の識別子です。 AppWizard によって作成されます。そのため、新しい AppWizard で生成されたサーバー アプリケーションから新しいコードをコピーする方法をここで説明する手法を使用すると、そのアプリケーションから GUID を "盗む" ことができます。 そうでない場合は、BIN ディレクトリで GUIDGEN.EXE ユーティリティを使用できます。

COleTemplateServer::ConnectTemplateを呼び出して、COleTemplateServer オブジェクトをドキュメント テンプレートに "接続" する必要があります。

アプリケーションがスタンドアロンで実行されるときに、システム レジストリを更新します。 これにより、ユーザーがアプリケーションの.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 では、埋め込みアイテムの編集に固有のメニュー項目が、その場で変更されました。 ファイル ベースのドキュメントを編集する代わりに、埋め込みアイテムを編集するときにまったく異なるメニュー構造を使用すると、これら 2 つの異なるモードに異なるユーザー インターフェイスを提供することがはるかに簡単になります。 後で説明するように、埋め込みオブジェクトをインプレースで編集するときに、まったく別のメニュー リソースが使用されます。

このリソースを作成するには、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

OLESTATUS 型を参照しているため、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 では、ネイティブ データの一部として (COleServerItem から派生した) CServerItemオブジェクトが使用されるため、このフラグを 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_rectBounds' メンバーは存在しません (一部は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 OLE サンプル HIERSVR の MFC 3.0 バージョンでは、 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 というメニュー リソースにコピーします。 メニューを変更して、[編集] メニューと [ヘルプ] メニューのポップアップのみが残るようにします。 [編集] メニューと [ヘルプ] メニューの間に 2 つの区切り記号をメニューに追加します (次のようになります: Edit || Help)。 これらの区切り記号の意味と、サーバーとコンテナーのメニューの結合方法の詳細については、「 メニューとリソース: メニューのマージ」を参照してください。

サブセット ツール バーのビットマップは、"サーバー" オプションをオンにして、AppWizard で生成された新しいアプリケーションからビットマップをコピーすることで簡単に作成できます。 その後、このビットマップを Visual C++ にインポートできます。 ビットマップには必ずIDR_HIERSVRTYPE_SRVR_IPの ID を指定してください。

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

これで、インプレース アクティブ化もサポートする任意のコンテナーでインプレースで実行する準備ができました。 しかし、コードにはまだ小さなバグが1つあります。 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 と CServerItem クラスの両方があります。 (COleServerItem から派生した) CServerItemは、必要に応じてのみ作成されます。 コンテナー (またはコンテナー) がその特定のノードへの特定のリンクの使用を停止すると、CServerNode に関連付けられている CServerItem オブジェクトが削除されます。 この設計はより有効で、より適用範囲が広い。 その柔軟性は、複数の選択リンクを扱うときに利用できます。 これら 2 つのバージョンの HIERSVR はどちらも複数の選択をサポートしていますが、MFC 3.0 バージョンの HIERSVR を使用して追加 (およびこのような選択へのリンクをサポートする) 方がはるかに簡単です。これは、 COleServerItem がネイティブ データから分離されているためです。

こちらも参照ください

番号別テクニカル ノート
カテゴリ別テクニカル ノート