次の方法で共有


テクニカル ノート 41: MFC/OLE1 から MFC/OLE 2 への移植

Note

次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。

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

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 サンプルの OCLIENT および HIERSVR) を移植するときに見つかる問題について説明します。

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

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

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

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

MFC の "用意された実装" クラス (CToolBarCStatusBarCScrollView など) には、OLE 2 サポート用の特殊なケース コードが組み込まれています。 そのため、これらのクラスをアプリケーションで使用できる場合は、それらのクラスを OLE 対応にする取り組みからメリットが得られます。 ここでも、それらの目的のために "お手製の" クラスを作成することはできますが、推奨されません。 同様の機能を実装する必要がある場合、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 "edit" コントロールに組み込まれている機能を使用して、クリップボードの貼り付け機能を実装します。 これは、従来の Windows API の使用と、新しい OLE API との統合の興味深い組み合わせを示しています。

サンプル アプリケーションの詳細については、「MFC のサンプルのヘルプ」をご覧ください。

ケース スタディ: MFC 2.0 からの OCLIENT

前述のように、OCLIENT は MFC 2.0 に含まれており、OLE は MFC/OLE1 で実装されていました。 このアプリケーションを最初に MFC/OLE 2 クラスを使用するように変換した手順を、以下に示します。 MFC/OLE クラスをさらに説明するために、初期ポートの完了後にいくつかの機能が追加されました。 これらの機能については、ここでは説明しません。これらの高度な機能の詳細については、サンプルをご覧ください。

Note

コンパイラ エラーとステップバイステップのプロセスは Visual C++ 2.0 で作成されました。 特定のエラー メッセージと場所が Visual C++ 4.0 で変更された可能性がありますが、概念情報は有効なままです。

準備と実行

OCLIENT サンプルを MFC/OLE に移植するために取られる方法は、最初にビルドして、結果として生じる明らかなコンパイラ エラーを修正することから始めます。 OCLIENT サンプルを MFC 2.0 から取得し、このバージョンの 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 の値を自動的に決定できます。 また、pFormatDC パラメーターは必要なくなりました。これは、渡された pDC の "属性 DC" からフレームワークによって構築されるためです。 この問題を解決するには、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 の要件でした。 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 ハンドラーによって発生します。 [新しいオブジェクトの挿入] コマンドの処理は、非常に大きな変更点があったもう 1 つの分野です。 この場合、新しい OLE コンテナー アプリケーションに対して、元の実装を AppWizard が提供した実装と単に結合するというのが最も簡単です。 実際、これは他のアプリケーションの移植にも応用できる手法です。 MFC/OLE1 では、AfxOleInsertDialog 関数を呼び出すと、[オブジェクトの挿入] ダイアログ ボックスが表示されます。 このバージョンでは、COleInsertObject ダイアログ オブジェクトを構築し、DoModal を呼び出します。 また、新しい OLE 項目は classname 文字列ではなく CLSID で作成されます。 最終的な結果は次のようになります。

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

Note

[新しいオブジェクトの挿入] の動作は、使用するアプリケーションによって異なる場合があります。

また、<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*) へのポインターである追加のパラメーターを取るようになりました。 このパラメーターは、"ビジュアル編集" (またはインプレース アクティブ化) を実装するためにのみ使用されます。 ここでは、そのパラメーターを 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 のメンバーは常に 0 でした)。 MFC/OLE 2 では、これが COleClientItem::GetExtentSetExtent でさらに直接的にサポートされています。これは、SIZE または CSize を代わりに操作します。

新しい 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 に置き換えられました (この新しい組織では、コンテナー/サーバー アプリケーションを簡単に構築できます)。 COleClientDocCOleDocument にマップする #define があり、OCLIENT などの MFC/OLE1 のアプリケーションを MFC/OLE 2 に簡単に移植できます。 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 によって提供されます。事実、ここでのコードの多くは、"コンテナー" をサポートする新しい AppWizard アプリケーションから直接借用されました。

