例 : プロパティ ページの実装

更新 : 2007 年 11 月

この例では、ドキュメント クラス インターフェイスのプロパティを表示し、変更もできるプロパティ ページの作成方法について説明します。このインターフェイスは、Visual Studio の共通環境オブジェクト モデルの例のドキュメントによって公開されます。ただし、これから作成するプロパティ ページでは、操作対象のオブジェクトが正しいインターフェイスをサポートする限り、オブジェクトがどこにあるかは問題ではありません。

この例は ATLPages サンプルに基づいています。

この例では、次の手順を実行します。

  • [クラスの追加] ダイアログ ボックスと ATL プロパティ ページ ウィザードを使用して、ATL プロパティ ページ クラスを追加します。

  • ダイアログ リソースを編集するために、Document インターフェイスの必要なプロパティに新しいコントロールを追加します。

  • ユーザーによる変更をプロパティ ページ サイトに通知するために、メッセージ ハンドラを追加します。

  • 「ハウスキーピング」の説明に従って #import ステートメントと typedef を追加します。

  • プロパティ ページに渡すオブジェクトを検証するために、IPropertyPageImpl::SetObjects をオーバーライドします。

  • プロパティ ページのインターフェイスを初期化するために、IPropertyPageImpl::Activate をオーバーライドします。

  • オブジェクトを最新のプロパティ値に更新するために、IPropertyPageImpl::Apply をオーバーライドします。

  • 簡単なヘルパー オブジェクトを作成して、プロパティ ページを表示します。

  • プロパティ ページをテストするマクロを作成します。

ATL プロパティ ページ クラスの追加

まず、DLL サーバーの新しい ATL プロジェクト (ATLPages7) を作成します。次に、ATL プロパティ ページ ウィザードで、プロパティ ページを生成します。プロパティ ページの [短い名前] を DocProperties に設定します。次に、[文字列] ページに切り替えて、プロパティ ページ固有の項目を次の表のように設定します。

項目

[タイトル]

TextDocument

[ドキュメント文字列]

VCUE TextDocument プロパティ

[ヘルプ ファイル]

<空白>

ウィザードの [文字列] ページに設定した値は、プロパティ ページ コンテナが IPropertyPage::GetPageInfo を呼び出すと、プロパティ ページ コンテナに戻ります。それ以降の文字列に対する処理はコンテナに依存しますが、通常、文字列はユーザーがページを識別するために使用されます。[タイトル] は、通常、ページ上部のタブに表示されます。[ドキュメント文字列] はステータス バーやツールヒントに表示されます (ただし、標準的なプロパティ フレームでは、この文字列は使用されません)。

5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

[文字列] ページで設定した文字列は、ウィザードによって文字列リソースとしてプロジェクトに格納されます。ページのコードを生成した後でこの情報を変更する必要がある場合、リソース エディタでこの文字列を簡単に編集できます。

[OK] をクリックして、ウィザードでプロパティ ページを生成します。

ダイアログ リソースの編集

プロパティ ページが生成されたら、ページを表すダイアログ リソースにコントロールをいくつか追加する必要があります。ここでは、エディット ボックス、静的テキスト コントロール、およびチェック ボックスを追加し、次のように ID を設定します。

Visual Studio 編集ダイアログ リソース

これらのコントロールは、ドキュメントのファイル名と読み取り専用のステータスを表示するために使用します。

5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

ダイアログ リソースには、フレームやコマンド ボタンは含まれません。また、想定するようなタブも作成されません。これらの機能を持つプロパティ ページ フレームは、OleCreatePropertyFrame を呼び出すと作成されます。

メッセージ ハンドラの追加

コントロールを適切な場所に配置したら、メッセージ ハンドラを追加できます。メッセージ ハンドラは、いずれかのコントロールの値が変更されたときに、ページのダーティな状態を更新するために使用します。

