共用方式為


範例︰實作屬性頁

Visual Studio 2019 及更新版本中未提供 ATL 屬性頁精靈。

此範例示範如何建置可顯示 (並可讓您變更) 文件類別介面屬性的屬性頁。

此範例是以 ATLPages 範例為基礎。

若要完成此範例,您將會:

新增 ATL 屬性頁類別

首先,為 DLL 伺服器建立一個稱為 ATLPages7 的新 ATL 專案。 現在,使用 ATL 屬性頁精靈產生屬性頁。 為屬性頁提供一個 DocProperties簡短名稱,然後切換到 [字串]頁面,以設定屬性頁面專屬的項目,如下表所示。

項目
標題 TextDocument
Doc 字串 VCUE TextDocument 屬性
Helpfile <空白>

您在這個精靈頁面上設定的值將會在呼叫 IPropertyPage::GetPageInfo 時,傳回至屬性頁容器。 字串在此之後發生的變化取決於容器,但通常用來向使用者識別您的頁面。 Title 通常會在出現您頁面上方的索引標籤中,而 Doc 字串則可能會顯示在狀態列或工具提示中 (但標準屬性框架完全不會使用此字串)。

注意

精靈會將您在此處設定的字串儲存為您專案中的字串資源。 如果您需要在產生頁面的程式碼之後變更此資訊,您可以使用資源編輯器,輕鬆地編輯這些字串。

按一下 [確定],讓精靈產生屬性頁。

編輯對話方塊資源

既然已經產生屬性頁,您必須將幾個控制項新增至代表您頁面的對話方塊資源。 新增編輯方塊、靜態文字控制項以及核取方塊,並設定其識別碼,如下所示:

Screenshot of a dialog resource in the visual editor.

這些控制項將用來顯示文件的檔案名稱及其唯讀狀態。

注意

對話方塊資源不包含框架或命令按鈕,也沒有您可能預期的索引標籤式外觀。 屬性頁框架會提供這些功能,例如,透過呼叫 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 以回應對編輯控制項或核取方塊所做的變更,如此會通知頁面網站頁面已變更。 頁面網站通常會透過啟用或停用屬性頁框架上的的 [套用] 按鈕來回應。

注意

在您自己的屬性頁中,您可能需要精確地追蹤使用者已經變更哪些屬性,讓您可以避免更新未曾變更的屬性。 此範例會追蹤原始屬性值,並在套用變更時將原始屬性值與來自 UI 的目前值相比較,藉此實作該程式碼。

內務處理

現在將一些 #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;
}

注意

對此頁面而言,僅支援單一物件很合理,因為您將允許使用者設定物件的檔案名稱,而且任何一個位置只能存在一個檔案。

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

注意

在此實作一開始,針對 m_bDirty 的檢查是為了在多次呼叫 Apply 時,避免不必要的物件更新所進行的。 系統也會針對每個屬性值進行檢查,以確保只有變更會導致對 Document 進行方法呼叫。

注意

Document 會將 FullName 當作唯讀屬性公開。 若要根據對屬性頁所做的變更來更新文件的檔案名稱,您必須使用 Save 方法,使用不同的名稱儲存檔案。 因此,屬性頁中的程式碼不必限制自己取得或設定屬性。

顯示內容頁

若要顯示此頁面,您需要建立一個簡單的協助程式物件。 協助程式物件會提供一種簡化 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 開發環境中建立並執行的簡單巨集,測試屬性頁和協助程式物件。 此巨集將會建立一個協助程式物件,然後使用 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 範例