まず、インプレース アクティブである項目が存在するときに使用するメニュー リソースを追加する必要があります。 この追加のメニュー リソースは、IDR_OCLITYPE リソースをコピーし、ファイルとウィンドウのポップアップを除くすべてを削除することによって Visual C++ で作成できます。 グループの分離を示すために、[ファイル] ポップアップと [ウィンドウ] ポップアップの間に 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 をオーバーライドすることによって、インプレース アクティブ化は完全に無効にされました。 インプレース アクティブ化を許可するには、このオーバーライドを削除する必要があります。 また、NULL が DoVerb に対するすべての呼び出しに渡されました (そのうちの 2 つがあります)。これは、ビューを提供することがインプレース アクティブ化にのみ必要であったためです。 インプレース アクティブ化を完全に実装するには、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;
}

この時点では、項目をインプレース アクティブ化できるコードが十分にあり、アクティブなときに項目のサイズ変更や移動を処理できますが、ユーザーが編集セッションを終了することを許可するコードはありません。 一部のサーバーでは、ESCAPE キーを操作することによってこの機能が提供されます。ただし推奨されているのは、項目を非アクティブ化するために、コンテナーに、(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 に含まれており、OLE は MFC/OLE1 で実装されていました。 このメモでは、最初に MFC/OLE 2 クラスを使用するようにこのアプリケーションを変換した手順について簡単に説明します。 MFC/OLE 2 クラスをさらに説明するために、初期ポートの完了後にいくつかの機能が追加されました。 これらの機能については、ここでは説明しません。これらの高度な機能の詳細については、サンプルをご覧ください。

Note

コンパイラ エラーとステップバイステップのプロセスは Visual C++ 2.0 で作成されました。 特定のエラー メッセージと場所が Visual C++ 4.0 で変更された可能性がありますが、概念情報は有効なままです。

準備と実行

HIERSVR サンプルを MFC/OLE に移植するために取られる方法は、最初にビルドして、結果として生じる明らかなコンパイラ エラーを修正することから始めます。 HIERSVR サンプルを MFC 2.0 から取得し、このバージョンの 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

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 に格納されます。 ドキュメントに記載されていない 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 フラグがサポートされなくなりました。 ネイティブ データ (サーバー項目のシリアル化関数によって作成されたデータ) は常にコピーされるため、最初のパラメーターを削除します。 さらに、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)。 これらの区切り記号の意味と、サーバーとコンテナーのメニューの結合方法の詳細については、「メニュー とリソース: メニューの結合」をご覧ください。

サブセット ツール バーのビットマップは、"Server" オプションをオンにして、新しい 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/OLE1 では使用できなかった多くの機能を、MFC/OLE 2 では使用できます。 実装する機能の詳細については、HIERSVR のサンプルをご覧ください。 HIERSVR で実装されている機能の一部を以下に示します。

  • ズーム。コンテナーに関する真の WYSIWYG 動作を実現します。

  • ドラッグアンドドロップとカスタムクリップボード形式。

  • 選択を変更するときのコンテナー ウィンドウのスクロール。

MFC 3.0 の HIERSVR サンプルでは、サーバー アイテムに対して若干異なる設計も使用しています。 これにより、メモリを節約し、リンクの柔軟性を高めることができます。 バージョン 2.0 の HIERSVR では、ツリー内の各ノードは COleServerItem です。 COleServerItem は、これらの各ノードに対して厳密には必要以上のオーバーヘッドが生じますが、アクティブな各リンクには COleServerItem が必要です。 しかし、ほとんどの場合、特定の時点でアクティブなリンクはほとんどありません。 これをより効率的にするために、このバージョンの MFC の HIERSVR は、COleServerItem からノードを分離します。 これには CServerNode と CServerItem クラスの両方があります。 CServerItem (COleServerItem から派生) は、必要に応じてのみ作成されます。 コンテナーが特定のノードへの特定のリンクの使用を停止すると、CServerNode に関連付けられている CServerItem オブジェクトは削除されます。 この設計は、より効率的で柔軟性に優れています。 複数の選択リンクを扱う場合に、この柔軟性を活用できます。 この 2 つのバージョンの HIERSVR では複数選択がサポートされていませんが、COleServerItem がネイティブ データから分離されているため、MFC 3.0 バージョンの HIERSVR で追加 (およびそのような選択へのリンクのサポート) が非常に簡単になります。

関連項目

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