> Замечу, что объекты указанных классов (а значит и > соответствующие контролы) «НЕЗАВИСИМЫ» в том смысле, что > они почти ничего не знают друг о друге (идеология MFC). Нет, у MFC другая идеология. В плане абстракции данных он даже не совсем ООП, так как все данные держит открытыми - непозволительная вольность. Я не очень люблю MFC, но совсем за другие вещи.
На самом деле, MFC это библиотека классов, но только самые мелкие из них (контролы, например, или сокеты) используются в приложении в чистом виде. Класс приложения, класс диалога и др., используются как базовые. Поэтому ничто не мешает хранить там что угодно. Кроме того, Class Wizard предоставляет возможность вводить в эти самые создаваемые классы переменные для обращения к объектам (см "Дополнение к дополнению"). Есть один глобальный theApp. В нем можно хранить указатели на диалоги (но насколько я понял диалог один, так что этого не надо). В эти диалоги из ClassWizard-а подобавлять переменных, через которые потом можно будет обращаться к контролам (в частности CTabCtrl).
> чекбоксов или эдитов на закладке CPage2, т.к. она (кнопка) > не знает даже указателя на диалог CPage2. При переводе Но может узнать. С CPage-ами, как я уже говорил, немного сложнее, так как о них неизвестно в design time-е и приходится динамически сохранять/изменять эту информацию. Но и это не очень сложно, так как конструкторы/деструкторы создаваемых объектов всегда могут найти своего родителя (через глобальный theApp или это и есть сам theApp в случае диалогов), и изменять там каждый свое поле (указатель). А в случае CPage-ей и это не надо, так как система при добавлении итема в таб контрол позволяет передать один long - вполне достаточно для передачи указателя на объект-страницу. А таб контрол впоследствии в состоянии получить этот указатель.
> Вот дополнения к мотивации: Мотивация вполне правильная на мой взгляд, но есть и еще одно. Ни один объект не должен ничего делать с объектами, непосредственным владельцем которого он не является. То есть, при нажатии на какую-то кнопку должно измениться состояние. Об этом пока что знает только кнопка. Она сообщает об этом приложению. Приложение переходит в нужное состояние и сообщает своим диалогам (переводит их). Диалоги переводят в свою очередь свои контролы (ну чтоб не наследоваться от CTabView можно сообщать всем страницам). А страницы уже непосредственно меняют состояние контролов у себя.
И кстати, если состояний не так много, не так уж нужно вводить функцию-диспетчер всех состояний. Гораздо прозрачнее и понятнее ввести функции OnStartup, OnClose, OnRead и т.д. (эти названия нужно понимать как: "действия, которые необходимо выполнить при переходе в такое-то состояние"), ну можно и так OnStartupState, OnCloseState, OnReadState - кому как понятнее. Они должны быть виртуальными. А следовательно можно будет немного переписать бизнес-логику просто отнаследовавшись и переопределив виртуальную функцию (в случае с громадным case-ом это не так просто, особенно если эти case будут иерархически распределены, как я сказал выше).
> xxx.FillControls(UM_STARTUP); > xxx.DisplayControls(UM_STARTUP); Я кстати не совсем понял, зачем эти две функции разделены, если они всегда идут парой.
Мой вариант:
// стартуем приложение MFC VC++
BOOL CGkmApp::InitInstance()
{
InitCommonControls();
CWinApp::InitInstance();
…
// в следующих двух вызовах читаем данные, заполняем контролы
// и приводим их в приличный вид визибл-невизибл, энайб-дисайбл и т.д.
OnStartup();
// запрещаем пользователю делать всякие глупости и портить данные,
// пока он сознательно не переведёт приложение в состояние
// ST_EDIT, нажав, например, на кнопку «Редактировать»
OnRead();
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
// Обработка возвращенного значения.
//...
return TRUE;
}
//-------------------------------------------------------------
// Здесь пользователь нажимает кнопку «Редактировать» для перевода
// формы приложения в режим редактирования ST_EDIT
void CPageGeneral::OnBnClickedEditButton()
{
// здесь, до вызовов двух функций ниже
// все 200 контролов ещё недоступны для редактирования
theApp.OnEdit();
// Кнопка сообщает приложению, приложение - диалогу, диалог - страницам,
// страницы - контролам. Причем сообщает можно понимать как угодно.
// Например, страница прямо переводит контролы в нужное состояниею
// А все остальные просто вызывают нужную мембер-функцию дочернего объекта.
// здесь все 200 контролов уже доступны для редактирования
// и пользователь может вводить данные
}
//-------------------------------------------------------------
// Здесь пользователь нажимает кнопку «Сохранить» для перевода
// приложения в режим сохранения результатов редактирования ST_SAVE,
// а затем возврата в состояние просмотра данных ST_READ
void CPageGeneral::OnBnClickedSaveButton()
{
// здесь все 200 контролов доступны для редактирования
// т.к. состояние ещё ВОЗМОЖНО ST_EDIT
theApp.OnSave();
theApp.OnRead();
// В данном контексте, лучше бы назвать эти функции просто Save и ReadOnly
// Хотя для сохранения сквозного стиля можно оставить и так
// здесь все 200 контролов изменили своё состояние,
// и уже недоступны для редактирования
// Мы в состоянии ST_READ просмотра базы данных
}
---
> >*МАСЛО В ОГОНЬ* >
> Думаю, масло следует подливать понемногу, шаг за шагом. > В предыдущем посте предложено тупое решение, когда > указатели на все требуемые объекты, например, диалоги > Page1,…, Page20 объявлены extern. На мой взгляд, это не > такой уж плохой путь для простых по логике GUI. Так, что в > простых случаях я так и буду делать, и других наворотов > (см. далее шаг 1 и т.д.) мне и не надо. > > Шаг 1. > Для более сложных приложений, если мы хотим «большой > беспорядок привести к нескольким маленьким беспорядкам», > заметим, что двадцать глобальных указателей – прямо скажем, > не очень изящно. Ну, добавим все указатели в некий массив. > Необходимость в таком массиве, вернее специальном классе > CMessanger (язык не поворачивается сказать элементе > паттерна) дополнительно обоснована далее, см. шаг 2. В > функциях FillControls, DisplayControls нам нужно будет > уметь извлекать из массива указатели на диалоги по > некоторым символическим идентификаторам, которые мы знаем > (сами задаём при добавлении указателя на объект в массив). > Поэтому в элементах массива будем хранить указатели вместе > с идентификаторами. [skipped]
ИЗВРАТ!!! %-) :-)
Я же говорю. С CPage-ами надо поступать проще:
class CPage: public CDialog {
public:
virtual void OnStartup() = 0;
virtual void OnRead() = 0;
virtual void OnEdit() = 0;
// ...
}
class CPage1: public CPage {
// переопределение виртуальных функций
}
class CPage2: public CPage {
// переопределение виртуальных функций
}
//...
class CPage20: public CPage {
// переопределение виртуальных функций
}
// При добавлении CPage:
CMainDialog::SomeFunction() {
TCITEM tci;
tci.mask = TCIF_PARAM | TCIF_TEXT;
tci.pszText = "Page 1";
tci.lParam = reinterpret_cast<LPARAM>(new CPage1);
if (tci.lParam == 0) {
// ошибка, хотя можно использовать и try-catch
}
// m_pTabCtrl был добавлен в ClassWizard-е
m_pTabCtrl->InsertItem(0, &tci);
// И так далее для остальных табов
}
// Работать с этим так:
void
CMainDialog::OnStartup() {
// ...
for (int i = 0; i < m_pTabCtrl->GetItemCount(); i++) {
TCITEM tci;
tci.mask = TCIF_PARAM;
m_pTabCtrl->GetItem(i, &tci);
((CPage *)tci.lParam)->OnStartup();
}
// ...
}
// В сгенеренный ClassWizard-е класс CGkmApp вручную добавить диалог
// Или найти способ получать этот диалог динамически - я не слишком люблю
// MFC, поэтому не сильно с ним разбирался
class CGkmApp: public CWinApp {
// ...
CMainDialog *m_pMainDlg;
//...
}
// Все остальное в том же духе. Даже если в MFC нет абстракции данных,
// ее можно ввести искусственно - просто не использовать чужие данные напрямую.
---
> Однако часто объекты проекта по ЛОГИКЕ ПРИЛОЖЕНИЯ > представляют независимые ГРУППЫ, сообщения которых должны > обрабатывать различные обработчики FillControls, > DisplayControls. Например, диалоги и виды, описанные выше – Вот поэтому, нужно чтоб объект делал только то что знает как делать точно. То бишь приложение переводит в новое состояние своих детей, диалог - своих, а страницы просто переводят контролы в нужное состояние. А использование виртуальных функций при этом помогает отделить интерфейс от реализации.
> Ещё раз благодарю всех тех, кто откликнулся. Обещаю, что > дальше не буду писать столь пространно и столь длинные > посты. Это было необходимо в настоящем постинге, т.к. далее > я буду только ссылаться на предыдущие свои постинги. Если, > конечно, дискуссия состоится. Да ничего. Модераторов тут много. Если что - амнистируем :-)
|