Пример. Реализация страницы свойств
Мастер страницы свойств в ATL недоступен в Visual Studio 2019 и более поздних версиях.
В этом примере показано, как создать страницу свойств, которая отображает (и позволяет изменять) свойства интерфейса класса документов.
Пример основан на примере ATLPages.
Для работы с этим примером вам необходимо сделать следующее:
Добавьте класс страницы свойств ATL, используя диалоговое окно "Добавление класса" и мастер страницы свойств ATL.
Измените ресурс диалогового окна, добавив новые элементы управления для нужных свойств интерфейса
Document
.Добавьте обработчики сообщений, чтобы информировать сайт страницы свойств об изменениях, внесенных пользователем.
Добавьте несколько операторов
#import
и определение типа в разделе Действия по обслуживанию.Переопределите IPropertyPageImpl::SetObjects, чтобы проверить объекты, передаваемые на страницу свойств.
Переопределите IPropertyPageImpl::Activate, чтобы инициализировать интерфейс страницы свойств.
Переопределите IPropertyPageImpl::Apply, чтобы обновить последние значения свойств для объекта.
Отобразите страницу свойств, создав простой вспомогательный объект.
Создайте макрос, который будет проверять страницу свойств.
Добавление класса страницы свойств ATL
Сначала создайте проект ATL для сервера DLL с именем ATLPages7
. Теперь с помощью мастера страницы свойств ATL создайте страницу свойств. Дайте странице свойств короткое имя из DocProperties, затем перейдите на страницу строк, чтобы задать элементы, относящиеся к странице свойства, как показано в таблице ниже.
Товар | Значение |
---|---|
Заголовок | TextDocument |
Строка документа | Свойства TextDocument VCUE |
Helpfile | <пусто> |
Значения, заданные на этой странице мастера, будут возвращены в контейнер страницы свойств при вызове IPropertyPage::GetPageInfo
. После этого происходящее со строками зависит от контейнера, но обычно они будут использоваться для идентификации вашей страницы для пользователя. Заголовок обычно отображается на вкладке над вашей страницей, а строка документа может отображаться в строке состояния или всплывающей подсказке (хотя стандартный фрейм свойств вообще не использует эту строку).
Примечание.
Заданные здесь строки сохраняются мастером в качестве строковых ресурсов в вашем проекте. Вы можете легко изменять эти строки, используя редактор ресурсов, если вам нужно изменить эти сведения после того, как был создан код для вашей страницы.
Нажмите кнопку ОК, чтобы мастер создал страницу свойств.
Изменение ресурса диалогового окна
Теперь, когда создана страница, нужно добавить несколько элементов управления в ресурс диалогового окна, представляющего вашу страницу. Добавьте поле редактирования, статический текстовый элемент управления, флажок и настройте их идентификаторы, как показано ниже:
Эти элементы управления будут использоваться для отображения имени файла документа и его состояния "только для чтения".
Примечание.
Ресурс диалогового окна не содержит фрейм или кнопки команд, а также не имеет ожидаемых вкладок. Эти функции предоставляются фреймом страницы свойств, например, созданным с помощью вызова OleCreatePropertyFrame.
Добавление обработчиков сообщений
После настройки элементов управления вы можете добавить обработчики сообщений, чтобы обновить "грязное" состояние страницы при изменении значения любого из элементов управления:
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;
}
Этот код реагирует на изменения, внесенные в элемент управления или флажок редактирования, вызывая IPropertyPageImpl::SetDirty, который сообщает сайту о том, что страница изменилась. Обычно сайт реагирует включением или отключением кнопки Применить во фрейме страницы свойств.
Примечание.
На ваших страницах свойств может потребоваться точно отслеживать, какие свойства были изменены пользователем, чтобы избежать обновления неизмененных свойств. В этом примере код реализуется путем отслеживания исходных значений свойств и сравнения их с текущими значениями из пользовательского интерфейса, когда следует применить изменения.
Действия по обслуживанию
Теперь добавьте пару операторов #import
в файл DocProperties.h, чтобы компилятору было известно об интерфейсе 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
Вам также потребуется ссылаться на базовый IPropertyPageImpl
класс. Добавьте в класс следующее CDocProperties
typedef
:
typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;
Переопределение IPropertyPageImpl::SetObjects
Первый метод IPropertyPageImpl
, который вам нужно переопределить, это SetObjects. Добавьте сюда код, чтобы убедиться, что передан только один объект и что он поддерживает ожидаемый интерфейс Document
:
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;
}
Примечание.
Имеет смысл поддерживать только один объект для этой страницы, потому что вы позволите пользователю устанавливать имя файла объекта. В любом расположении может существовать только один файл.
Переопределение IPropertyPageImpl::Activate
Следующим шагом является инициализация страницы свойств с помощью значений свойств базового объекта при первом создании страницы.
В этом случае вы должны добавить следующие члены в класс, так как вы также будете использовать начальные значения свойств для сравнения, когда пользователи страницы применяют свои изменения:
CComBSTR m_bstrFullName; // The original name
VARIANT_BOOL m_bReadOnly; // The original read-only state
Реализация базового класса метода Activate отвечает за создание диалогового окна и его элементов управления, поэтому вы можете переопределить этот метод и добавить собственную инициализацию после вызова базового класса:
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;
}
Этот код использует методы COM интерфейса Document
, чтобы получить требуемые свойства. Затем он использует программы-оболочки API Win32, предоставленные CDialogImpl и его базовыми классами, для отображения значений свойств для пользователя.
Переопределение IPropertyPageImpl::Apply
Когда пользователи хотят применить свои изменения к объектам, сайт страницы свойств вызовет метод Apply. Теперь следует создать обратный код в Activate
, в котором Activate
принимает значения из объекта и отправляет их в элементы управления на странице свойств, а Apply
получает значения из элементов управления на странице свойств и отправляет их в объект.
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;
}
Примечание.
Проверка m_bDirty в начале этой реализации является начальной проверкой, позволяющей избежать ненужного обновления объектов, если Apply
вызывается несколько раз. Проверяются также значения каждого свойства, чтобы гарантировать, что только изменения приведут к вызову метода Document
.
Примечание.
Document
предоставляет FullName
в виде свойства "только для чтения". Для обновления имени файла документа на основе изменений, внесенных в страницу свойств, необходимо использовать метод Save
, чтобы сохранить файл с другим именем. Таким образом, код на странице свойств не должен только получать или настраивать свойства.
Отображение страницы свойств
Чтобы отобразить эту страницу, вам нужно создать простой вспомогательный объект. Вспомогательный объект предоставляет метод, который упрощает API OleCreatePropertyFrame
для отображения одной страницы, подключенной к одному объекту. Этот объект будет спроектирован так, чтобы его можно было использовать из Visual Basic.
Используйте диалоговое окно "Добавление класса" и мастер простых объектов ATL, чтобы создать класс, используя Helper
в качестве его короткого имени. После создания добавьте метод, как показано в таблице ниже.
Товар | Значение |
---|---|
Имя метода | ShowPage |
Параметры | [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk |
Параметр bstrCaption является заголовком, отображаемым в качестве заголовка диалогового окна. Параметр bstrID является строкой, которая отображает CLSID либо ProgID страницы свойств. Параметр pUnk будет указателем IUnknown
объекта, свойства которого будут настроены на странице свойств.
Реализуйте метод, как показано ниже:
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
);
}
Создание макроса
Создав проект, вы можете протестировать страницу свойств и вспомогательный объект с помощью простого макроса, который можно создать и запустить в среде разработки Visual Studio. Этот макрос создаст вспомогательный объект, затем вызовет его метод ShowPage
, используя ProgID страницы свойств DocProperties и указатель IUnknown
документа, активного в данный момент в редакторе Visual Studio. Ниже показан код, необходимый для этого макроса:
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
При запуске этого макроса будет показана страница свойств, отображающая имя файла и состояние "только для чтения" текущего активного текстового документа. Доступное только для чтения состояние документа влияет только на возможность записи в документ в среде разработки и не влияет на атрибут "только для чтения" файла на диске.