До меня дошли сведения, что предыдущий, 16-тый, выпуск дошел почему-то
не до всех подписчиков. То ли из-за глюков на ГорКоте, то ли из-за
гиперактивности магнитных бурь ... ;)
Повторно высылать я его, пожалуй, не стану, лучше дам вам на него
прямую ссылку.
Тем, кто его еще не видел, рекомендую прочитать,так как данный выпуск
связан с предыдущим.
Итак, наконец-то мы добрались до темы создания окон свойств, которые в
MFC реализуются с помощью, как вы уже догадались, класса CPropertySheet.
Работа с окнами свойств : использование класса CPropertySheet
Как известно, практически во всех более-менее серъезных программах
есть диалоговые окна настройки параметров, или опций, приложения. Такие окна
получили название окон свойств. Чтобы увидеть одно из таких окон,
достаточно выбрать Tools|Options в Visual C++ IDE.
Задача создания окон свойств стоит практически перед каждым
разработчиком, именно поэтому в MFC решение этой проблемы в некоторой
степени автоматизировано с помощью класса CPropertySheet. В
результате его применения вы получаете готовое диалоговое окно с
набором закладок и ;некоторым количеством стандартных кнопок - OK, Cancel, и
т.д. Закладки здесь - это объекты типа CPropertyPage. Этот класс,
кажется, уже фигурировал в одном из выпусков. Никогда не путайте
CPropertySheet и CPropertyPage: помните, что первый (CPropertySheet) СОДЕРЖИТ
вторые (CPropertyPage) так же, как книга содержит страницы.
Так, с этим разобрались, идем дальше. Как пользоваться классом CPropertySheet?
Очень несложно, вы в этом сами сейчас убедитесь.
Для каждой закладки нужно создать диалоговый ресурс (не обязательно со стилем child),
куда вы помещаете все содержимое соответствующей страницы (также, как и при работе
с CTabCtrl). Например, IDD_PROPPAGE1 и IDD_PROPPAGE2. Можно сразу заполнить поле
Caption в диалогах, чтобы потом заголовки закладок сформировались автоматически.
В проект добавляется класс-наследник от CPropertySheet, пускай он называется
CMyPropSheet. Для того, чтобы можно было работать с контролами на закладках,
добавляется отдельный класс для каждой страницы-закладки (наследованный от CPropertyPage).
Например, для двух закладок это будут классы CPropPage1 и CPropPage2 (эти классы
добавьте дабл-кликнув на поверхности соответствующего диалога и выбрав
"Create a new class", затем в поле "Base class" выберите CPropertyPage в
качестве класса-родителя). В эти классы нужно поместить члены, связанные с контролами,
расположенными на странице. Например, если у нас на первой странице (IDD_PROPPAGE1)
есть Edit Box, добавляем в класс CPropPage1 переменную m_strEdit класса СString,
доступ - public. Пусть на второй странице у нас Check Box, значит в класс CPropPage2
записываем член m_isChecked типа BOOL, и т.д. Использование типа доступа public к этим
полям в данном случае оправданно, т.к. избавляет в дальнейшем от многих хлопот. И не
забывайте, эти члены класса должны быть связаны с соответствующими контролами на
закладке.
Теперь в файл mypropsheet.h (где объявлен класс CMyPropSheet) пишем:
#include "proppage1.h" // делаем классы страниц видимыми
#include "proppage2.h" ...
class CMyPropSheet: public CPropertySheet
{ ...
protected:
CPropPage1 page1; // первая страница
CPropPage2 page2; // вторая страница ...
}
Чтобы добавить страницы к окну свойств, необходимо в каждый из конструкторов
СMyPropSheet вставить по две следующие строчки:
AddPage(&page1);
AddPage(&page2);
Таким образом, мы сделали страницы-закладки частью нашего окна свойств.
Настал момент теперь решить, где вы собираетесь хранить настройки вашего приложения.
Я это обычно делаю в классе главного окна, по причине легкости доступа, но ничто
не мешает вам хранить их там, где удобнее, причем не обязательно все вместе.
Неплохой вариант - хранить их в классе приложения, даже с некоторой точки зрения
он более логичный.
Но предположим, вы решили хранить их в классе главного окна, а чтобы они не
перемешивались с другими полями класса, объединить их в структуру:
...
class CMainFrame: public CFrameWnd
{ ...
public:
struct Options
{
CString str;
BOOL val;
} options; ...
}
В конструкторе CMainFrame поля структуры options установите в начальные
значения. Их можно задать жестко, но обычно сохраненные ранее значения загружаются
из файла или реестра , иначе вы быстро доведете пользователя вашей программы до
белого каления, заставляя его менять параметры после каждого
запуска.
Теперь вставьте обработчик события, возникновение которого должно приводить к выводу
на экран вашего окна свойств (например, выбор пункта меню "Сервис|Параметры...").
В обработчике вы устанавливаете параметры, после чего выводите окно свойств. Если
пользователь нажал "OK", то после закрытия окна свойств нужно обновить структуру
options:
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)? - 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);
} ...
}
Имея в руках эти две функции, можно без труда решить поставленную
задачу, располагая виды как на рис. А, B, C или любым другим способом. Ещё раз
замечу, что после всех перемещений необходимо вызывать
CSplitterWnd::RecalcLayout и CFrameWnd::RecalcLayout.
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 функцию (не сообщение!) фонты различные? Вроде это
сообшение не менялось у второй версии. Заранее спасибо за ответ.
- Игорь