例: プロパティ ページの実装
ATL プロパティ ページ ウィザードは、Visual Studio 2019 以降では使用できません。
この例では、ドキュメント クラス インターフェイスのプロパティを表示する (変更することもできる)プロパティ ページの作成方法を示します。
この例は、ATLPages の例をベースにしています。
この例を完了するには、以下を行います。
[クラスの追加] ダイアログ ボックスと ATL プロパティ ページ ウィザードを使用して、Add the ATL プロパティ ページ クラスを追加します。
Document
インターフェイスの関心のあるプロパティ用の新しいコントロールを追加することで、ダイアログ リソースを編集します。ユーザーが行った変更のプロパティ ページ サイトへの通知を維持するために、メッセージ ハンドラーを追加します。
いくつかの
#import
ステートメントと typedef を ハウスキープ処理セクションに追加します。プロパティ ページに渡されるオブジェクトを検証するために IPropertyPageImpl::SetObjects をオーバーライドします。
プロパティ ページのインターフェイスを初期化するために IPropertyPageImpl::Activate をオーバーライドします。
オブジェクトを最新のプロパティ値で更新するために IPropertyPageImpl::Apply をオーバーライドします。
シンプルなヘルパー オブジェクトを作成することで、プロパティ ページを表示します。
プロパティ ページをテストするマクロを作成します。
ATL プロパティ ページ クラスの追加
まず、ATLPages7
という名前の DLL サーバー用の新しい ATL プロジェクトを作成します。 次に、ATL プロパティ ページ ウィザードを使用してプロパティ ページを生成します。 プロパティ ページに DocProperties という短い名前を付けた後、[文字列] ページに切り替えて、次の表に示すプロパティ ページに固有の項目を設定します。
項目 | Value |
---|---|
Title | TextDocument |
Doc String | VCUE TextDocument のプロパティ |
Helpfile | <blank> |
このウィザード ページに設定した値は、プロパティ ページ コンテナーが IPropertyPage::GetPageInfo
を呼び出したときに返されます。 その後の文字列の処理はコンテナーによって決まりますが、通常は、ユーザーがページを識別するために使用されます。 Title は、通常、ページ上部のタブに表示され、Doc String は、ステータス バーまたはツールヒント上に表示できます (ただし、標準プロパティ フレームではこの文字列はまったく使用されません)。
Note
ここで設定した文字列は、ウィザードによって、文字列リソースとしてプロジェクトに格納されます。 ページのコードが生成された後でこれらの文字列の情報を変更する必要がある場合は、リソース エディターを使用して簡単に編集できます。
[OK] をクリックして、ウィザードでプロパティ ページを生成します。
ダイアログ リソースの編集
これでプロパティ ページが生成されたので、ページを表すダイアログ リソースにいくつかのコントロールを追加する必要があります。 次に示すように、編集ボックス、静的テキスト コントロール、およびチェック ボックスを追加し、それらの ID を設定します。
これらのコントロールは、ドキュメントのファイル名とその読み取り専用状態を表示するために使用されます。
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
基底クラスを参照する必要があります。次の typedef
を CDocProperties
クラスに追加します。
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
Document
は FullName
を読み取り専用プロパティとして公開します。 プロパティ ページに加えられた変更に基づいて、ドキュメントのファイル名を更新するには、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
このマクロを実行すると、ファイル名と現在アクティブなテキスト ドキュメントの読み取り専用状態を示すプロパティ ページが表示されます。 ドキュメントの読み取り専用状態は、開発環境でドキュメントに書き込むことができることを反映しているだけです。それは、ディスク上のファイルの読み取り専用属性には影響しません。