BEGIN_MSG_MAP(CDocProperties)
   COMMAND_HANDLER(IDC_NAME, EN_CHANGE, OnUIChange)
   COMMAND_HANDLER(IDC_READONLY, BN_CLICKED, OnUIChange)
   CHAIN_MSG_MAP(IPropertyPageImpl<CDocProperties>)
END_MSG_MAP()

   // Respond to changes in the UI to update the dirty status of the page
   LRESULT OnUIChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
   {
      wNotifyCode; wID; hWndCtl; bHandled;
      SetDirty(true);
      return 0;
   }

このコードでは、エディット コントロールまたはチェック ボックスの変更に対して、IPropertyPageImpl::SetDirty を呼び出して対応します。IPropertyPageImpl::SetDirty は、ページ サイトにページの変更を通知します。通常、ページ サイトは、プロパティ ページ フレームの [適用] ボタンを有効または無効にして対応します。

5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

プロパティ ページによっては、変更されていないプロパティを更新することがないように、ユーザーが変更したプロパティを厳密に記録する必要がある場合があります。このコードの実装では、元のプロパティ値を記録し、変更を適用するときに元のプロパティ値と UI の現在の値を比較します。

ハウスキーピング

ここで、コンパイラが Document インターフェイスを認識できるように、DocProperties.h に #import ステートメントを 2 つ追加します。

// MSO.dll
#import <libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52> version("2.2") \
   rename("RGB", "Rgb")   \
   rename("DocumentProperties", "documentproperties")   \
   rename("ReplaceText", "replaceText")   \
   rename("FindText", "findText")   \
   rename("GetObject", "getObject")   \
   raw_interfaces_only

// dte.olb
#import <libid:80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2> \
   inject_statement("using namespace Office;")   \
   rename("ReplaceText", "replaceText")   \
   rename("FindText", "findText")   \
   rename("GetObject", "getObject")   \
   rename("SearchPath", "searchPath")   \
   raw_interfaces_only

さらに、IPropertyPageImpl 基本クラスを参照する必要があります。CDocProperties クラスに次の typedef を追加します。

typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;

IPropertyPageImpl::SetObjects のオーバーライド

最初にオーバーライドする IPropertyPageImpl のメソッドは、SetObjects です。ここで、コードを追加して、渡したオブジェクトが 1 つだけかどうか、さらに必要な Document インターフェイスをそのオブジェクトがサポートしているかどうかをチェックします。

STDMETHOD(SetObjects)(ULONG nObjects, IUnknown** ppUnk)
{
   HRESULT hr = E_INVALIDARG;
   if (nObjects == 1)
   {
      CComQIPtr<EnvDTE::Document> pDoc(ppUnk[0]);
      if (pDoc)
         hr = PPGBaseClass::SetObjects(nObjects, ppUnk);
   }
   return hr;
}
5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

このページでサポートするオブジェクトは、1 つだけである必要があります。ユーザーはオブジェクトのファイル名を設定できるため、特定の場所には 1 つのファイルしか置くことができないためです。

IPropertyPageImpl::Activate のオーバーライド

次に、プロパティ ページを最初に作成したときに基になったオブジェクトのプロパティ値を使用して、プロパティ ページを初期化します。

この場合、ページのユーザーによって変更が適用されるとき、比較のためのプロパティの初期値も必要になります。そのため、クラスに次のメンバを追加する必要があります。

CComBSTR m_bstrFullName;  // The original name
VARIANT_BOOL m_bReadOnly; // The original read-only state

Activate メソッドの基本クラスの実装は、ダイアログ ボックスとそのコントロールを作成します。そのため、Activate メソッドをオーバーライドし、基本クラスを呼び出した後で独自の初期設定を追加できます。

