Partager via


Exemple : implémentation d’une page de propriétés

L’Assistant Page de propriétés ATL n’est pas disponible dans Visual Studio 2019 et versions ultérieures.

Cet exemple montre comment créer une page de propriétés qui affiche (et vous permet de modifier) les propriétés de l’interface Classes de documents.

L’exemple est basé sur l’exemple ATLPages.

Pour mettre en place cet exemple, vous allez :

Ajout de la classe de page de propriétés ATL

Commencez par créer un projet ATL pour un serveur DLL appelé ATLPages7. Utilisez maintenant l’Assistant Page de propriétés ATL pour générer une page de propriétés. Renseignez le champ Nom court de la page de propriétés avec la valeur DocProperties, puis basculez sur la page Chaînes pour définir les éléments spécifiques de page de propriétés comme indiqué dans le tableau ci-dessous.

Article Value
Titre TextDocument
Chaîne doc VCUE TextDocument Properties
Helpfile <blank>

Les valeurs que vous définissez sur cette page de l’Assistant sont envoyées au conteneur de page de propriétés lorsqu’il appelle IPropertyPage::GetPageInfo. Ce qu’il advient des chaînes après cette opération dépend du conteneur, mais en général, elles sont utilisées pour identifier votre page pour l’utilisateur. Le titre s’affiche généralement dans un onglet au-dessus de votre page, et la chaîne doc peut être affichée dans une barre d’état ou info-bulle (bien que le frame de propriété standard n’utilise pas cette chaîne du tout).

Remarque

Les chaînes que vous définissez ici sont stockées en tant que ressources de chaîne dans votre projet par l’Assistant. Vous pouvez facilement modifier ces chaînes à l’aide de l’éditeur de ressources si vous avez besoin de modifier ces informations une fois que le code de votre page a été généré.

Cliquez sur OK pour que l’Assistant génère votre page de propriétés.

Modification de la ressource de boîte de dialogue

Maintenant que votre page de propriétés est générée, vous devez ajouter quelques contrôles à la ressource de boîte de dialogue représentant votre page. Ajoutez un champ de modification, un contrôle de texte statique et une case à cocher, puis définissez leurs ID comme indiqué ci-dessous :

Screenshot of a dialog resource in the visual editor.

Ces contrôles sont utilisés pour afficher le nom de fichier du document et son état en lecture seule.

Remarque

La ressource de boîte de dialogue n’inclut pas de frame ou de boutons de commande, et, contrairement à ce que vous pouvez penser, ne ressemble pas à un onglet. Ces caractéristiques sont fournies par un frame de page de propriétés tel que celui créé en appelant OleCreatePropertyFrame.

Ajout de gestionnaires de messages

Avec les contrôles en place, vous pouvez ajouter des gestionnaires de messages pour mettre à jour l’état modifié de la page lorsque la valeur de l’un des contrôles change :

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

Ce code répond aux modifications apportées au contrôle d’édition ou à la case à cocher en appelant IPropertyPageImpl::SetDirty, qui informe le site de la page que la page a été modifiée. En général, le site de la page répond en activant ou désactivant un bouton Appliquer sur le frame de la page de propriété.

Remarque

Dans vos propres pages de propriétés, vous avez peut-être besoin de suivre précisément quelles propriétés ont été modifiées par l’utilisateur pour éviter de mettre à jour des propriétés qui n’ont pas été modifiées. Cet exemple implémente ce code en gardant une trace des valeurs de propriété d’origine et en les comparant avec les valeurs actuelles de l’IU au moment d’appliquer les modifications.

Tâches de nettoyage

Maintenant, ajoutez deux instructions #import à DocProperties.h afin que le compilateur reconnaisse l’interface 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

Vous devez également faire référence à la IPropertyPageImpl classe de base ; ajoutez ce qui suit typedef à la CDocProperties classe :

typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;

Substitution d’IPropertyPageImpl ::SetObjects

La première méthode IPropertyPageImpl que vous devez substituer est SetObjects. Ici, vous allez ajouter du code pour vérifier qu’un seul objet a été transmis et qu’il prend en charge l’interface Document que vous attendez :

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

Remarque

