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

ATL プロパティ ページ ウィザードは、Visual Studio 2019 以降では使用できません。

この例では、ドキュメント クラス インターフェイスのプロパティを表示する (変更することもできる)プロパティ ページの作成方法を示します。

この例は、ATLPages の例をベースにしています。

この例を完了するには、以下を行います。

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

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

項目 Value
Title TextDocument
Doc String VCUE TextDocument のプロパティ
Helpfile <blank>

このウィザード ページに設定した値は、プロパティ ページ コンテナーが IPropertyPage::GetPageInfo を呼び出したときに返されます。 その後の文字列の処理はコンテナーによって決まりますが、通常は、ユーザーがページを識別するために使用されます。 Title は、通常、ページ上部のタブに表示され、Doc String は、ステータス バーまたはツールヒント上に表示できます (ただし、標準プロパティ フレームではこの文字列はまったく使用されません)。

Note

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

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

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

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

Screenshot of a dialog resource in the visual editor.

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

Note

期待に反して、ダイアログ リソースには、フレームもコマンド ボタンも含まれず、タブも表示されません。 これらの機能は、たとえば 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 を呼び出すことで、編集コントロールまたはチェック ボックスに対して行われた変更に応答し、ページが変更されたことをページ サイトに通知します。 通常、ページ サイトは、プロパティ ページ フレームの [適用] ボタンを有効または無効にすることで応答します。

Note

プロパティ ページでは、どのプロパティがユーザーによって変更されたかを正確に追跡して、変更されていないプロパティの更新を回避できるようにする必要があります。 この例では、元のプロパティの値を追跡し、変更を適用するときに、それらを UI の現在の値と比較することで、そのコードを実装しています。

ハウスキープ処理

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

// 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 基底クラスを参照する必要があります。次の typedefCDocProperties クラスに追加します。

typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;

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

オーバーライドする必要がある最初の IPropertyPageImpl メソッドは SetObjects です。 ここでは、単一のオブジェクトのみが渡されていること、および想定している 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;
}

Note

このページでは、オブジェクトのファイル名の設定をユーザーに許可するため、単一のオブジェクトのみをサポートするのが合理的であり、任意の 1 つの場所には 1 つのファイルのみが存在できます。

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

次の手順は、ページの初回の作成時に、基になるオブジェクトのプロパティ値を使用してプロパティ ページを初期化することです。

ここでは、プロパティの初期値は、ページのユーザーが変更を適用したときに比較対象としても使用するため、次のメンバーをクラスに追加する必要があります。

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

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

Note

この実装の先頭にあるm_bDirty に対するチェックは、Apply が 2 回以上呼び出されたときに、オブジェクトの不必要な更新を回避するための初期チェックと同一です。 変更のみが Document へのメソッド呼び出しになるように、各プロパティ値に対するチェックも存在します。

Note

DocumentFullName を読み取り専用プロパティとして公開します。 プロパティ ページに加えられた変更に基づいて、ドキュメントのファイル名を更新するには、Save メソッドを使用して、別の名前でファイルを保存する必要があります。 このため、プロパティ ページのコード自体をプロパティの取得または設定に制限する必要はありません。

プロパティ ページの表示

このページを表示するには、単純なヘルパー オブジェクトを作成する必要があります。 ヘルパー オブジェクトは、単一のオブジェクトに接続された単一のページを表示するための OleCreatePropertyFrame API を簡素化するメソッドを提供します。 このヘルパーは、Visual Basic から使用できるように設計されます。

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

項目 Value
メソッド名 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 開発環境で作成して実行できる単純なマクロを使用して、プロパティ ページとヘルパー オブジェクトをテストできます。 このマクロは、ヘルパー オブジェクトを作成した後、DocProperties プロパティ ページの ProgID と Visual Studio エディターで現在アクティブになっているドキュメントの IUnknown ポインターを使用して、ShowPage メソッドを呼び出します。 このマクロで必要なコードを次に示します。

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 の例