Beispiel: Implementieren einer Eigenschaftenseite

Der ATL-Eigenschaftenseiten-Assistent ist in Visual Studio 2019 und höher nicht verfügbar.

Dieses Beispiel zeigt, wie Sie eine Eigenschaftenseite erstellen, auf der Eigenschaften der Document Classes-Schnittstelle angezeigt werden (und auf der Sie die Eigenschaften ändern können).

Das Beispiel basiert auf dem ATLPages-Beispiel.

In diesem Beispiel führen Sie folgende Aktionen aus:

Hinzufügen der ATL-Eigenschaftsseitenklasse

Zunächst erstellen Sie ein neues ATL-Projekt für einen DLL-Server namens ATLPages7. Danach verwenden Sie den ATL-Eigenschaftenseiten-Assistenten, um eine Eigenschaftenseite zu generieren. Weisen Sie der Eigenschaftenseite den KurznamenDocProperties zu, und wechseln Sie dann zur Seite Zeichenfolgen, um für die Eigenschaftenseite spezifische Elemente festzulegen, wie in der unten stehenden Tabelle gezeigt.

Element Wert
Titel TextDocument
Dokumentzeichenfolge VCUE TextDocument Properties
Hilfedatei <leer>

Die Werte, die Sie auf dieser Seite des Assistenten festlegen, werden an den Container der Eigenschaftenseite zurückgegeben, wenn dieser IPropertyPage::GetPageInfo aufruft. Was danach mit den Zeichenfolgen geschieht, hängt vom Container ab, aber in der Regel werden sie verwendet, um Ihre Seite für den Benutzer zu identifizieren. Der Titel wird üblicherweise auf einer Registerkarte über Ihrer Seite angezeigt, die Dokumentzeichenfolge in einer Statusleiste oder QuickInfo (allerdings verwendet der Frame für Standardeigenschaften diese Zeichenfolge gar nicht).

Hinweis

Die Zeichenfolgen, die Sie hier festlegen, werden vom Assistenten als Zeichenfolgenressourcen in Ihrem Projekt gespeichert. Sie können diese Zeichenfolgen ganz einfach mit dem Ressourcen-Editor bearbeiten, wenn Sie diese Informationen ändern müssen, nachdem der Code für Ihre Seite generiert wurde.

Klicken Sie auf OK, damit der Assistent Ihre Eigenschaftenseite generiert.

Bearbeiten der Dialogressource

Nach dem Generieren der Eigenschaftenseite müssen Sie der Dialogfeldressource, die Ihre Seite repräsentiert, einige Steuerelemente hinzufügen. Fügen Sie ein Bearbeitungsfeld, ein statisches Textsteuerelement und ein Kontrollkästchen hinzu, und legen Sie die IDs wie im Folgenden gezeigt fest:

Screenshot of a dialog resource in the visual editor.

Mit diesen Steuerelementen werden der Dateiname und der schreibgeschützte Status des Dokuments angezeigt.

Hinweis

Die Dialogfeldressource enthält weder einen Frame noch Befehlsschaltflächen und weist auch nicht Registerkartenformat auf, das Sie möglicherweise erwartet haben. Diese Features werden durch einen Eigenschaftenseitenframe bereitgestellt, z.B. durch den, der durch den Aufruf von OleCreatePropertyFrame erstellt wird.

Hinzufügen von Nachrichtenhandlern

Wenn die Steuerelemente vorhanden sind, können Sie Meldungshandler hinzufügen, um den geänderten Status der Seite zu aktualisieren, wenn sich der Wert eines der Steuerelemente ändert:

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

Dieser Code reagiert auf Änderungen am Bearbeitungssteuerelement oder Kontrollkästchen durch Aufruf von IPropertyPageImpl::SetDirty, wodurch die Site der Seite darüber informiert wird, dass die Seite sich geändert hat. In der Regel reagiert die Site der Seite durch Aktivieren oder Deaktivieren einer Anwenden-Schaltfläche im Frame der Eigenschaftenseite.

Hinweis

Auf Ihren eigenen Eigenschaftenseiten müssen Sie möglicherweise nachverfolgen, welche Eigenschaften genau vom Benutzer geändert wurden, um zu vermeiden, dass Eigenschaften aktualisiert werden, die sich nicht geändert haben. Dieses Beispiel implementiert diesen Code, indem die ursprünglichen Eigenschaftswerte nachverfolgt und mit den aktuellen Werten der Benutzeroberfläche verglichen werden, wenn die Änderungen angewendet werden sollen.

Housekeeping

Fügen Sie jetzt einige #import-Anweisungen zu „DocProperties.h“ hinzu, sodass der Compiler die Document-Schnittstelle kennt:

// 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

Sie müssen auch auf die IPropertyPageImpl Basisklasse verweisen. Fügen Sie der Klasse Folgendes typedef hinzu CDocProperties :

typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;

Überschreiben von IPropertyPageImpl::SetObjects

Die erste IPropertyPageImpl-Methode, die Sie überschreiben müssen, ist SetObjects. Hier fügen Sie Code hinzu, um zu überprüfen, ob nur ein einzelnes Objekt übergeben wurde und dass es die Document-Schnittstelle unterstützt, die Sie erwarten:

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

Hinweis