STDMETHOD(Activate)(HWND hWndParent, LPCRECT prc, BOOL bModal)
{
   // If we don't have any objects, this method should not be called
   // Note that OleCreatePropertyFrame will call Activate even if
   // a call to SetObjects fails, so this check is required
   if (!m_ppUnk)
      return E_UNEXPECTED;

   // Use Activate to update the property page's UI with information
   // obtained from the objects in the m_ppUnk array

   // We update the page to display the Name and ReadOnly properties
   // of the document

   // Call the base class
   HRESULT hr = PPGBaseClass::Activate(hWndParent, prc, bModal);
   if (FAILED(hr))
      return hr;

   // Get the EnvDTE::Document pointer
   CComQIPtr<EnvDTE::Document> pDoc(m_ppUnk[0]);
   if (!pDoc)
      return E_UNEXPECTED;

   // Get the FullName property
   hr = pDoc->get_FullName(&m_bstrFullName);
   if (FAILED(hr))
      return hr;

   // Set the text box so that the user can see the document name
   USES_CONVERSION;
   SetDlgItemText(IDC_NAME, CW2CT(m_bstrFullName));

   // Get the ReadOnly property
   m_bReadOnly = VARIANT_FALSE;
   hr = pDoc->get_ReadOnly(&m_bReadOnly);
   if (FAILED(hr))
      return hr;

   // Set the check box so that the user can see the document's read-only status
   CheckDlgButton(IDC_READONLY, m_bReadOnly ? BST_CHECKED : BST_UNCHECKED);

   return hr;
}

このコードでは、Document インターフェイスの COM メソッドを使用して、必要なプロパティを取得します。続いて、CDialogImpl とその基本クラスによって提供された Win32 API ラッパーを使用して、ユーザーにプロパティ値を示します。

IPropertyPageImpl::Apply のオーバーライド

ユーザーが変更をオブジェクトに適用しようとすると、プロパティ ページ サイトが Apply メソッドを呼び出します。コードのこの部分では、Activate とは反対のことを行います。つまり、Activate がオブジェクトから値を取得してプロパティ ページのコントロールにプッシュするのに対し、Apply はプロパティ ページのコントロールから値を取得してオブジェクトにプッシュします。

STDMETHOD(Apply)(void)
{
   // If we don't have any objects, this method should not be called
   if (!m_ppUnk)
      return E_UNEXPECTED;

   // Use Apply to validate the user's settings and update the objects'
   // properties

   // Check whether we need to update the object
   // Quite important since standard property frame calls Apply
   // when it doesn't need to
   if (!m_bDirty)
      return S_OK;

   HRESULT hr = E_UNEXPECTED;

   // Get a pointer to the document
   CComQIPtr<EnvDTE::Document> pDoc(m_ppUnk[0]);
   if (!pDoc)
      return hr;

   // Get the read-only setting
   VARIANT_BOOL bReadOnly = IsDlgButtonChecked(IDC_READONLY) ? VARIANT_TRUE : VARIANT_FALSE;

   // Get the file name
   CComBSTR bstrName;
   if (!GetDlgItemText(IDC_NAME, bstrName.m_str))
      return E_FAIL;

   // Set the read-only property
   if (bReadOnly != m_bReadOnly)
   {
      hr = pDoc->put_ReadOnly(bReadOnly);
      if (FAILED(hr))
         return hr;
   }

   // Save the document
   if (bstrName != m_bstrFullName)
   {
      EnvDTE::vsSaveStatus status;
      hr = pDoc->Save(bstrName, &status);
      if (FAILED(hr))
         return hr;
   }

   // Clear the dirty status of the property page
   SetDirty(false);

   return S_OK;
}
5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

実装の最初の方で行う m_bDirty に対するチェックは、Apply が 2 回以上呼び出された場合にオブジェクトの不要な更新を防ぐために行われる最初のチェックです。また、変更のときだけ Document に対してメソッド呼び出しが行われるように、各プロパティ値のチェックも行います。

5h66xtbw.alert_note(ja-jp,VS.90).gifメモ :