Il est judicieux de prendre en charge un seul objet de cette page, car vous allez autoriser l’utilisateur à définir le nom de fichier de l’objet ; seul un fichier peut exister à un emplacement.

Substitution d’IPropertyPageImpl ::Activate

L’étape suivante consiste à initialiser la page de propriétés avec les valeurs de propriété de l’objet sous-jacent lors de la création de la page.

Dans ce cas, vous devez ajouter les membres suivants à la classe, puisque vous allez également utiliser les valeurs de propriété initiales à des fins de comparaison lorsque les utilisateurs de la page appliquent leurs modifications :

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

L’implémentation de la classe de base de la méthode Activate est responsable de la création de la boîte de dialogue et de ses contrôles. Par conséquent, vous pouvez substituer cette méthode et ajouter votre propre initialisation après avoir appelé la classe de base :

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

Ce code utilise les méthodes COM de l’interface Document pour obtenir les propriétés qui vous intéresse. Il utilise ensuite les wrappers API Win32 fournis par CDialogImpl et ses classes de base pour afficher les valeurs de propriété pour l’utilisateur.

Substitution d’IPropertyPageImpl ::Apply

Lorsque les utilisateurs souhaitent appliquer leurs modifications aux objets, le site de la page de propriétés appelle la méthode Apply. C’est ici que vous devez inverser le code dans Activate : alors que Activate prenait les valeurs de l’objet pour les transmettre (push) aux contrôles sur la page de propriétés, Apply prend les valeurs des contrôles sur la page de propriétés et les transmet (push) à l’objet.

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

Remarque

La vérification par rapport à m_bDirty au début de cette implémentation est une vérification initiale pour éviter les mises à jour inutiles des objets si Apply est appelé plusieurs fois. Il existe également des vérifications par rapport à chacune des valeurs de propriété pour s’assurer que seules les modifications entraînent un appel de méthode vers Document.

Remarque

Document expose FullName en tant que propriété en lecture seule. Pour mettre à jour le nom de fichier du document en fonction des modifications apportées à la page de propriétés, vous devez utiliser la méthode Save pour enregistrer le fichier avec un nom différent. Par conséquent, le code d’une page de propriétés ne se limite pas à l’obtention ou à la définition de propriétés.

Affichage de la page de propriétés

Pour afficher cette page, vous devez créer un objet d’assistance simple. L’objet d’assistance offrira une méthode qui simplifie l’API OleCreatePropertyFrame pour l’affichage d’une page unique connectée à un objet unique. Cette assistance sera conçue de façon à pouvoir être utilisée dans Visual Basic.

Utilisez la boîte de dialogue Ajouter une classe et l’Assistant Objet simple ATL pour générer une nouvelle classe, et utilisez Helper comme nom court. Une fois la classe créée, ajoutez une méthode comme indiqué dans le tableau ci-dessous.

Article Value
Nom de la méthode ShowPage
Paramètres [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk

Le paramètre bstrCaption correspond à la légende à afficher comme titre pour la boîte de dialogue. Le paramètre bstrID est une chaîne représentant un CLSID ou ProgID de la page de propriétés à afficher. Le paramètre pUnk est le pointeur IUnknown de l’objet dont les propriétés sont configurées par la page de propriétés.

Implémentez la méthode comme indiqué ci-dessous :

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

Création d’une macro

Une fois que vous avez créé le projet, vous pouvez tester la page de propriétés et l’objet d’assistance à l’aide d’une macro simple que vous pouvez concevoir et exécuter dans l’environnement de développement Visual Studio. Cette macro va créer un objet d’assistance, puis appeler sa méthode ShowPage à l’aide du ProgID de la page de propriétés DocProperties et le pointeur IUnknown du document actuellement actif dans l’éditeur Visual Studio. Le code dont vous avez besoin pour cette macro est indiqué ci-dessous :

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

Lorsque vous exécutez cette macro, la page de propriétés s’affiche et indique le nom de fichier et l’état en lecture seule du document texte actuellement actif. L’état en lecture seule du document reflète uniquement la capacité à écrire dans le document dans l’environnement de développement ; cela n’affecte pas l’attribut en lecture seule du fichier sur le disque.

Voir aussi

Pages de propriétés
Exemple ATLPages