Es ist sinnvoll, für diese Seite nur ein einzelnes Objekt zu unterstützen, da Sie dem Benutzer erlauben, den Dateinamen des Objekts festzulegen – und an jedem Speicherort kann nur eine einzige Daten vorhanden sein.

Überschreiben von IPropertyPageImpl::Activate

Der nächste Schritt besteht darin, die Eigenschaftenseite mit den Eigenschaftswerten des zugrunde liegenden Objekts zu initialisieren, wenn die erste Seite erstellt wird.

In diesem Fall sollten Sie die folgenden Member zur Klasse hinzufügen, da Sie auch die anfänglichen Eigenschaftswerte verwenden, um Vergleiche durchzuführen, wenn Benutzer der Seite ihre Änderungen anwenden:

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

Die Implementierung der Basisklasse der Activate-Methode ist für das Erstellen des Dialogfelds und der zugehörigen Steuerelemente zuständig, daher können Sie diese Methode überschreiben und nach dem Aufruf der Basisklasse Ihre eigene Initialisierung hinzufügen:

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

Dieser Code verwendet die COM-Methoden der Document-Schnittstelle, um die Eigenschaften abzurufen, die Sie interessieren. Dann verwendet der Code die Win32-API-Wrapper, die von CDialogImpl und den zugehörigen Basisklassen bereitgestellt werden, um dem Benutzer die Eigenschaftswerte anzuzeigen.

Überschreiben von IPropertyPageImpl::Apply

Wenn Benutzer ihre Änderungen an den Objekten anwenden möchten, ruft die Site der Eigenschaftenseite die Methode Apply auf. Hier erfolgt jetzt das Gegenteil des Codes in Activate: Während Activate Werte aus dem Objekt akzeptiert und an die Steuerelemente auf der Eigenschaftenseite übermittelt hat, akzeptiert Apply Werte aus den Steuerelementen auf der Eigenschaftenseite und übermittelt sie an das Objekt.

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

Hinweis

Die Prüfung auf m_bDirty am Anfang dieser Implementierung ist eine anfängliche Prüfung, um unnötige Aktualisierungen der Objekte zu vermeiden, wenn Apply mehr als einmal aufgerufen wird. Es gibt auch Prüfungen für jeden der Eigenschaftswerte, um sicherzustellen, dass nur Änderungen zu einem Methodenaufruf von Document führen.

Hinweis

Document macht FullName als schreibgeschützte Eigenschaft verfügbar. Um den Dateinamen des Dokuments basierend auf Änderungen zu aktualisieren, die an der Eigenschaftenseite vorgenommen wurden, müssen Sie die Save-Methode verwenden, um die Datei unter einem anderen Namen zu speichern. Daher sollte sich der Code auf einer Eigenschaftenseite nicht auf das Abrufen und Festlegen von Eigenschaften beschränken.

Anzeigen der Eigenschaftenseite

Um diese Seite anzuzeigen, müssen Sie ein einfaches Hilfsobjekt erstellen. Das Hilfsobjekt bietet eine Methode, die die OleCreatePropertyFrame-API zum Anzeigen einer einzelnen Seite vereinfacht, die mit einem einzelnen Objekt verknüpft ist. Dieses Hilfsobjekt ist so konzipiert, dass es mit Visual Basic verwendet werden kann.

Verwenden Sie das Dialogfeld „Klasse hinzufügen“ und den ATL-Assistenten für einfache Objekte, um eine neue Klasse zu generieren, und verwenden Sie Helper als Kurznamen. Nach dem Erstellen fügen Sie eine Methode hinzu, wie in der unten stehenden Tabelle gezeigt.

Element Wert
Methodenname ShowPage
Parameter [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk

Der bstrCaption-Parameter ist die Beschriftung, die als Titel des Dialogfelds angezeigt werden soll. Der bstrID-Parameter ist eine Zeichenfolge, die entweder eine CLSID oder eine ProgID der anzuzeigenden Eigenschaftenseite repräsentiert. Der pUnk-Parameter ist der IUnknown-Zeiger des Objekts, dessen Eigenschaften durch die Eigenschaftenseite konfiguriert werden.

Implementieren Sie die unten gezeigte Methode:

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

Erstellen eines Makros

Nachdem Sie das Projekt erstellt haben, können Sie die Eigenschaftenseite und das Hilfsobjekt mit einem einfachen Makro testen, das Sie in der Visual Studio-Entwicklungsumgebung erstellen und ausführen. Dieses Makro erstellt ein Hilfsobjekt, und ruft dann die ShowPage-Methode auf; dabei verwendet das Makro die ProgID der Eigenschaftenseite DocProperties und den IUnknown-Zeiger des derzeit im Visual Studio-Editor aktiven Dokuments. Hier sehen Sie den für dieses Makro erforderlichen Code:

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

Wenn Sie dieses Makro ausführen, wird die Eigenschaftenseite mit dem Dateinamen und dem schreibgeschützten Status des derzeit aktiven Textdokuments angezeigt. Der schreibgeschützte Status des Dokuments spiegelt nur die Möglichkeit wider, in der Entwicklungsumgebung in das Dokument zu schreiben, und wirkt sich nicht auf das Schreibschutzattribut der Datei auf dem Datenträger aus.

Siehe auch

Eigenschaftenseiten
ATLPages-Beispiel