Document は、FullName を読み取り専用プロパティとして公開します。プロパティ ページに対する変更に基づいてドキュメントのファイル名を更新するには、Save メソッド使用してファイルを別名で保存します。プロパティ ページのコードをプロパティの取得または設定に限定しなくてもよいことがわかります。

プロパティ ページの表示

このページを表示するには、簡単なヘルパー オブジェクトを作成する必要があります。ヘルパー オブジェクトには、1 つのオブジェクトに接続された 1 つのページを表示するために、OleCreatePropertyFrame API を簡略化したメソッドが用意されています。このヘルパーは、Visual Basic で使用できるようにデザインされます。

[クラスの追加] ダイアログ ボックス[ATL シンプル オブジェクト ウィザードを使用して、新しいクラスを生成し、短い名前を Helper にします。新しいクラスを作成したら、次の表に示すメソッドを追加します。

項目

メソッド名

ShowPage

パラメータ

[in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk

bstrCaption パラメータは、ダイアログ ボックスのタイトルとして表示するキャプションです。bstrID パラメータは、表示するプロパティ ページの CLSID または ProgID を表す文字列です。pUnk パラメータは、プロパティ ページによってプロパティを設定するオブジェクトの IUnknown ポインタになります。

次のようにメソッドを実装します。

STDMETHODIMP CHelper::ShowPage(BSTR bstrCaption, BSTR bstrID, IUnknown* pUnk)
{
   if (!pUnk)
      return E_INVALIDARG;

   // First, assume bstrID is a string representing the CLSID 
   CLSID theCLSID = {0};
   HRESULT hr = CLSIDFromString(bstrID, &theCLSID);
   if (FAILED(hr))
   {
      // Now assume bstrID is a ProgID
      hr = CLSIDFromProgID(bstrID, &theCLSID);
      if (FAILED(hr))
         return hr;
   }

   // Use the system-supplied property frame
   return OleCreatePropertyFrame(
      GetActiveWindow(),   // Parent window of the property frame
      0,           // Horizontal position of the property frame
      0,           // Vertical position of the property frame
      bstrCaption, // Property frame caption
      1,           // Number of objects
      &pUnk,       // Array of IUnknown pointers for objects
      1,           // Number of property pages
      &theCLSID,   // Array of CLSIDs for property pages
      NULL,        // Locale identifier
      0,           // Reserved - 0
      NULL         // Reserved - 0
      );
}

マクロの作成

プロジェクトをビルドしたら、簡単なマクロを使用してプロパティ ページとヘルパー オブジェクトをテストできます。簡単なマクロは、Visual Studio 開発環境で作成して実行できます。このマクロは、ヘルパー オブジェクトを作成し、ヘルパー オブジェクトの ShowPage メソッドを呼び出します。ShowPage メソッドを呼び出すときには、DocProperties プロパティ ページの ProgID と、Visual Studio エディタで現在アクティブなドキュメントの IUnknown ポインタが使用されます。このマクロで必要なコードを次に示します。

Imports EnvDTE
Imports System.Diagnostics

Public Module AtlPages

    Public Sub Test()
        Dim Helper
        Helper = CreateObject("ATLPages7.Helper.1")

        On Error Resume Next
        Helper.ShowPage( _
            ActiveDocument.Name, _
            "ATLPages7Lib.DocumentProperties.1", _
            DTE.ActiveDocument _
            )
    End Sub

End Module

このマクロを実行すると、プロパティ ページが開き、現在アクティブなテキスト ドキュメントのファイル名と読み取り専用ステータスが表示されます。ドキュメントの読み取り専用ステータスは、開発環境でドキュメントへの書き込みが可能かどうかを示すだけであり、ディスクのファイルの読み取り専用属性には影響しません。

参照

処理手順

ATLPages サンプル : IPropertyPageImpl を使用したプロパティ ページの実装

概念

ATL COM プロパティ ページ