Теперь в файл mypropsheet.h (где объявлен класс CMyPropSheet) пишем:
#include "proppage1.h" // делаем классы страниц видимыми
#include "proppage2.h"
…
class CMyPropSheet: public CPropertySheet {
…
protected:
CPropPage1 page1; // первая страница
CPropPage2 page2; // вторая страница
…
}
Чтобы добавить страницы к окну свойств, необходимо в каждый из конструкторов CMyPropSheet вставить по две следующие строчки:
AddPage(&page1);
AddPage(&page2);
Таким образом, мы сделали страницы-закладки частью нашего окна свойств. Настал момент теперь решить, где вы собираетесь хранить настройки вашего приложения. Я это обычно делаю в классе главного окна, по причине легкости доступа, но ничто не мешает вам хранить их там, где удобнее, причем не обязательно все вместе. Неплохой вариант – хранить их в классе приложения, даже с некоторой точки зрения он более логичный.
Но предположим, вы решили хранить их в классе главного окна, а чтобы они не перемешивались с другими полями класса, объединить их в структуру:
…
class CMainFrame: public CFrameWnd {
…
public:
struct Options {
CString str;
BOOL val;
} options;
…
}
В конструкторе CMainFrame поля структуры options установите в начальные значения. Их можно задать жестко, но обычно сохраненные ранее значения загружаются из файла или реестра , иначе вы быстро доведете пользователя вашей программы до белого каления, заставляя его менять параметры после каждого запуска.
Теперь вставьте обработчик события, возникновение которого должно приводить к выводу на экран вашего окна свойств (например, выбор пункта меню "Сервис|Параметры…"). В обработчике вы устанавливаете параметры, после чего выводите окно свойств. Если пользователь нажал "OK", то после закрытия окна свойств нужно обновить структуру options:
#include "mypropsheet.h"
…
void CMainFrame::OnToolsOptions() {
CMyPropSheet ps("Параметры приложения", this, 0);
ps.page1.m_strEdit = options.str; // настраиваем закладки
ps.page2.m_isChecked = options.val; // соответственно текущим параметрам
if (ps.DoModal() == IDOK) // если пользователь нажал OK
{
options.str = ps.page1.m_strEdit; // сохраняем параметры
options.val = ps.page2.m_isChecked;
}
}
Вот и все, что касается элементарного использования класса CPropertySheet для создания окна свойств. Как видите, работать с ним довольно просто. В одном из следующих выпусков я расскажу вам о задействовании кнопки "Применить" ("Apply"), о нетривиальном использовании этого класса для создания мастеров (wizards), а также о расширенном классе CPropertySheetEx.
ВОПРОС-ОТВЕТ
Q. Возникла вот такая задачка. Имеется некоторое разбиение SDI на несколько view при помощи сплиттеров (A). Как его изменить не убивая окна (на B или C)?
+--+----+ +--+----+ +--+----+
| | | | | | + + +
+--+----+ +--+ | + +----+
| | | | | + + +
A +-------+ B +--+----+ C +--+----+
Nikita Zeemin
A. Для создания окна сплиттера в MFC служит класс CSplitterWnd. Этот класс предоставляет функции для создания вида (CreateView) и удаления вида DeleteView) в заданной панели, но не предоставляет функции, которая позволила бы перенести вид из одной панели в другую. Чтобы проделать это вручную, нужно понимать, каким образом связаны объект класса CSplitterWnd и объекты дочерних видов CView.
CSplitterWnd может иметь не более 16 панелей по горизонтали и столько же по вертикали. Таким образом, он может сожержать не более 256 панелей. Каждой панели соответствует уникальный идентификатор, который и назначается тому виду, который в этой панели находится. Отображение координат панели на её идентификатор выполняет функция int CSplitterWnd::IdFromRowCol(int row, int col);
На самом деле после целой серии ASSERT'ов она просто возвращает значение
AFX_IDW_PANE_FIRST + row * 16 + col
где AFX_IDW_PANE_FIRST — константа, объявленная в MFC.
Это подсказывает простой способ перемещения вида из одной панели в другую: нужно всего лишь подменить его идентификатор, после чего вызвать CSplitterWnd::RecalcLayout для обновления содержимого сплиттера. Если изначально вид не являлся дочерним окном сплиттера и требуется поместить его в одну из панелей, то необходимо также поменять ему родителя с помощью функции CWnd::SetParent. Таким образом функция, вставляющая вид в заданную панель, может выглядеть примерно так:
class CMySplitter : public CSplitterWnd {
…
void InsertView(int nRow, int nCol, CWnd *pView) {
pView->SetParent(this);
pView->SetDlgCtrlID(IdFromRowCol(nRow, nCol));
}
…
}
Аналогичным образом вид переносится "в юрисдикцию" главного окна приложения, порождённого от CFrameWnd:
class CMyFrame : public CFrameWnd {
…
void InsertView(CWnd *pView) {
pView->SetParent(this);
pView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
}
…
}
Имея в руках эти две функции, можно без труда решить поставленную задачу, располагая виды как на рис. A, B, C или любым другим способом. Ещё раз замечу, что после всех перемещений необходимо вызывать CSplitterWnd::RecalcLayout и CFrameWnd::RecalcLayout.
Alexander Shargin ОБРАТНАЯ СВЯЗЬ
Alexander Shargin, воистину герой сегодняшнего выпуска, в письме с ответом на предыдущий вопрос также недоумевал по поводу вопроса прошлого выпуска, где требовалось минимизировать CPropertySheet:
…Честно говоря, я не понимаю, о чём идёт речь. Я буду очень вам признателен, если вы объясните мне, в чём проблема. Дело в том, что я создал визардом приложение на базе диалога, а затем просто заменил диалог на объект класса CMySheet, порождённого от CPropertySheet. После чего добавил пару вкладок (типа CPropertyPage) и вызов ModifyStyle(0, WS_MINIMIZEBOX) в обработчике OnCreate. В результате этих несложных операций получилось приложение, главное окно которого без проблем сворачивается на панель задач.
Я посмотрел приаттаченный им проект и убедился, что Александр совершенно прав. После чего я сам проделал то же самое с новым проектом, и получил такой же результат. Итак, вся загвоздка была в том, что ModifyStyle нужно было вызывать не из OnInitDialog, а из OnCreate!
После этого я задумался, откуда же у класса CPropertySheet вообще есть метод OnInitDialog, ведь сам класс является прямым наследником CWnd. Оказалось, что этот метод, наряду с DoModal, был добавлен туда искусственно, чтобы обращение с классом напоминало обращение с CDialog. Не знаю, почему бы Microsoft просто не сделать CPropertySheet наследником CDialog, но наверное у них были свои причины (хотя здесь можно и посомневаться ;)
Я переслал письмо Александра человеку, задавшему вопрос, и получил от него положительный ответ – у него тоже все заработало.
Вот, оказывается, как просто открывался ларчик! Не надо было перехватывать WM_NCLBUTTONDOWN, не нужно было делать callback функцию… (решение автора вопроса)…
И еще – насчет минимизации в левый нижний угол – видимо это был частный случай поведения, вызванный моими манипуляциями со стилями ;-)
Напоследок хочу процитировать Win32 Q&A из MSDN, чтобы абсолютно точно уяснить для всех
Как Windows определяет, нужно ли выводить кнопку приложения на панель задач
Правила эти довольно просты, хотя и не очень хорошо документированы. Когда вы создаете окно, Windows проверяет его расширенный стиль. Если установлен стиль WS_EX_APPWINDOW (определенный как 0x00040000), на панель задач выводится кнопка окна. Если же установлен стиль WS_EX_TOOLWINDOW (0x00000080), то кнопка не выводится. Не следует создавать окна, где установлены оба эти стиля.
Можно создать окно, не имеющее ни один из этих стилей. Тогда кнопка выводится только в том случае, если у окна нет родителей.
И последнее замечание: прежде чем тестировать что-либо из вышеописанного, панель задач проверяет, установлен ли стандартный стиль видимости WS_VISIBLE. Если нет, то окно спрятано, и показывать кнопку нет никакого смысла. Стили WS_EX_APPWINDOW, WS_EX_TOOLWINDOW и информация о принадлежности окна проверяются ТОЛЬКО при установленном WS_VISIBLE.
Jeffrey Richter В ПОИСКАХ ИСТИНЫ
Q. У меня программа с использованием MFC и Doc/View. Я вставил RichEditCtrl во вью. (2-ой версии). Установил шрифт с помощью сообщения SetCharFormat. Внимание, вопрос: почему если я ввожу текст с клавиатуры и использую ReplaceText функцию (не сообщение!) фонты различные? Вроде это сообщение не менялось у второй версии. Заранее спасибо за ответ.
Игорь
Это все на сегодня. Пока!
© Алекс Jenter mailto:[email protected] Красноярск, 2000.
Программирование на Visual C++