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:
Adicionar a classe de página de propriedades da ATL usando a caixa de diálogo Adicionar Classe e o Assistente de Página de Propriedades da ATL.
Editar o recurso de caixa de diálogo adicionando novos controles para as propriedades interessantes da interface
Document
.Adicionar manipuladores de mensagens para manter o site da página de propriedades informado sobre as alterações feitas pelo usuário.
Adicionar algumas instruções
#import
e um typedef na seção Manutenção.Substituir IPropertyPageImpl::SetObjects para validar os objetos que estão sendo passados para a página de propriedades.
Substituir IPropertyPageImpl::Activate para inicializar a interface da página de propriedades.
Substituir IPropertyPageImpl::Apply para atualizar o objeto com os valores de propriedade mais recentes.
Exibir a página de propriedades criando um objeto auxiliar simples.
Criar uma macro que testará a página de propriedades.
Adicionando a classe de página de propriedades da 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 |
---|---|
Title | 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:
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.