Compartir a través de


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

Captura de pantalla de un recurso de diálogo en el editor visual.

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.

Consulte también

Páginas de propiedades
Ejemplos de Visual C++