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 :
Ajouter la classe de page de propriétés ATL à l’aide de la boîte de dialogue Ajouter une classe et de l’Assistant Page de propriétés ATL.
Modifier la ressource de boîte de dialogue en ajoutant des contrôles aux propriétés intéressantes de l’interface
Document
.Ajouter des gestionnaires de messages pour que le site de la page de propriétés reste informé des modifications apportées par l’utilisateur.
Ajouter certaines instructions
#import
et un typedef dans la section Tâches de nettoyage.Substituer IPropertyPageImpl::SetObjects pour valider les objets transmis à la page de propriétés.
Substituer IPropertyPageImpl::Activate pour initialiser l’interface de la page de propriétés.
Substituer IPropertyPageImpl::Apply pour mettre à jour l’objet avec les dernières valeurs de propriété.
Afficher la page de propriétés en créant un objet d’assistance simple.
Créer une macro qui testera la page de propriétés.
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.
Élément | Valeur |
---|---|
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).
Notes
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.
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 :
Ces contrôles sont utilisés pour afficher le nom de fichier du document et son état en lecture seule.
Notes
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.
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é.
Notes
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.
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;
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;
}
Notes
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.
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.
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;
}
Notes
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
.
Notes
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.
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.
Élément | Valeur |
---|---|
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
);
}
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.