Ejemplo: implementación de una página de propiedades
El Asistente para páginas de propiedades ATL no está disponible en Visual Studio 2019 ni en versiones posteriores.
En este ejemplo puede ver cómo crear una página de propiedades que muestre (y le permita cambiar) propiedades de la interfaz Clases de documento.
El ejemplo se basa en el ejemplo ATPages.
Para completar este ejemplo, hará lo siguiente:
Agregar la clase de la página de propiedades ATL mediante el cuadro de diálogo Agregar clase y el Asistente para páginas de propiedades ATL.
Editar el recurso de diálogo agregando nuevos controles para las propiedades interesantes de la interfaz
Document
.Agregar controladores de mensajes para mantener informado al sitio de la página de propiedades de los cambios realizados por el usuario.
Agregar algunas instrucciones
#import
y un typedef en la sección Mantenimiento.Invalidar IPropertyPageImpl::SetObjects para validar los objetos que se pasan a la página de propiedades.
Invalidar IPropertyPageImpl::Activate para inicializar la interfaz de la página de propiedades.
Invalidar IPropertyPageImpl:: Apply para actualizar el objeto con los valores de propiedad más recientes.
Mostrar la página de propiedades creando un objeto auxiliar sencillo.
Crear una macro que probará la página de propiedades.
Agregar la clase de página de propiedades ATL
En primer lugar, cree un nuevo proyecto ATL para un servidor DLL llamado ATLPages7
. Ahora utilice el Asistente para páginas de propiedades ATL para generar una página de propiedades. Asigne a la página de propiedades un Nombre corto de DocProperties y, a continuación, cambie a la página Cadenas para establecer elementos específicos de la página de propiedades como se muestra en la siguiente tabla.
Elemento | Valor |
---|---|
Título | TextDocument |
Cadena de documento | Propiedades VCUE TextDocument |
Archivo de ayuda | <en blanco> |
Los valores que establezca en esta página del asistente se devolverán al contenedor de la página de propiedades cuando llame a IPropertyPage::GetPageInfo
. Lo que les ocurra a las cadenas después de eso dependerá del contenedor, pero normalmente se usarán para identificar su página para el usuario. El título suele aparecer en una pestaña situada sobre su página y la cadena de documento se puede mostrar en una barra de estado o en la información sobre herramientas (aunque el marco de propiedades no usa esta cadena en absoluto).
Nota:
El asistente almacenará las cadenas que establezca aquí como recursos de cadena en el proyecto. Puede editar fácilmente estas cadenas mediante el editor de recursos si tiene que cambiar esta información una vez que se haya generado el código para su página.
Haga clic en Aceptar para que el asistente genere su página de propiedades.
Edición del recurso de diálogo
Ahora que se ha generado su página de propiedades, tendrá que agregar algunos controles al recurso de diálogo que represente su página. Agregue un cuadro de edición, un control de texto estático y una casilla, y establezca sus identificadores como se muestra a continuación:
Estos controles se usarán para mostrar el nombre de archivo del documento y su estado de solo lectura.
Nota:
El recurso de diálogo no incluye un marco ni botones de comando, ni tampoco tiene pestañas como tal vez haya imaginado. Un marco de la página de propiedades como el que se creó llamando a OleCreatePropertyFrame proporciona estas características.
Adición de controladores de mensajes
Con los controles en contexto, puede agregar controladores de mensajes para actualizar el estado con errores de la página al cambiar el valor de cualquiera de los controles:
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;
}
Este código responde a los cambios realizados en la casilla o control de edición llamando a IPropertyPageImpl::SetDirty, que informa al sitio de la página de que esta ha cambiado. Normalmente, el sitio de la página responderá habilitando o deshabilitando el botón Aplicar en el marco de la página de propiedades.
Nota:
En sus propias páginas, es posible que tenga que realizar un seguimiento de exactamente las propiedades que el usuario ha modificado para que pueda evitar la actualización de las propiedades sin cambios. En este ejemplo se implementa ese código realizando un seguimiento de los valores de propiedad originales y comparándolos con los valores actuales desde la interfaz de usuario en el momento de aplicarse los cambios.
Mantenimiento
Ahora agregue un par de instrucciones #import
a DocProperties.h para que el compilador conozca la interfaz 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
También necesitará hacer referencia a la IPropertyPageImpl
clase base; agregue lo siguiente typedef
a la CDocProperties
clase:
typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;
Invalidación de IPropertyPageImpl::SetObjects
El primer método IPropertyPageImpl
que debe invalidar es SetObjects. Aquí agregará código para comprobar que solo se ha pasado un único objeto y que admite la interfaz Document
que espera:
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;
}
Nota:
Tiene sentido admitir solo un único objeto para esta página, ya que permitirá al usuario establecer el nombre de archivo del objeto. Solo puede existir un archivo en cualquier ubicación.
Invalidación de IPropertyPageImpl::Activate
El siguiente paso consiste en inicializar la página de propiedades con los valores de propiedad del objeto subyacente la primera vez que se crea la página.
En este caso, debe agregar los siguientes miembros a la clase, ya que también usará los valores de propiedad iniciales para la comparación cuando los usuarios de la página apliquen sus cambios:
CComBSTR m_bstrFullName; // The original name
VARIANT_BOOL m_bReadOnly; // The original read-only state
La implementación de la clase base del método Activate es responsable de la creación del cuadro de diálogo y sus controles, por lo que puede invalidar este método y agregar su propia inicialización después de llamar a la clase 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;
}
Este código usa los métodos COM de la interfaz Document
para obtener las propiedades que le interesan. A continuación, usa los contenedores de la API Win32 que proporciona CDialogImpl y su clase base para mostrar los valores de propiedad al usuario.
Invalidación de IPropertyPageImpl::Apply
Cuando los usuarios deseen aplicar sus cambios a los objetos, el sitio de la página de propiedades llamará al método Apply. Este es el lugar en el que realizar el proceso inverso del código en Activate
(mientras que Activate
tomó valores del objeto y los insertó en los controles de la página de propiedades, Apply
toma valores de los controles de la página de propiedades y los inserta en el objeto).
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;
}
Nota:
La comparación con m_bDirty al principio de esta implementación es una comprobación inicial para evitar actualizaciones innecesarias de los objetos si se llama a Apply
más de una vez. Asimismo se realizan comparaciones con cada uno de los valores de propiedad para asegurarse de que solo los cambios dan como resultado una llamada de método a Document
.
Nota:
Document
expone FullName
como propiedad de solo lectura. Para actualizar el nombre de archivo del documento en función de los cambios realizados en la página de propiedades, debe usar el método Save
para guardar el archivo con otro nombre. Por tanto, el código de una página de propiedades no tiene por qué limitarse a obtener o establecer propiedades.
Mostrar la página de propiedades
Para mostrar esta página, debe crear un objeto auxiliar sencillo. El objeto auxiliar proporcionará un método que simplifica la API OleCreatePropertyFrame
para mostrar una sola página conectada a un único objeto. Este asistente se diseñará para que pueda usarse desde Visual Basic.
Utilice el cuadro de diálogo Agregar clase y el Asistente para objetos simples ATL para generar una nueva clase y usar Helper
como su nombre corto. Una vez creada, agregue un método como se muestra en la siguiente tabla.
Elemento | Valor |
---|---|
Nombre del método | ShowPage |
Parámetros | [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk |
El parámetro bstrCaption es el título que se va a mostrar como título del cuadro de diálogo. El parámetro bstrID es una cadena que representa un CLSID o un ProgID de la página de propiedades que se va a mostrar. El parámetro pUnk será el puntero IUnknown
del objeto cuyas propiedades configurará la página de propiedades.
Implemente el método como se muestra a continuación:
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
);
}
Crear una macro
Una vez que ha creado el proyecto, puede probar la página de propiedades y el objeto auxiliar mediante una macro sencilla que puede crear y ejecutar en el entorno de desarrollo de Visual Studio. Esta macro creará un objeto auxiliar y, a continuación, llamará a su método ShowPage
mediante el ProgID de la página de propiedades DocProperties y el puntero IUnknown
del documento actualmente activo en el editor de Visual Studio. El código que necesita para esta macro se muestra a continuación:
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
Al ejecutar esta macro, la página de propiedades aparecerá mostrando el nombre de archivo y el estado de solo lectura del documento de texto actualmente activo. El estado de solo lectura del documento solo refleja la capacidad de escribir en el documento en el entorno de desarrollo; no afecta al atributo de solo lectura del archivo en el disco.