Compartilhar via


Exemplo: implementação de uma página de propriedades

O assistente de Página de Propriedades da ATL não está disponível no Visual Studio 2019 e versões posteriores.

Este exemplo mostra como criar uma página de propriedades que exibe (e permite alterar) propriedades da interface Classes de documento.

O exemplo se baseia na amostra de ATLPages.

Para concluir este exemplo, você vai:

Adicionando a classe de página de propriedade ATL

Primeiro, crie um novo projeto ATL para um servidor DLL denominado ATLPages7. Agora, use o Assistente de Página de Propriedades da ATL para gerar uma página de propriedades. Dê à página de propriedades um Nome Curto de DocProperties, em seguida, alterne para a página Cadeias de Caracteres para definir itens específicos da página de propriedades, conforme mostrado na tabela abaixo.

Item Valor
Título TextDocument
Doc String Propriedades de TextDocument VCUE
Helpfile <em branco>

Os valores que você define nessa página do assistente serão retornados ao contêiner da página de propriedades quando ele chamar IPropertyPage::GetPageInfo. O que acontece com as cadeias de caracteres depois disso depende do contêiner, mas, normalmente, elas são usadas para identificar sua página para o usuário. Geralmente, o Título aparece em uma guia acima da sua página e o Doc String pode ser exibido em uma barra de status ou Dica de Ferramenta (embora o quadro da propriedade padrão não use essa cadeia de caracteres).

Observação

As cadeias de caracteres que você define aqui são armazenadas pelo assistente como recursos de cadeia de caracteres em seu projeto. É possível editar com facilidade essas cadeias de caracteres usando o editor de recursos se precisar alterar essas informações depois que o código da página tiver sido gerado.

Clique em OK para que o assistente gere a página de propriedades.

Editando o recurso de caixa de diálogo

Agora que sua página de propriedades foi gerada, você precisará adicionar alguns controles ao recurso de caixa de diálogo que representem a página. Adicione uma caixa de edição, um controle de texto estático e uma caixa de seleção e defina as respectivas IDs, conforme mostrado abaixo:

Screenshot of a dialog resource in the visual editor.

Esses controles serão usados para exibir o nome de arquivo do documento e seu status somente leitura.

Observação

O recurso de caixa de diálogo não inclui um quadro ou botões de comando, e o seu layout não apresenta guias como talvez você esperasse. Esses recursos são fornecidos por um quadro de página de propriedades, como o que foi criado chamando OleCreatePropertyFrame.

Adicionando manipuladores de mensagens

Com os controles definidos, você pode adicionar manipuladores de mensagens para atualizar o status anormal da página quando o valor de qualquer um dos controles é alterado:

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

Esse código responde às alterações feitas no controle de edição ou na caixa de seleção chamando IPropertyPageImpl::SetDirty, que informa o site da página que a página foi alterada. Normalmente, o site da página responderá habilitando ou desabilitando um botão Aplicar no quadro da página de propriedades.

Observação

Em suas próprias páginas de propriedades, talvez você precise acompanhar quais propriedades precisamente foram alteradas pelo usuário para que seja possível evitar a atualização de propriedades que não foram alteradas. Este exemplo implementa esse código acompanhando os valores da propriedade original e comparando-os com os valores atuais da interface do usuário no momento de aplicar as alterações.

Manutenção

Agora, adicione algumas instruções #import a DocProperties.h para que o compilador saiba sobre a 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

Também é necessário consultar a classe base IPropertyPageImpl, adicionando o seguinte typedef à classe CDocProperties:

typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;

Substituindo IPropertyPageImpl::SetObjects

O primeiro método IPropertyPageImpl que você precisa substituir é SetObjects. Aqui, você adicionará código para verificar que apenas um único objeto foi passado e que ele é compatível com a interface Document que você está esperando:

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

Observação

Faz sentido dar suporte apenas a um único objeto para essa página, pois você permitirá que o usuário defina o nome do arquivo do objeto – apenas um arquivo pode existir em qualquer local.

Substituindo IPropertyPageImpl::Activate

A próxima etapa é inicializar a página de propriedades com os valores de propriedade do objeto subjacente quando a página é criada pela primeira vez.

Nesse caso, será preciso adicionar os seguintes membros à classe, uma vez que você também usará os valores iniciais da propriedade para comparação quando os usuários da página aplicarem as alterações:

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

A implementação da classe base do método Activate é responsável por criar a caixa de diálogo e seus controles, de modo que você possa substituir esse método e adicionar sua própria inicialização após chamar a classe 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;
}

Esse código usa os métodos COM da interface Document para obter as propriedades em que você está interessado. Ele usa os wrappers de API do Win32 fornecidos por CDialogImpl e suas classes base para exibir os valores de propriedade ao usuário.

Substituindo IPropertyPageImpl::Apply

Quando os usuários quiserem aplicar suas alterações nos objetos, o site da página de propriedades chamará o método Apply. Esse é o lugar para fazer o inverso do código em Activate – enquanto Activate extraiu valores do objeto e efetuou push deles nos controles da página de propriedades, Apply usa os valores dos controles na página de propriedades e os envia ao 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;
}

Observação

A verificação em relação a m_bDirty no início dessa implementação é uma verificação inicial para evitar atualizações desnecessárias de objetos se Apply for chamado mais de uma vez. Também há verificações em relação a cada um dos valores de propriedade para garantir que somente as alterações resultem em uma chamada de método para Document.

Observação

Document expõe FullName como uma propriedade somente leitura. Para atualizar o nome do arquivo do documento com base nas alterações feitas na página de propriedades, é preciso usar o método Save para salvar o arquivo com outro nome. Assim, o código em uma página de propriedades não precisa se limitar a obter ou definir propriedades.

Exibindo a página de propriedades

Para exibir essa página, você precisa criar um objeto auxiliar simples. O objeto auxiliar fornecerá um método que simplifica a API OleCreatePropertyFrame para a exibição de uma única página conectada a um único objeto. Esse auxiliar será criado para que possa ser usado no Visual Basic.

Use a caixa de diálogo Adicionar Classe e o Assistente de Objeto Simples de ATL para gerar uma nova classe e usar Helper como seu nome curto. Depois de criado, adicione um método, conforme mostrado na tabela abaixo.

Item Valor
Nome do método ShowPage
Parâmetros [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk

O parâmetro bstrCaption é a legenda a ser exibida como o título da caixa de diálogo. O parâmetro bstrID é uma cadeia de caracteres que representa uma CLSID ou ProgID da página de propriedades a ser exibida. O parâmetro pUnk será o ponteiro IUnknown do objeto cujas propriedades serão definidas pela página de propriedades.

Implemente o método, conforme mostrado abaixo:

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

Criando uma macro

Após criação do projeto, você poderá testar a página de propriedades e o objeto auxiliar que está usando uma macro simples que pode ser criada e executada no ambiente de desenvolvimento do Visual Studio. Essa macro criará um objeto auxiliar e chamará seu método ShowPage usando ProgID da página de propriedades DocProperties e o ponteiro IUnknown do documento ativo no momento no editor do Visual Studio. O código necessário para essa macro é mostrado abaixo:

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

Quando você executar essa macro, a página de propriedades será exibida mostrando o nome do arquivo e o status somente leitura do documento de texto ativo no momento. O estado somente leitura do documento reflete somente a capacidade de gravar no documento no ambiente de desenvolvimento; ele não afeta o atributo somente leitura do arquivo no disco.

Confira também

Páginas de propriedades
Exemplo de páginas ATL