информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Атака на InternetСетевые кракеры и правда о деле ЛевинаВсе любят мед
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
 Умер Никлаус Вирт 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
если вы видите этот текст, отключите в настройках форума использование JavaScript
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[C++] Сообщения пользователя 22.07.03 09:14  Число просмотров: 1373
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
> Постой-ка. В Сях же можно несколько классов наследовать.

Так просто не получится. Проблема таже что и в COM. Это тема отдельного топика. Хотя можно, конечно и тупо:
1) Наследовать CMsgDialog : public CDialog
2) Сделать в CMsgDialog обработку сообщений
3) "Рабочие" диалоги наследовать от CMsgDialog

Тупо, но работает железно, хотя и не всё.... Всё равно сообщения
(или вызов виртуальных месаговых методовCMsgDialog) придётся делать
для КАЖДОГО диалога, что в общем случае НЕ ПОДХОДИТ.


> Сделай общий управляющий класс... Или в этом случае не
> очень удобно?

Умничка! Так к этомуж я и подвожу!!! В моём посте, в начале топика написано - "для затравки".

> Еще тупой способ: Переписать MFC под себя. Взять из
> исходников нужные куски, дописать в глобальных классах
> (CView, например) нужные обработчики... а потом этой
> "новой" библиотекой все время и пользоваться.

Опять умничка!
Но только пусть это лучше сделают мелкософтовские садисты - авторы MFC.
<programming>
[C++] Сообщения пользователя 20.07.03 03:46  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Предлагаю дискуссию.
Как организовать обмен пользовательскими «сообщениями»между классами приложения MFC, которые бы отражали общую бизнес-логику программы?
Вопрос не в том, как это вообще можно сделать, а как сделать эффективнее, с точки зрения скорости разработки приложения VC++.

Хотя вопрос касается приложений MFC, но для лучшего его понимания - начну со стиля построения приложений, который я уже лет 6 использую в BC++ Builder.
Удобно для оконной формы BC++ Builder с большим числом контролов (50-100, что характерно для приложений баз данных) определить две функции, например,bool FillControls(unsigned int msg) и bool DisplayControls(unsigned int msg) для обработки «сообщений» пользователя типа CLEAR_CONTROLS, READ_DATABASE, SAVE_DATABASE и т.д:
bool FillControls(unsigned int msg)
{
switch( msg )
{
case STARTUP: …..
case CLEAR_CONTROLS: …..
case READ_DATABASE: …..
}
}
Например, по нажатию кнопки «Открыть», обработчик кнопки делает последовательность вызовов FillControls(READ_DATABASE) и DisplayControls(READ_DATABASE). Это приводит к тому что, соединяемся с DB, читаем данные в контролы и перерисовываем их, контролируя на каждом шаге, что должно быть disable, что enable, что readonly и т.д.
Удобство заключается в том, что всё управление отображением и заполнением контролов (enable, visible и т.д) сосредоточено в одном месте. Вернее сосредоточена общая логика приложения в части управления контролами.
Такую обработку несложно построить в BC++, т.к. все контролы являются членами класса формы приложения. Т.е из указанных выше функций (методов формы) видны все контролы.
Гораздо сложнее это реализовать в VC++. Это связано с тем, что классы, в которых созданы члены-контролы ничего не знают друг о друге, и не видят соответствующих контролов в общем случае. Например, если Вы создали приложение с CTreeView – СFormView, где на форму положили CTabCtrl и для каждой закладки создали по диалогу CPage1,…., CPage20, то для каждого класса!!! придётся писать соответсвующие обработчики bool FillControls(unsigned int msg), bool DisplayControls. При этом необходимо ещё найти приемлемый механизм обмена сообщениями между классами.

У кого есть идеи? Предлагаю для затравки тупой метод решения задачи - использование глобальных указателей на каждый из диалогов. Умные – предложите Вы.

Делаем тупо:
extern CTvView* pTV;
extern CPage1* Page1;

extern CPage20* Page20;

bool FillControls(unsigned int msg)
{
switch( msg )
{
case STARTUP:
pTV->Clear();
Page1->ClearControls();
….
Page20->ClearControls();

}
}
[C++] ИТОГ 29.07.03 11:25  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
*ИТОГ *

(Везде, где есть сильные утверждения – разумеется ИМХО. По тексту не везде писал, но это так)

Подведём черту. Общее заключение о том, как надо и как не_надо, я не сделал для себя. Можно с определённостью повторить только то, что заметил Ktirf в одном из самых первых ответов, имея в виду тему его поста - «Не слишком благодарное дело - тянуть подход из библиотеки с одной идеологией в библиотеку с другой». Это точно. Хотя задачу мы и развивали так, чтобы выработать некое ОБЩЕЕ решение, полезное и в средах «рисования-программирования», и в средах типа VC (++ MFC), но всё же - «Что борланду хорошо, то мелкософту - смерть». Заметил я про борланд, т.к. это было в начале топика.


** BCB++ **

В отличие от VC, IDE BCB вставляет строительные кирпичики–компоненты непосредственно в определение формы. Для программеров, кто не сталкивался с рисовалками борланда - это выглядит, приблизительно так:

class TfrmMonitor : public TForm
{
__published: // IDE-managed Components
TPanel *PanelMain;
TTreeView *TV;
….. // ~ 100 -200 компонент контролов, включая табы, вякого рода
….. // тул-, кул-бары, меню, ст. диалоги, имидж-листы
…… // и т.д.. Т.е. это ВСЁ то, что программер VC пишет руками.
// Как в Visual Basic, в коде мы просто обращаемся к хорошо написанным
// методам и пропертям этих компонент

private: // User declarations
….
…..
void DisplayControls( int _mode);
void FillControls( int _mode);
public: // User declarations
__fastcall TfrmMonitor(TComponent* Owner);
};

Для КАЖДОЙ, маломальски навороченной формы (их в проекте может быть 15-20) я пишу приват-обработчики FillControls/DisplayControls, аналогичные тем, что выше. Эти обработчики обращаются !!!ТОЛЬКО К СВОИМ!!! компонентам. Я НИКОГДА не обращаюсь к ДРУГИМ формам из этих обработчиков. Да это и невозможно, в силу их «всё-приват», в силу того, что «наружу» смотрит только конструктор, и + пара перезагруженных методов, чтобы удобнее создавать форму в ран-тайм SDI-приложения. Это хоть и косметика, но такая, что делает программирование ОДНОЙ ЭТОЙ ФОРМЫ более прозрачным для меня. Это, думаю, полезно только для BCB++ и только для SDI, и только если формы - МОДАЛЬНЫЕ. Наличие FillControls/DisplayControls не делает в этом случае приложение противоречивым. В этих функциях повсеместно использую рекурсию (чем её больше, тем лучше), для большей прозрачности обработки событий, см. ниже:

void TfrmMonitor::FillControls( int _mode)
{
switch (_mode) {
case _STARTUP :
TVClear(TV);
FillControls(_ERASE);
TVStartUp(TV);

break;
case _READ:
FillControls(_READ_DB);

break;
case _ERASE:
LVClear(LV);

break;
case _READ_DB:
// TODO

break;
case _READ_FOLDER:
// TODO

break;
case _READ_OBJECT:
break;


}
}
Заменять этот свич вызовами соответствующих методов формы, или нет – непринципиально, дело вкуса. Однако это ликвидирует эффект концентрированности управления логикой событий в ОДНОЙ форме, имхо. То, что в перечисленных выше условиях ЭТО работает железно – проверено многолетней практикой.


** ЧИСТО ООП **

Здесь про классический ООП подход, который amirul, изложил очень лаконично в своих постах. Поскольку его важные замечания могли остаться незамеченными, то сделаю их перепост, вернее выдержки. Перепост связан и с тем, что на разных форумах предлагали, дескать, «а мож так?» (в смысле, как amirul). Кроме того перепост необходим, поскольку дальше будут ссылки на этот подраздел. Если вкратце, то:

1) Ни каких горизонталей. Вся система представляет строго иерархическую модель. «…Если сделать нормальную иерархию объектов, то рассылать нужно сверху-вниз слева-направо и тогда порядок всегда будет жестко задан - вот от него и отталкиваться впоследвие.»

2) Глобальные обработчики не имеют права на жизнь. Любая обработка только внутри самого класса. «…Ни в коем случае нельзя вводить глобальных обработчиков (даже для каких то групп). Нужно чтобы каждый КОНКРЕТНЫЙ объект САМ знал, как реагировать на тот или иной вызов. Обработчик должен быть методом класса (если речь об абстрактном классе, то виртуальным методом)… При переходе приложения в какое-то состояние, оно должно перевести только своих непосредственных детей. А те уж сами о себе позаботятся.»

Проще, наверное, сделать перепост кода, чтобы было понятно не только «в принципе», но и как?:

ВАРИАНТ amirul:

// стартуем приложение MFC VC++
BOOL CMyApp::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 просмотра базы данных
}

Все объекты, в том числе и theApp, должны быть реализациями классов, для которых определены методы OnSave, OnRead и т.д. Можно их наследовать от «интерфейсных заготовок» с виртуальными или чисто виртуальными (что лучше) методами, соответствующими enum app_states. Дело вкуса. Например:

КОД amirul:

class CPage: public CDialog {
public:
virtual void OnStartup() = 0;
virtual void OnRead() = 0;
virtual void OnEdit() = 0;
// ...
}

class CPage1: 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();
}
// ...
}
Я бы здесь отметил, что построенное таким образом приложение будет работать всегда. Единственно, что необходимо сделать – это разработать иерархию, о которой выше. Может появиться много нудного, однотипного кода. Но в этом и фокус, чтобы делать одну и ту же работу, пускай и нудную. Вероятность, что доедим до конца проекта -100%, хоть и не «экспрессом». А «нудный» код, о котором я, например, при очистке содержимого контролов таков:

НУДНЫЙ КОД

void CMyApp:: ClearControls()
{
m_pMainDialog.ClearControls();
m_pTreeView.ClearControls();

m_pParent1.ClearControls();
//…
//…
m_pParentXXX.ClearControls();
//и т.д.
}
// А затем КАЖДЫЙ из родителей перечисляет
void CParent1:: ClearControls()
{
m_pChield1. ClearControls();
m_pChield2. ClearControls();
//…
//…
m_pChieldXXX. ClearControls();
//и т.д.
}

В этом примере есть некоторая натяжка, связанная с тем, что в листе дерева на каждом уровне иерархии не будет уж очень большого числа Children. В этом подходе есть огромное преимущество перед использованием CMessanger. Оно в том, что мы легко и всегда контролируем последовательность и результаты (если необходимо) каждого вызова OnRead, OnEdit и т.д. на каждом уровне иерархии. Так что подход железный, в том числе и при построении GUI.


** ОБЩЕЕ ПРО СMessanger для GUI**

Основной идеей было НE создание около-медиатора CMessanger, а попытка опереться только и только на логику приложения вне зависимости от модели взаимодействия CWnd-объектов приложения (диалогов, видов, баров и т.д.). Если кому-то, нравятся сравнения с сетевыми технологиями, то это попытка получить «наложенное» решение, например, приватлан поверх существующей сетевой инфраструктуры . CMessanger – это пример (даже не реализация) одного из инструментов такого решения. Если и развивать это направление, то только при удовлетворении следующих условий, имхо:

1) от иерархической модели управления состояниями enum app_states следует отказаться в том смысле, что методы для перевода приложения в одно из состояний app_states должны быть определены не для всего дерева (см. выше «ЧИСТО ООП»), а только для ряда ключевых листьев (node) этого дерева. Если в «ЧИСТО ООП» переход в новое состояние всегда выполняется, начиная с рут (например, theApp), как было сказано «слева направо и сверху вниз», то здесь, путь короче – реагируют на сообщения только те подписчики, которым это необходимо. Если этого не сделать, то при введении CMessanger параллельно будут жить ДВЕ логики управления app_states.

2) исключением из п.1 могут быть случаи, имхо, когда ООП–иерархия управления состояниями app_states присутствует только для некоторых ветвей иерархического дерева, для некоторых групп объектов, например, пейджей и им подобным, см. выше. Во взаимодействии этих объектов ветки (CMainDialog, CTabCtrl, CPage1, … , CPage20) ОСТАВЛЯЕМ ИМ ИХ ОБЪЕКТНО-ОРИЕНТИРОВАННУЮ ЖИЗНЬ. Другими словами, если необходимо перевести приложение в состояние ST_READ, то будем вызывать только один метод CMainDialog::OnRead() (далее слева направо и снизу вниз по схеме выше «ЧИЧТО ООП»). Отличие от «ЧИСТО ООП» в том, начинать «оповещения» будем не с корня theApp, а с theMsg, где мы делаем подписчиками наиболее значимые на наш взгляд объекты. В данном случае один из таких значимых объектов – CmainDialog, но не пейджи.

3) Демо-CMessanger необходимо довести до ума. Во-первых, «разрегистрация». Во-вторых, можно попытаться сделать модель более чистой, убив там виртуальные методы FillControls/DisplayControls, но дав что-то взамен для управления последовательностью и для контроля результатов выполнения переопределённых виртуальных методов CMsgItem. На одном из форумов было, например, предложение ввести приоритеты выполнения команд. Мелочь – заменить виртуальные методы CMsgItem чисто виртуальными. Ну и последнее - помнить о том ЗЛЕ и граблях, о которых ниже.


** ГОРИЗОНТАЛИ И ДРУГОЕ ЗЛО **

1) Если мы оставим в приложении модель «ЧИСТО ООП» и дополнительно введём СMessanger, то не создав ничего, мы сломаем модель ООП, поскольку тем самым введём избыточные горизонтальные связи между листьями (node) ООП иерархической модели. Состояние приложения должно меняться или по одной модели, или по другой. Надо что-то одно.

2) amirul при обсуждении в одном постов проблем модернизации правильно сказал, что в общем случае «число» изменений в коде при модернизации приложения, построенного на плоской модели CMessanger будет квадратично зависеть от числа подписчиков N. Это действительно так, если различать сообщения A->B и B->A и кроме того A->A. Я бы ослабил формулировку до «может зависеть…», имея в виду реальные ситуации, а не модель, но заметил бы другое зло. С некоторой натяжкой можно говорить о N*N независимых способах передачи сообщений состояниях системы в простом мессангере, как о N*N степенях свободы. Если провести дальше аналогию в механику, или теорию надёжности, то мы получим катастрофическое снижение и того и другого даже при малой вероятности краха приложения при передаче только одного сообщения (здесь имеется ввиду ошибка программера). Модель становится «неустойчивой». Все эти рассуждения носят налёт теоретизации, возможно и излишней. Но не трудно увидеть, что, включая в мессангер минимально необходимое число объектов, а не всё, что попало, мы будем также «квадратично приближаться» к дереву ООП. Т.е. в манагер необходимо включать, только главных фигурантов приложения. А там уж пусть они объектно-ориентированно плодятся и размножаются.

3) Если мы уж включили объект в манагер, то обращаться к объекту следует только через посредник-манагер. Иначе конфликт логик – неизбежен, рано или поздно. Т.е. модель в некотором смысле становится очень зажатой.

4) плохая идея хранить где-то ещё указатели на объекты, которые мы уже включили в манагер.


** А ЧТО ТОГДА ХОРОШЕГО ВО ВСЁМ ЭТОМ? **

1) Хорошее есть в применении похожих паттернов проектирования для решения более сложных задач. Но у нас другой, относительно простой случай-всего лишь GUI. Так, что к нам «их хорошее» не относится.

2) Если есть «безобидные» состояния и сообщения, например, ClearControls, то для последних можно сделать только один вызов, а не десять, как в «ЧИСТО ООП» (см. НУДНЫЙ КОД выше):

// Броадкаст рассылка
void CXXXXX:: ClearControls()
{
Messanger.ClearControls();
}

3) В цепочку обработки сообщений легко включить любые объекты, а не только CWnd, см., шаг 5.

4) Не то, что хорошее, но обнадёживает. Всё, что я постил в «шагах» работает.


** ЧТО ЕЩЁ СОВЕТОВАЛИ НА ФОРУМАХ? **

Здесь приведу только некоторые советы, которые мелькали не один раз у различных форумчан.

1) Делать рассылку мессаг при помощи SendMessage API или MFC. Мы это убили на «ШАГЕ 5». Надеюсь, что понятно почему.
2) Использовать возможности архитектуры Документ-Вид, для того, чтобы «оповещать» виды о любых изменениях. Идея, я думаю, понятна всем: получаем документ -> изменяем там, чего нам надо -> просим документ сказать CDocument::UpdateAllViews. Далее для всех наших видов переопределяем CView::OnUpdate, где вначале анализируем app_state, а затем уж в зависимости от состояния приложения перерисовываемся, как надо.
Думаю, это будет работать. Не пробовал. Но это MFC, от специфики которой хотелось уйти. Допускаю, что в этом случае можно с небольшими накладными расходами получить контроль за состояниями приложения, хотя мелкософт и делал эту архитектуру несколько для иного, уж не для управления app_states – это точно. Я даже сомневаюсь, что сами мелкософтовцы её пользуются.
3) Было много советов по поводу того, как работать с пейджами, где хранить указатели на CWnd-объекты (в аппе, майнфрейме и т.д.), за что большое спасибо. Посмотреть код других на форуме, думаю всем полезно.


PS.
Не судите строго, люди добрые, ибо я вообще не Программист, а eMbedded.
Благодарю всех, кто уже высказался и кто ещё выскажется.
Предлагаю подвести здесь черту. Хотелось бы услышать Ваше мнение по любым вопросам, связанным с темой топика. Стоит дальше этим заниматься, или нет?
В общем, интересно всё, что Вы думаете по этому поводу.
[C++] ИТОГ - мнение 30.07.03 12:23  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> НУДНЫЙ КОД
>
> void CMyApp:: ClearControls()
> {
> m_pMainDialog.ClearControls();
> m_pTreeView.ClearControls();
>
> m_pParent1.ClearControls();
> //…
> //…
> m_pParentXXX.ClearControls();
> //и т.д.
> }
Небольшое замечание: на самом деле такой "нудный код" тоже является признаком плохого стиля. Уже хотя бы потому, что при добавлении нового объекта, менять нужно не только в том месте, где производится собственно добавление (что неизбежно), но и в данном (и других, потому как код нужно добавлять не только для ClearControls) обработчике. То же самое и с удалением. Вывод: везде, где родителю необходимо что-то сделать со ВСЕМИ своими детьми (если количество больше 2-3-х), я ПРОЕКТИРУЮ класс так, чтобы родитель мог ПЕРЕЧИСЛИТЬ всех своих детей в цикле. Количество кода стремительно уменьшается. И при добавлении/удалении детей изменениям подвергается только само место добавления (удалять можно тоже перечислением).

Еще раз напомню, что развитие парадигм программирования шло именно по пути уменьшения связей между частями программы. И даже если ООП ничего не говорит о таком подходе, важно понимать, куда именно движется программирование и соответсвовать, так сказать. То, что при подходе с перечислением уменьшается количество связей, думаю объяснять не стоит. Тем более что в этом вопросе никто не собирается вставлять палки в колеса. Все системные контейнеры (взять окна, tab control-ы и др.) хранят список дочерних объектов и предоставляют возможность их перечисления, если ты при проектировании своего класса будешь поступать так же - будет тебе счастье :-)

> // А затем КАЖДЫЙ из родителей перечисляет
> void CParent1:: ClearControls()
> {
> m_pChield1. ClearControls();
> m_pChield2. ClearControls();
> //…
> //…
> m_pChieldXXX. ClearControls();
> //и т.д.
> }
Причем сказанное выше касается КАЖДОГО из родителей. Вплоть до диалога, который может выключить всех своих детей при помощи EnumChildWindows. Кроме того, как ленивый программист, я пытаюсь вынести общий код как можно выше в базовые классы. То есть добавлять/перечислять всех детей однотипных объектов одной и той же функцией (а не похожей, или даже copy/paste-нутой). То есть сделать, например, класс страницы, которая умеет выключать всех своих детей перечислением в for-е и отнаследовать все страницы от нее. Главное не переусердствовать и не вводить в иерархию ничего натянутого. Например, если способ перечисления для окна существенно отличается от способа перечисления для табконтрола, то не стоит пытаться сделать им общую базу только для того чтоб "вынести как можно выше". Связь база-наследник должна появляться только в том случае если анализ выявил такую связь. То есть надо стараться, но знать меру.

> Если в «ЧИСТО ООП» переход в новое состояние всегда
> выполняется, начиная с рут (например, theApp), как было
Самое интересное, что в данном случае переход в новое состояние начинается тоже с рута, которым в данном случае является мессенгер и дальше вниз по иерархии. Такой подход имеет право на жизнь. Так как хотя в принципе могут регистрироваться все (расплющивая иерархию в одну горизонталь), но реально регистрируются только ключевые объекты, которые на самом деле можно считать и так находящимися на одном уровне (и в мессенгер они попадают тоже на один). А иерархия при этом не разрушается.

НО!!! Единственный реально необходимый пример такой архитектуры, я вижу в том, чтобы иметь несколько мессенгеров. То есть иметь несколько разных частей приложения в разных состояниях. А избавиться от "нудного кода" можно и проще и правильнее - прямо в CWinApp-наследнике. Что мешает хранить список детей в нем? И при рассылке - проходить по списку.

Добавлять в список нужно не путем регистрации, а прямо при создании объекта. То есть родитель (приложение) создает детей (виды и др.) и тут же добавляет их в список. Терминология немного прихрамывает, потому как динамические родитель-ребенок отличаются от статических база-наследник, по крайней мере, то что Я называю так, имеет разную основу. Родитель - это контейнер, хранящий детей (и умеющий добавлять/удалять их), а база - это просто базовый класс, который выражает утверждение, что наследник ЯВЛЯЕТСЯ кроме всего прочего и базой. То есть родитель-ребенок это связь типа мессенгер-подписчики (то есть содержит), а база-наследник - типа окно-диалог (то есть является). Короче родитель и ребенок это объекты, а база и наследник - классы. Так вот эти дочерние объекты должны иметь общий базовый класс и переопределять виртуальные функции реакции на некоторые события (о которых им будет сообщать родитель путем вызова этих функций).

От разрегистрации тоже можно избавиться используя замечательную особенность двусвязного списка. Особенность заключается в том, что любой элемент двусвязного списка можно удалить без знания из какого же собственно списка он удаляется (есть связи и вперед и назад - нужно просто связать элемент, предшествующий удаляемому, с элементом, последующим). Как это делается смотри виндовый LIST_ENTRY и сопутствующие макросы. Так вот в общую базу для детей приложения (и любых других детей) можно ввести поле связи. Тогда сам список нужно будет знать только при добавлении. А мы его и будем знать: создает ребенка родитель и он же добавляет ребенка в СВОЙ список. А ребенок в деструкторе (деструкторе той самой базы) сам удалится из списка. Разрегистрацию можно и ввести, но только для динамического изменения топологии дерева, по которому проходят сообщения (то есть для случая: ребенок остался, но не хочет получать сообщения).

Хотя лучше, чтобы сообщения получали вообще ВСЕ дети, а потом сами решали нужно оно им или нет (в переопределенных виртуальных функциях).

Если я непонятно выразился - могу даже набросать пример кода, только пока я не уверен в реальной необходимости - мне лениво :-).

> малой вероятности краха приложения при передаче только
> одного сообщения (здесь имеется ввиду ошибка программера).
Кроме того при росте количества зависимостей растет и вероятность самой этой ошибки. И как правильно было замечено растет также и вероятность, что эта ошибка станет фатальной.

> 3) Если мы уж включили объект в манагер, то обращаться к
> объекту следует только через посредник-манагер. Иначе
> конфликт логик – неизбежен, рано или поздно. Т.е. модель в
> некотором смысле становится очень зажатой.
>
> 4) плохая идея хранить где-то ещё указатели на объекты,
> которые мы уже включили в манагер.
Как сказал Оккам: "Не вводи сущности сверх необходимости". То бишь, зачем воодить мессенгер, если тот же список можно (и проще) хранить в самом приложении.

> 2) Если есть «безобидные» состояния и сообщения, например,
> ClearControls, то для последних можно сделать только один
> вызов, а не десять, как в «ЧИСТО ООП» (см. НУДНЫЙ КОД
> выше):

> // Броадкаст рассылка
> void CXXXXX:: ClearControls()
> {
> Messanger.ClearControls();
> }
Данный случай, при правильном использовании, так и остался "чистым ООП", как я написал выше :-), но вся загвоздка в том, что его нужно сознательно использовать правильно. Я же предпочитаю не надеяться на сознательность. Может быть даже я сам когда нибудь забуду о том, из каких побуждений я исходил и какие предположения делал. Если мне просто запретят делать, то что нежелательно - я вспомню быстрее :-)

Но вот даже при "чистом ООП" можно ограничиться одним вызовом, а не "нудным кодом". Я привел пример с CPage-ем. Оставаясь иерархичным этот пример делал все в одном цикле. Точно так же любой контейнер может хранить однородный список своего содержимого, а не 20 указателей на разные типы. И это не будет отходом от ООП, потому что данный список создается и поддерживается самим контейнером, и в него не может попасть никто посторонний. В случае с мессенгером - может, но ты лично решил этого не делать, что и придает твоему приложению видимость ООП. Точно так же MFC нарушает одно из требований ООП - абстракцию данных, но если СОЗНАТЕЛЬНО не использовать их, с MFC МОЖНО работать КАК С ООП.

> 3) В цепочку обработки сообщений легко включить любые
> объекты, а не только CWnd, см., шаг 5.
Это следствие общего базового класса и никак не связано с архитектурой, построенной на мессенгере. Ничто не мешает точно так же хранить список указателей в самом приложении не вынося его в отдельный глобальный объект.

> 4) Не то, что хорошее, но обнадёживает. Всё, что я постил в
> «шагах» работает.
:-)))

Вывод: текущий вариант можно доработать, подвергнув минимальным изменениям. То что рассылка сообщений вынесена в отдельный класс - хорошо (как сказал далай-Страутсруп: как можно большее количество сущностей должно иметь свой класс :-) ). Но то, что мессенгер один и глобальный - плохо. Нужно отнаследовать приложение от мессенгера и регистрировать подписчиков прямо при создании (этих подписчиков). Причем регистрироваться должен не сам подписчик, а родитель может зарегистрировать своего ребенка в качестве подписчика на сообщения СВОЕГО мессенгера.

Стоит упомянуть, что классы всех контейнеров, которые не могут напрямую перечислить свое содержимое (как табконтрол или окно) следует отнаследовать от мессенгера (ну или мессенгер-подобного класса).

Иерархия сохраняется, код упрощается, количество связей уменьшается, добавление объектов с разных уровней иерархии в один мессенгер не НЕЖЕЛАТЕЛЬНО, а НЕВОЗМОЖНО, короче все счастливы и все работает.
[C++] Спасибо за мнение 31.07.03 10:49  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Хотел было кинуть ответ в «приват», но как ни искал «приват», не нашёл. Вероятно, в движке форума этого нет? В «приват», т.к. думаю мой пост вряд ли будет интересен другим. Там, ниже – я почти по всем пунктам согласен.

********************
>>Небольшое замечание: на самом деле такой "нудный код" тоже является признаком плохого >>стиля. Уже хотя бы потому, что при добавлении нового объекта, менять нужно не только в том >>месте, где производится собственно добавление (что неизбежно), но и в данном (и других, >>потому как код нужно добавлять не только для ClearControls) обработчике. То же самое и с >>удалением.

Согласен – натяжка;)). Ну уж очень хотелось;)))



********************
>>Вывод: везде, где родителю необходимо что-то сделать со ВСЕМИ своими детьми (если >>количество больше 2-3-х), я ПРОЕКТИРУЮ класс так, чтобы родитель мог ПЕРЕЧИСЛИТЬ всех >>своих детей в цикле. Количество кода стремительно уменьшается. И при добавлении/удалении >>детей изменениям подвергается только само место добавления (удалять можно тоже >>перечислением).

Согласен при ~ >= 3. А так, можно и «нудным кодом».



********************
>>Еще раз напомню, что развитие парадигм программирования шло именно по пути уменьшения >>связей между частями программы. И даже если ООП ничего не говорит о таком подходе, важно >>понимать, куда именно движется программирование и соответсвовать, так сказать. То, что при >>подходе с перечислением уменьшается количество связей, думаю объяснять не стоит. Тем >>более что в этом вопросе никто не собирается вставлять палки в колеса. Все системные >>контейнеры (взять окна, tab control-ы и др.) хранят список дочерних объектов и предоставляют >>возможность их перечисления, если ты при проектировании своего класса будешь поступать так >>же - будет тебе счастье :-)


Понятно. Твой пример о TabCtrl, для которого я ранее сделал перепост, думаю, всё равно, будет полезен для многих программеров. Хотя, сам я делаю немного по другому, т.к. мне недостаточно только lparam – мов для пейджей. В неком CTabPageCtrl:public CTabCtrl у меня есть мембер-массив структур, который хранит кроме указателя на диалог пейджи ещё разную ерунду, которая мне необходима для работы с БД. Понятно, что и в этом случае, можно было обойтись lparam, присваивая ему адрес этой структуры, для которой делаем new в ран-тайм, когда вставляем пейдж. Но для такой мелочёвки мне было удобнее тупо, по быстрому и надёжно. Как обрабатывать перечисления – дело вкуса, имхо.



********************
>>Причем сказанное выше касается КАЖДОГО из родителей. Вплоть до диалога, который может >>выключить всех своих детей при помощи EnumChildWindows. Кроме того, как ленивый >>программист, я пытаюсь вынести общий код как можно выше в базовые классы. То есть >>добавлять/перечислять всех детей однотипных объектов одной и той же функцией (а не >>похожей, или даже copy/paste-нутой). То есть сделать, например, класс страницы, которая умеет >>выключать всех своих детей перечислением в for-е и отнаследовать все страницы от нее. >>Главное не переусердствовать и не вводить в иерархию ничего натянутого. Например, если >>способ перечисления для окна существенно отличается от способа перечисления для >>табконтрола, то не стоит пытаться сделать им общую базу только для того чтоб "вынести как >>можно выше". Связь база-наследник должна появляться только в том случае если анализ >>выявил такую связь. То есть надо стараться, но знать меру.

В начале 90-х мне надо было прикрутить графической «интерфейс» кастомизированного приложения в среде AutoCad 11.0 (досова штука). Писал в то время исключительно plain C,
хотя уже и был Турбо С++ 1.0. Поскольку это был плайн-С, а без абстракций – никуда, то многообразие данных моделировалось указателями на структуры данных, а многообразие методов – указателями на структуры функций. Для моделирования эламентов GUI ( в графике!!!! , под ДОСом. В то время – кул!!!) я именно ЭТО и делал (то, о чём ты говоришь). Т.е. выстраивались двусвязанные списки, которые были «контейнерами», и в которые «ставлялись» кнопки, диалоги и т.д. Там и были перечисления «контролов», которые обрабатывались в цикле. Были там и FillControls вместе с Дисплеем;))

Для чего я это рассказываю? Было всё это непросто. Хотя и работало. Не поверишь – даже продать удалось в одну контору (в единственном экземпляре)!!! Но вот посоветовать пойти по этому пути другим – у меня не хватает духу. Очень уж тернистый путь. Опасаюсь я, что это простое решение, которое можно отсоветовать кому-либо. Опасаюсь, т.к. помню ту головную боль. Но согласен – если сделать, то будет счастя. Может и так. Всёж С++ на дворе, не плайн-С.



********************
>> Если в «ЧИСТО ООП» переход в новое состояние всегда
>>выполняется, начиная с рут (например, theApp), как было
>>Самое интересное, что в данном случае переход в новое состояние начинается тоже с рута, >>которым в данном случае является мессенгер и дальше вниз по иерархии. Такой подход имеет >>право на жизнь. Так как хотя в принципе могут регистрироваться все (расплющивая иерархию в >>одну горизонталь), но реально регистрируются только ключевые объекты, которые на самом >>деле можно считать и так находящимися на одном уровне (и в мессенгер они попадают тоже на >>один). А иерархия при этом не разрушается.

Плохо то, что для «…иерархия при этом не разрушается…» очень много перекладывается на «вменяемость» программера;))



********************
>>НО!!! Единственный реально необходимый пример такой архитектуры, я вижу в том, чтобы >>иметь несколько мессенгеров. То есть иметь несколько разных частей приложения в разных >>состояниях.

Если ты заметил, то мессангер я для этого и сделал без сингентона. Хотя последнее очень красивое слово (это тоже паттерн). С Singleton–ном – это у того программера, для кода которого я сделал перепост в «шагах».



********************
>>Добавлять в список нужно не путем регистрации, а прямо при создании объекта. То есть >>родитель (приложение) создает детей (виды и др.) и тут же добавляет их в список. >>Терминология немного прихрамывает, потому как динамические родитель-ребенок отличаются >>от статических база-наследник, по крайней мере, то что Я называю так, имеет разную основу. >>Родитель - это контейнер, хранящий детей (и умеющий добавлять/удалять их), а база - это >>просто базовый класс, который выражает утверждение, что наследник ЯВЛЯЕТСЯ кроме всего >>прочего и базой. То есть родитель-ребенок это связь типа мессенгер-подписчики (то есть >>содержит), а база-наследник - типа окно-диалог (то есть является). Короче родитель и ребенок >>это объекты, а база и наследник - классы. Так вот эти дочерние объекты должны иметь общий >>базовый класс и переопределять виртуальные функции реакции на некоторые события (о >>которых им будет сообщать родитель путем вызова этих функций).

Если это совет мне, то - хороший.
Для этого надо сделать так, чтобы объекты умели быть одновременно и мессангерами и подписчиками. Т.е. если я правильно понял, то что было в начале твоего поста, и что здесь – ключевые объекты должны уметь выступать в роли «контейнера» (т.е. мессангера), если они включают в контейнерном мессангере «списки» других объектов-подписчиков, И одновременно должны уметь быть подписчиками, т.к. могут входить в «вышележащий» контейнер (мессангер).

Если это совет другим, кто читает пост – то ???????????? Люди поделают, поделают, а потом будут пилюваться в нас и костерить.



********************
>>От разрегистрации тоже можно избавиться используя замечательную особенность двусвязного >>списка. Особенность заключается в том, что любой элемент двусвязного списка можно удалить >>без знания из какого же собственно списка он удаляется (есть связи и вперед и назад - нужно >>просто связать элемент, предшествующий удаляемому, с элементом, последующим).

Это и есть ОБЯЗАТЕЛЬНАЯ разрегистрация, когда подписчик уходит со сцены;)))



********************
>> Как это делается смотри виндовый LIST_ENTRY и сопутствующие макросы.

Думаю, что это мелкософт у меня «посмотрел»;)))
Когда я это делал для досовых задач в плайн-С, виндовз ещё не было. Я постил, чуть выше.
Шучу, конечно.



********************
>>А ребенок в деструкторе (деструкторе той самой базы) сам удалится из списка.

Мессангер так и работает, в смысле в деструкторе.



********************
>> Разрегистрацию можно и ввести, но только для динамического изменения топологии дерева, по >>которому проходят сообщения (то есть для случая: ребенок остался, но не хочет получать >>сообщения).

Кстати да. Хотя мне трудно придумать, где это может быть на практике – «…ребенок остался, но не хочет получать сообщения ….»


********************
>>Я же предпочитаю не надеяться на сознательность. Может быть даже я сам когда нибудь забуду >>о том, из каких побуждений я исходил и какие предположения делал. Если мне просто запретят >>делать, то что нежелательно - я вспомню быстрее :-)

То, что и меня это смущает, я уже постил.


********************
>>Но вот даже при "чистом ООП" можно ограничиться одним вызовом, а не "нудным кодом". Я >>привел пример с CPage-ем. Оставаясь иерархичным этот пример делал все в одном цикле. >>Точно так же любой контейнер может хранить однородный список своего содержимого, а не 20 >>указателей на разные типы. И это не будет отходом от ООП, потому что данный список >>создается и поддерживается самим контейнером, и в него не может попасть никто посторонний. >>В случае с мессенгером - может, но ты лично решил этого не делать, что и придает твоему >>приложению видимость ООП.

1) Если контейнер будет мессангером, то для такого КЛАССА можно в принципе по ООП-вски определить его поведение – каких деток он умеет включать в свой список подписчиков.

2) Если сделать п.1, ДАЖЕ ЕСЛИ ОБЪЕКТЫ БУДУТ РАЗНЫХ КЛАССОВ, то всё равно будет по ООП-вски, т.к. грубо говоря, будем в цикле говорить всем – «нарисоваться», и все нарисуются в соответствие с поведением своих классов.



********************
>>Вывод: текущий вариант можно доработать, подвергнув минимальным изменениям. То что >>рассылка сообщений вынесена в отдельный класс - хорошо (как сказал далай-Страутсруп: как >>можно большее количество сущностей должно иметь свой класс :-) ). Но то, что мессенгер один >>и глобальный - плохо. Нужно отнаследовать приложение от мессенгера и регистрировать >>подписчиков прямо при создании (этих подписчиков). Причем регистрироваться должен не сам >>подписчик, а родитель может зарегистрировать своего ребенка в качестве подписчика на >>сообщения СВОЕГО мессенгера.
>>Стоит упомянуть, что классы всех контейнеров, которые не могут напрямую перечислить свое >>содержимое (как табконтрол или окно) следует отнаследовать от мессенгера (ну или мессенгер->>подобного класса).
>>Иерархия сохраняется, код упрощается, количество связей уменьшается, добавление объектов >>с разных уровней иерархии в один мессенгер не НЕЖЕЛАТЕЛЬНО, а НЕВОЗМОЖНО, короче >>все счастливы и все работает.

Да. Надо попробовать. То, что от мессангера можно будет отнаследлваться, в любом классе, понятно. Надо подумать, как сделать так, чтобы такой класс будет одновременно отнаследоваться от подписчика. Это надо, так как сам контейнер, может быть включён в контейнер «выше».

PS. В очередной раз спасибо.
[C++] Спасибо за мнение 31.07.03 13:44  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> Хотел было кинуть ответ в «приват», но как ни искал
> «приват», не нашёл. Вероятно, в движке форума этого нет? В
> «приват», т.к. думаю мой пост вряд ли будет интересен
> другим. Там, ниже – я почти по всем пунктам согласен.
У меня в профиле есть и мыло и аська:
amurul@mail.ru
14031980

> Согласен при ~ >= 3. А так, можно и «нудным кодом».
Ага. Придется больше написать кода, поддерживающего список из одного-двух элементов. Проще прямо вызвать. Кроме того, такой прямой вызов чаще всего будет прозрачнее и понятнее.

> мне было удобнее тупо, по быстрому и надёжно. Как
> обрабатывать перечисления – дело вкуса, имхо.

Вот об этом я и говорю. Главное ПЕРЕЧИСЛЯТЬ, а как - не главное :-)

> В начале 90-х мне надо было прикрутить графической
> «интерфейс» кастомизированного приложения в среде AutoCad
> 11.0 (досова штука). Писал в то время исключительно plain
> C,
> хотя уже и был Турбо С++ 1.0. Поскольку это был плайн-С, а
> без абстракций – никуда, то многообразие данных
> моделировалось указателями на структуры данных, а
> многообразие методов – указателями на структуры функций.
Ядро линуха именно так и написано :-) Вообще ОО-подход можно прикрутить к любому языку. Другое дело поддержка ООП в самом языке.

> которое можно отсоветовать кому-либо. Опасаюсь, т.к. помню
> ту головную боль. Но согласен – если сделать, то будет
> счастя. Может и так. Всёж С++ на дворе, не плайн-С.
На самом деле все не так дико. Наоборот такой подход УМЕНЬШАЕТ количество кода, которое надо написать и за которым нужно следить.

> Плохо то, что для «…иерархия при этом не разрушается…»
> очень много перекладывается на «вменяемость» программера;))
Ага, мне это тоже не нравится, тем более что выход есть

> Если ты заметил, то мессангер я для этого и сделал без
> сингентона. Хотя последнее очень красивое слово (это тоже
> паттерн). С Singleton–ном – это у того программера, для
> кода которого я сделал перепост в «шагах».
Как я уже говорил, не силен я в дизайн паттернах, и слово "синглтон" мне не говорит совершенно ни о чем. Может я и упустил в жизни многое, но просто пока жизнь с ними не сталкивала, а времена, когда освоение компутерных просторов проходило в свободном режиме (делаю что хочу, учу что попадется) давно позади - сейчас времени нет осваивать, то в чем я не уверен, что оно мне пригодится.


> Если это совет мне, то - хороший.
> Для этого надо сделать так, чтобы объекты умели быть
> одновременно и мессангерами и подписчиками. Т.е. если я
> правильно понял, то что было в начале твоего поста, и что
> здесь – ключевые объекты должны уметь выступать в роли
> «контейнера» (т.е. мессангера), если они включают в
> контейнерном мессангере «списки» других
> объектов-подписчиков, И одновременно должны уметь быть
> подписчиками, т.к. могут входить в «вышележащий» контейнер
> (мессангер).
Ну да. Множественное наследование спасет отца русской демократии :-) Опять таки, будет хорошей идеей вынести и получателя и отправителя сообщения в отдельный класс и закрепить их интерфейсы. В базовый класс получателся можно ввести поля связей (для списка) и метод "добавиться в список"

> Если это совет другим, кто читает пост – то ????????????
> Люди поделают, поделают, а потом будут пилюваться в нас и
> костерить.
:-)))
Вообще, это даже не совет, а просто мое мнение. Воспользуются им напрямую, или просто используют как основу для своего мнения - мне все равно. Как я уже писал: все постится AS IS - автор не несет ответсвенности bla-bla-bla... :-)


>*********************
> >>От разрегистрации тоже можно избавиться используя
> замечательную особенность двусвязного >>списка.
> Особенность заключается в том, что любой элемент
> двусвязного списка можно удалить >>без знания из
> какого же собственно списка он удаляется (есть связи и
> вперед и назад - нужно >>просто связать элемент,
> предшествующий удаляемому, с элементом, последующим).
>
> Это и есть ОБЯЗАТЕЛЬНАЯ разрегистрация, когда подписчик
> уходит со сцены;)))
Ну да. Делается и в том и в другом случае в деструкторе, что избавляет нас от необходимости следить за валидностью указателей.

> Думаю, что это мелкософт у меня «посмотрел»;)))
:-)))))

>*********************
> >>А ребенок в деструкторе (деструкторе той самой
> базы) сам удалится из списка.
>
> Мессангер так и работает, в смысле в деструкторе.
Я к тому, что в этом плане (когда родитель следит за детьми) код не усложняется и списки всегда можно держать валидными без лишней головной боли (отслеживания всех мест, где МОЖЕТ удалиться ребенок)

>*********************
> >> Разрегистрацию можно и ввести, но только для
> динамического изменения топологии дерева, по
> >>которому проходят сообщения (то есть для случая:
> ребенок остался, но не хочет получать >>сообщения).
>
> Кстати да. Хотя мне трудно придумать, где это может быть на
> практике – «…ребенок остался, но не хочет получать
> сообщения ….»
Мне тоже трудно. Кроме того усложнится код: придется хранить два списка: список всех объектов (для последующего удаления) и список подписчиков. Я считаю, что вполне хороший метод отказаться от получения сообщений: получить и проигнорировать.

> 1) Если контейнер будет мессангером, то для такого КЛАССА
> можно в принципе по ООП-вски определить его поведение –
> каких деток он умеет включать в свой список подписчиков.
Деток-подписчиков, точно так же как сделано сейчас. Любой класс, который предполагается сделать подписчиком делается наследником такого класса.

> 2) Если сделать п.1, ДАЖЕ ЕСЛИ ОБЪЕКТЫ БУДУТ РАЗНЫХ
> КЛАССОВ, то всё равно будет по ООП-вски, т.к. грубо говоря,
> будем в цикле говорить всем – «нарисоваться», и все
> нарисуются в соответствие с поведением своих классов.
Они и будут разных классов, просто наследование и виртуальные функции помогут поместить в список указатели на однородные объекты (один тип), и обеспечить динамический полиморфизм при вызове интерфейсов подписчика


> Да. Надо попробовать. То, что от мессангера можно будет
> отнаследлваться, в любом классе, понятно. Надо подумать,
> как сделать так, чтобы такой класс будет одновременно
> отнаследоваться от подписчика. Это надо, так как сам
> контейнер, может быть включён в контейнер «выше».
Множественное наследование. Кроме того и мессенгер и подписчик объявляют виртуальные функции, а реализация переопределяет.

> PS. В очередной раз спасибо.
[C++] ШАГ 5. Сообщения пользователя. Слон родил мыша 27.07.03 06:23  
Автор: void <Grebnev Valery> Статус: Elderman
Отредактировано 27.07.03 06:26  Количество правок: 1
<"чистая" ссылка>
Простите, убогого, но немогу кончить. Уже семь дней...

*** ШАГ 5. Получился Медиатор *

На шаге 4 мы придумали не такой уж плохой механизм рассылки и обработки широковещательных сообщений. Я говорю мы, поскольку, на форумах кивали в сторону возможности использования SendMessage для обработки сообщений. При этом вызов методов CMessanger, например, CMessanger::EnableControls, CMessanger::ClearControls удобнее, чем рассылка соответствующих сообщений при помощи «универсального» метода CMessanger::SendMessage. В последнем случае необходимо по условному переходу ещё передать управление конкретному обработчику в зависимости WPARAM wParam, LPARAM lParam.
Однако здесь привлекательна, лишь форма (использование theMsg.EnableControls вместо theMsg.SendMessage(UM_NOTIFY, IDW_ALL, UM_ERASE, 0)). По существу CMessanger::EnableControls всё равно используется метод CMessanger::SendMessage, а тот в свою очередь использует метод рассылки сообщений MFC CWnd-объектам или API, см. врезку кода выше. Да, погнавшись за внешней стороной дела – замоделировались. Как справедливо, заметил один из участников форума, так мы «едем из Москвы в Подмосковье через Колыму». Надо сказать, что это участник форума весьма вежливый человек, и выразился мягко, поскольку едем не только через Колыму, но … как мудрёно! А сколько «технического» кода придётся написать? То, что надо написать строчки кода для каждого из методов ИНТЕРФЕЙСА наших команд в CMessanger (EnableControls, ClearControls и т.д) – это понятно, как бы мы не делали. Неизбежные накладные расходы, если необходим броадкастинг. Но то, что для каждого CWnd-класса придётся ещё и дополнительные макросы писать – последняя капля. Макросы месадж-мапа простые, но их придётся писать для каждого класса CWnd-объектов, которые участвуют в широковещательной рассылке. Ну и сами методы CWnd-объектов. Где уж там до useful programming style... Всё в топку!

Для того, чтобы отказаться от MS-SendMessage, надо сделать так, чтобы объекты, участвующие в оповещениях о событиях приложения, имели соответствующие методы, которые они наследовали бы от некого объекта CMsgItem, уже имеющего соответствующий командный «интерфейс». Для вызова виртуальных методов этого «интерфейса» CMessаnger должен знать адрес vtb CMsgItem, а не указатель на сам CWnd-объект. Расширим определение CWndInfo, добавив CMsgItem* m_pMsgItem, и немного упростим мембер-функции CMessаnger:


///////////////////////////////////////////////////
//
// file “utilwnd.h”
//
///////////////////////////////////////////////////

#include <afxtempl.h>

#define IDW_ALL 0 // send message to all CWnd objects

class CMsgItem; // forward declaration

////////////////////////////////////////////////////
// CMessanger class

class CMessanger {
public:
class CWndInfo { // object info class
public:
void* m_pWnd;// pointer to the object registered
UINT ID; // user symb.const. for the object registered
CMsgItem* m_pMsgItem;
public:
CWndInfo() : m_pWnd(NULL), m_pMsgItem(NULL), ID(0) {};
CWndInfo(void* pWnd, CMsgItem* pMsgItem = NULL, UINT ID_WND = 0)
{
m_pWnd = pWnd; m_pMsgItem = pMsgItem; ID = ID_WND;
}
~CWndInfo() {};
};
public:
CMessanger() : status(0) {};

// register object in CWndInfo array. Save pointer to the object,
// command inteface and user defined symbolic constant
// ID_WND for this object
void Register(void* pWnd, CMsgItem* pMsgItem, UINT ID_WND)
{
m_aWnd.Add(new CWndInfo(pWnd, pMsgItem, ID_WND));
}

// get CWnd* pointer from CWndInfo array by user defined constant ID_WND
CWnd* GetWnd(UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == m_aWnd.GetAt(i)->ID)
return (CWnd*) (m_aWnd.GetAt(i)->m_pWnd);
return NULL;
}
// complete status of message
UINT GetStatus(void) { return status; }
void SetStatus(UINT _status) { status = _status; }

//**** main message handling****
virtual bool FillControls (UINT message, UINT ID_WND = IDW_ALL) {return true;};
virtual bool DisplayControls(UINT message,UINT ID_WND = IDW_ALL) {return true;};

//**** additional message handling****
// broadcast (ID_WND == IDW_ALL) or direct message handling
void SendMessage(UINT message, UINT ID_WND = IDW_ALL,
WPARAM wParam = 0,LPARAM lParam = 0);

// special message handling
void EnableControls(bool Еnable, UINT ID_WND = IDW_ALL);
void ClearControls(UINT ID_WND = IDW_ALL);

private:
CArray <CWndInfo*, CWndInfo*> m_aWnd;
UINT status;
};

extern CMessanger theMsg;


////////////////////////////////////////////////////
// CMsgItem class

class CMsgItem {
public:
void Register(CMessanger* pMsg, void* pWnd, UINT ID_WND)
{
pMsg->Register( pWnd , this, ID_WND);
};
//**** additional message handling****
virtual void SendMessage(UINT message, WPARAM wParamMsg=0, LPARAM lParam=0){}

// special message handling
virtual void EnableControls(bool Enable){}
virtual void ClearControls (void) {}
};
//-----------------------------------------------------



///////////////////////////////////////////////////
//
// file “utilwnd.cpp”
//
///////////////////////////////////////////////////
#include "StdAfx.h"
#include "utilwnd.h"

CMessanger theMsg;

// general broadcast (ID_WND == IDW_ALL) or direct message handling
void CMessanger::SendMessage(UINT message , UINT ID_WND, WPARAM wParam, LPARAM lParam)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->SendMessage(message, wParam, lParam);
}
//-------------------------------------------------------------
// special message handling
void CMessanger::EnableControls(bool Enable, UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->EnableControls( Enable );
}
//-------------------------------------------------------------
void CMessanger::ClearControls( UINT ID_WND )
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID )
m_aWnd.GetAt(i)->m_pMsgItem->ClearControls();
}
//-------------------------------------------------------------


Как видно, класс CMsgItem представляет некий почти «абстрактный» командный интерфейс, и состоит только из виртуальных методов и дополнительной функции Register (одноимённая функция есть у CMessanger), которая введена косметически, для удобства регистрации. Здесь перечисляются все команды, которые необходимо выполнять согласно введённому раннее enum app_state. Выше для примера приведены только два метода. Дополнительно оставлен метод SendMessage, который теперь не имеет ничего общего с MFC или API(одноименную функцию обрабатывает CMessanger исключительно сам). Это сделано на «всякий пожарный», если во всех классах CWnd-объектов, наследующих, от CMsgItem, приемлемо ограничиться обработкой сообщений-команд только в SendMessage, например, в свиче, вместо переопределения большого числа мембер-функции EnableControls, ReadControls, и т.д в классе, derived от CMsgItem . Если кому-то нравится это, то последние можно удалить из определения CMsgItem и CMessanger. Кстати, может стоит это сделать? Последнее, что занудно уточню, и что и так всем понятно - методы CMsgItem никогда не вызываются напрямую при передаче сообщений. Это делает CMessanger (выступая от имени того, кто вызвал его соответствующий метод), вызывая переопределённый метод derived CWnd-класса(кого здесь стошнило – извините).

В классе CMessanger осталось всё без изменений, за исключение тех, что связаны с появлением на свет CMsgItem, и GetStatus(void), SetStatus(UINT _status). Об этих двух методах, как и о набивших всем оскомину функциях FillControls/DisplayControls – чуть ниже в резюме. Кроме того, в CWndInfo указатель на оконный объект, который пока И НЕ НУЖЕН, переделан на void*. Если потребуется этот CWnd-объект (а так, в принципе любого класса), то для использования его методов в теле FillControls/DisplayControls можно вызвать немного переделанную функцию GetWnd.

Как этим всем пользоваться? Например, если диалог CMainDialog будем наследовать и от CMsgItem, то так:

///////////////////////////////////////////////////////////////////
//
// CMainDialog dialog declaration
//
///////////////////////////////////////////////////////////////////

class CMainDialog : public CDialog, CMsgItem
{
// CDialog, CMainDialog declarations


public:

//////////////////////////////////////////////////
// CMsgItem interface general method
void SendMessage(UINT message, WPARAM wParamMsg, LPARAM lParam)
{
switch (message)
{
case UM_READ:
// Do something
break;
case UM_ERASE:
// Do something
break;
};
}

// CMsgItem interface special methods
void EnableControls(bool Enable)
{
// Do something
}
void ClearControls (void)
{
// Do something
}

};


///////////////////////////////////////////////////////////////////////
//
// CMainDialog dialog implementation
//
///////////////////////////////////////////////////////////////////////

//--------------------------------------------------------------------
BOOL CMainDialog::OnInitDialog()
{
CDialog::OnInitDialog();

// Inherited CMsgItem metod
Register(&theMsg, this, IDW_MAIN_DIALOG);

return TRUE;
}
//--------------------------------------------------------------------
void CMainDialog::OnBnClickedEditButton()
{
// «одним махом», широковещательной рассылкой очищаем все контролы
// тех объектов, о которых известно theMsg
theMsg.ClearControls();
}
//--------------------------------------------------------------------

В приведённом выше коде не хватает некого метода CMessanger – UnRegister. Не то, что бы я его не показал на форуме. Я просто про него забыл!!! из-за своего балбесянства. Последствия, которые могут быть, ясны – крах. Про него мне подсказал пост одного из участников форумов. Ниже, без персоналий привожу ЕГО фрагмент кода, где такой метод void CMediator::UnregisterRecipient(CCommandInterface *Rec) присутствует. Для тех, кто заинтересовался, и кто ещё не смотрел подобную проблематику, ооооочень рекомендую посмотреть код ЭТОГО человека. Прошу модераторов простить за перепост. Там же комментарии. Код чище, стильнее, чем у меня. Так, что некоторые вещи своего «рабочего» класса CMessanger я наглым образом перепишу, как У НЕГО:

//--------------------------------------------------------------------
//H-файл

class CCommandInterface // у меня это CMsgItem
{
public:
virtual void Execute(CCommand *cmd) = 0;
};

class CMediator // у меня это CMessanger
{
public:
static CMediator* Instance();

void RegisterRecipient(CCommandInterface *newRec);
void UnregisterRecipient(CCommandInterface *Rec);
void SendMessage(CCommand *cmd);
void SendMessage(CCommandInterface *whom, CCommand *cmd);

protected:
CMediator();
private:
static CMediator* m_instance;
CArray<CCommandInterface*, CCommandInterface*> m_recipients;
};

//--------------------------------------------------------------------
//CPP-файл

CMediator* CMediator::m_instance = NULL;

CMediator::CMediator()
{
}

CMediator* CMediator::Instance()
{
if (m_instance == NULL)
{
m_instance = new CMediator;
ASSERT(m_instance);
}
return m_instance;
}

void CMediator::RegisterRecipient(CCommandInterface *newRec)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (newRec == m_recipients[i])
{
return;
}
}
m_recipients.Add(newRec);
}

void CMediator::UnregisterRecipient(CCommandInterface *Rec)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (Rec == m_recipients[i])
{
m_recipients.RemoveAt(i);
return;
}
}
}

void CMediator::SendMessage(CCommand *cmd)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
m_recipients[i]->Execute(cmd);
}
}

void CMediator::SendMessage(CCommandInterface *whom, CCommand *cmd)
{
for (int i = 0; i < m_recipients.GetSize(); i++)
{
if (whom == m_recipients[i])
{
m_recipients[i]->Execute(cmd);
}
}
}

//--------------------------------------------------------------------
//CPP- где что-то делаем

любое заинтересованное окно получает указатель на медиатор таким и только таким образом (конструктор медиатора защищенный):
CMediator *mediator = CMediator::Instance();

и регистрирует себя, скажем в своем конструкторе
mediator->RegisterRecipient((CCommandInterface*)this);

В деструкторе, соответственно разрегистрирует
CMediator *mediator = CMediator::Instance();
mediator->UnregisterRecipient((CCommandInterface*)this);

//--------------------------------------------------------------------

Человек, который написал этот код, не участвует в данном форуме. Поэтому любая критика в его адрес была бы неуместной. Это моя просьба НЕ к тем, кто уже участвует в топике данного форума, т.к. это люди воспитанные и интеллигентные. В части построения GUI - критика тоже была бы не уместна, поскольку человек решает несколько иные задачи. Насколько я понял, это те задачи, для которых собственно и были придуманы паттерны mediator и observer. Так, что этот человек НЕ ЯВЛЯЕТСЯ ДАЖЕ МОИМ СТОРОННИКОМ. Он как бы сказал: «Посмотри. Может сгодится…». Мне сгодилось.


*РЕЗЮМЕ И ДОПОЛНЕНИЯ К ШАГ 5**

1) Можно посылать широковещательные сообщения всем объектам сразу, или в отдельности при помощи медиатора CMessanger, выполняя рассылку без использования SendMessage MFC или API.

2) То, что нет SendMessage MFC или API – это хорошо. Не сложно увидеть, что наряду с CWnd–объектами (derived видов и диалогов и т.д.), для которых выше я и «шагал», CMessanger умеет рассылать команды о состояниях enum app_states ЛЮБЫМ объектам. Создайте не CWnd-класс, например, некий CDatabaseOperations, который будет наследовать от CMgItem, и он тоже может быть включён в общую цепочку обработки сообщений.

3) В CMessanger оставлены виртуальные методы FillControls/DisplayControls. Простая широковещательная рассылка в ряде случаев может привести к «нежелательным эффектам», т.к. иногда необходимо контролировать ПОСЛЕДОВАТЕЛЬНОСТЬ, в которой объекты-подписчики будут выполнять команды CMessanger и РЕЗУЛЬТАТЫ этой обработки. Посредник CMessanger - совсем простой и нечего такого не умеет делать. Приведу цитату общего характера относительно паттерна Observer, где тоже присутствует броадкаст:

«…
неожиданные обновления. Поскольку наблюдатели не располагают информацией друг о друге, им неизвестно и о том, во что обходится изменение субъекта. Безобидная, на первый взгляд, операция над субъектом может вызвать целый ряд обновлений наблюдателей и зависящих от них объектов. Более того, нечетко определенные или плохо поддерживаемые критерии зависимости могут стать причиной непредвиденных обновлений, отследить которые очень сложно.
Эта проблема усугубляется еще и тем, что простой протокол обновления не содержит никаких сведений о том, что именно изменилось в субъекте. Без дополнительного протокола, помогающего выяснить характер изменений, наблюдатели будут вынуждены проделать сложную работу для косвенного получения такой информации.
…»

Бороться с негативом ясно, как. Есть следующие возможности:
А) для бродкаст-манагера «моделировать» асинхронную обработку подписчиками. Сложность решения возрастает неимоверно.

Б) делать умных подписчисиков, запрашивающих стэйт и другое, пока даже не знаю, что…
В) дробить сообщения на более мелкие, например, UM_READ - на UM_READ и UM_XXX_READ, что также не универсально и может перегружать логику приложения событиями
Г) делать что-нибудь нууууууууууу, совсем уж простое. Это я и делаю, сохраняя FillControls и DisplayControls в качестве виртуальных методов «медиатора». Как можно использовать FillControls и DisplayControls для группы подписчиков??? тоже понятно – если можно, то обработать мессагу бродкастом. Если необходим анализ очерёдности, или ещё чего (о чём можно знать только реализуя это в дочке CMessanger конкретного проекта) – то сделаем это. Короче говоря, FillControls и DisplayControls – это дополнительные посредники, вернее дополнительное средство анализа в посреднике (медиаторе). Если так не делать, то прямая дорога к п. А)Б)В), имхо. Например, можно было бы делать такой вызов theMsg.FillControls(UM_READ_DB):

//---------------------------------------------
// h-file
class CMyMsg: public CMessanger
{
public:
bool FillControls (UINT message);
};
extern CMyMsg theMsg;


//----------------------------------------------------
// cpp-file
bool CMyMsg::FillControls(UINT message)
{
switch (message)
{
case UM_ERASE:
ClearControls();


break;
case UM_READ_DB:
FillControls(UM_ERASE);
if( GetStatus() == ST_XXXX )
((Cxxx*) GetWnd(IDW_XXXX))->XXXX();


break;
}
return true;
}
//----------------------------------------------------

PS.
Ещё раз благодарю всех тех, кто откликнулся. Уже не обещаю, что дальше не буду писать столь пространно и столь длинные посты, т.к., думаю сделать завтра последний пост типа «ИТОГИ. КАК БЫТЬ?».

To amirul
amirul, там я хотел бы сделать перепост одного хорошего фрагмента твоего ответа мне. Этика, понимаш… Буду писать только хорошее. Если не возражаешь, то я сделаю это, поскольку «колеблющиеся в ООП» могли пропустить ЭТО между прочим.
[C++] ШАГ 5. Сообщения пользователя. Слон родил мыша 27.07.03 11:03  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> ///////////////////////////////////////////////////
> //
> // file “utilwnd.h”
> //
> ///////////////////////////////////////////////////
>
> #include <afxtempl.h>
>
> #define IDW_ALL 0 // send message to all CWnd objects
>
> class CMsgItem; // forward declaration
>
> ////////////////////////////////////////////////////
> // CMessanger class
>
> class CMessanger {
// skipped
> };
>
> extern CMessanger theMsg;
>
>
> ////////////////////////////////////////////////////
> // CMsgItem class
>
> class CMsgItem {
// skipped
> };
> //-----------------------------------------------------
> ----
Все сдаюсь ;-). Прнципиально мне больше не к чему придраться. Собственно, здесь имеется именно то, о чем я говорил, только в новое состояние переводится не все приложение, а мессенгер. Что ж допускаю, что могут быть проекты, где разные части приложения в некоторые моменты могут находиться в разных состояних, и потому такая схема там вполне оправдана.

> занудно уточню, и что и так всем понятно - методы CMsgItem
> никогда не вызываются напрямую при передаче сообщений. Это
> делает CMessanger (выступая от имени того, кто вызвал его
> соответствующий метод), вызывая переопределённый метод
> derived CWnd-класса(кого здесь стошнило – извините).
Не знаю почему должно тошнить, но отмечу ;-). Есть языковые средства, чтоб не приходилось уточнять, а описать это все в коде. А именно, методы CMsgItem-а, которые вызываются только из CMessanger-а сделать приватными и при этом объявить CMessanger-а другом. Это просто косметика, но явный запрет на вызов все же лучше, чем оговорка, что не стоит этого делать.

> неуместной. Это моя просьба НЕ к тем, кто уже участвует в
> топике данного форума, т.к. это люди воспитанные и
> интеллигентные. В части построения GUI - критика тоже была
Такие реверансы ;-) Аж непривычно. Не то чтобы тут была в почете грубость, но и особо извиняться при каждом обращении не нужно :-) Это твое неотъемлимое право - говорить.

> PS.
> Ещё раз благодарю всех тех, кто откликнулся. Уже не обещаю,
> что дальше не буду писать столь пространно и столь длинные
> посты, т.к., думаю сделать завтра последний пост типа
> «ИТОГИ. КАК БЫТЬ?».

> To amirul
> amirul, там я хотел бы сделать перепост одного хорошего
> фрагмента твоего ответа мне. Этика, понимаш… Буду писать
> только хорошее. Если не возражаешь, то я сделаю это,
> поскольку «колеблющиеся в ООП» могли пропустить ЭТО между
> прочим.
Ради бога. Мои посты распространяются AS IS в рамках Public Domain ;-)
[C++] ШАГ 5. Сообщения пользователя. Слон родил мыша 28.07.03 01:45  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
to amirul

> Все сдаюсь ;-). Прнципиально мне больше не к чему
> придраться.

А зря. Давай, когда будем подводить черту, всёж вернёмся к этому.
Я на самом деле, не из-за "реверансов", в сомнениях сейчас относительно возможности ведения "горизонталей". Они здесь, хоть и "замаскированно", но присутствуют. Подрыв устоев ООП - ЗЛО.
Хорошо, если программер знает о своей ереси, и использует это ЗЛО дозированно для быстрого достижения цели. Если довести идею до абсурда, и использовать ЭТО в там где надо и где нет - то только держись...

В общем, в раздумьях я…
[C++] В том коде, по поводу которого я сдался горизонталей вроде нет 28.07.03 04:23  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> to amirul
>
> > Все сдаюсь ;-). Прнципиально мне больше не к чему
> > придраться.
>
> А зря. Давай, когда будем подводить черту, всёж вернёмся к
> этому.
> Я на самом деле, не из-за "реверансов", в сомнениях сейчас
> относительно возможности ведения "горизонталей". Они здесь,
> хоть и "замаскированно", но присутствуют. Подрыв устоев ООП
> - ЗЛО.
Вроде там все хорошо. В плане: наверху мессенгер, на его сообщения можно подписаться. Сообщения рассылает только он, когда его снизу переводят в новое состояние. В общем может я невнимательно смотрел, но горизонталей я там не заметил.

Немного смущают case-ы в коде, который приведен ниже по тексту (его я не комментировал). Вроде как горизонталь, потому как существует функция, которая пытается знать обо всех возможных вариантах. Но case-ы насколько я заметил идут по состояниям системы. А состояния вряд ли будут интенсивно добавляться/удаляться даже при расширении проекта. Так что это небольшое зло, если не пытаться раздувать там логику, а просто передавать состояние (путем вызова последовательности функций) куда надо.

> Хорошо, если программер знает о своей ереси, и использует
> это ЗЛО дозированно для быстрого достижения цели. Если
> довести идею до абсурда, и использовать ЭТО в там где надо
> и где нет - то только держись...
Это да. Я в такие места обычно вставляю комментарий FIXME или TODO. То бишь: пока работает, но надо переделать, когда больше нечем будет заняться :-).

> В общем, в раздумьях я…
Ну давай. О результатах сообщай - покритикуем :-)
[C++] Отвечаю в корень, потому как не хочу квотить длинный пост :-) 25.07.03 13:30  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
Отвечу на все что вспомню

Насчет "нельзя" вводить глобальных обработчиков. Если бы этого действительно нельзя было делать, то это было бы запрещено самим языком. А раз язык разрешает - значит действительно просто "крайне нежелательно". Но! Если уж следовать одной из парадигм программирования, то следовать как можно точнее. Если хочется писать на Plain API (большинство своих программ я так и пишу - ну не люблю я MFC) - писать от начала и до конца и не смешивать. Если использовать ООП, то пытаться сделать как можно более ОО-ным.
***************

К сожалению я никогда не сталкивался с дизайн паттернами и даже не представляю себе что это такое. Изучить за пару часов до такой степени, чтобы давать советы я не смогу, поэтому здесь тебе придется решать самому.
***************

Насчет удобства броадкаст рассылки - все таки я считаю это весьма спорным. Но объяснить это вряд ли смогу. Просто весь мой опыт программирования показыает, что такие вещи всегда можно решить по-другому (если уж мы вообще решили связываться с ООП), причем малой кровью.
***************

По поводу примера со Сценой. Сцена не может быть "тупой" по определению (если она правильно спроектирована/реализована). Иначе можно предположить что и Животное может быть тупым и не уметь передвигаться (а просто дергать конечностями в приступе эпилепсии). Возможно, пример слишком упрощенный, и Сцену можно было бы раздробить еще на несколько классов, но она всегда управляет тем, что на ней происходит. И именно Сцена является диспетчером того, в какой последовательности кто ходит. Причем работает с глобальными объектами - слонами, мышами, дрессировщиками и т.д. А если Животные посходили с ума и начали двигаться хаотично, значит или так надо, или Сцена смоделирована неверно.
***************

Опасения, что ООП-шная иерархия будет трудно поддаваться изменениям несколько необоснованны. Как раз в ООП можно изменить любую часть проекта и все останется работать, если не изменять интерфейсы. Проблемы возникнут как раз с глобальными (пусть даже инкапсулированными в какой-то класс-диспетчер) обработчиками. Основной путь по которому развивались парадигмы программирования - это уменьшение количества зависимостей между частями программы. Потому как, чем их меньше, тем меньше вероятность, что программист чего-то упустит.

В этом плане непосредственные зависимости объекта-родителя с парой-тройкой своих детей гораздо выгоднее (рост количества связей линейный по отношению к количеству объектов), чем зависимость каждого объекта от диспетчера, а диспетчера от каждого объекта. В этом случае проявляется связь каждый-с-каждым (квадратичный рост зависимостей).
Это хорошо видно на примере маршрутизации TCP/IP (я приводил этот пример в одном из предыдущих постов).
****************

> На самом деле хотелось рассмотреть более общий случай, когда объектов
> достаточно много, так что выстроить их иерархию не то, чтобы трудно, а
> трудно уследить за всем по мере до_проектирования задачи.

Как раз в общем случае, чем больше объектов и чем сложнее между ними зависимости, тем проще оказывается с ними работать, когда они собраны в иерархию. Проектирование этой иерархии в общем случае выполняется задолго до того, как написана хоть одна строчка кода. Но после того, как она спроектирована - все становится почти тривиальным (и реализация, и сопровождение, и изменение). Изменение конечно же будет тривиальным, если новые требования не заставляют сломать всю иерархию, а только добавляют/удаляют ветви/листья.
****************

> В BCB++ c таб-компонентом проще. Из облати его видимости можно получить
> доступ ко всем контролам на каждой закладке, т.е. обабатывать поведение
> контролов по «ЕнаблеКонтролс» в одном методе, а не в 20-и.

Да там есть класс для страницы. Там вообще многие вещи от того, что вся форма создается только динамически, совершенно не полагаясь на ресурс типа RT_DIALOG. Я считаю это скорее недостатком, ну да ладно. В MFC тоже можно сделать выключение целой страницы, используя EnumChildWindows, причем сделать это в одном месте - в базовом классе CPage.
****************

ЗЫ: Если чего забыл - не обессудь. Я уже тоже почти потерял нить рассуждений :-)
[C++] А можно ли?.. 25.07.03 11:53  
Автор: whiletrue <Роман> Статус: Elderman
<"чистая" ссылка>
Можно ли написать функцию, перехватывающую ВСЕ сообщения данного приложения (или окна), анализирующую все это добро и посылающую уже в обработанном виде непосредственно контролам? Навроде маршрутизатора.

Тогда в ней и прописать доплнительную логику поведения Слона относительно Мыши (из примера). Т.о. мы никаких логик ООП не нарушаем...
[C++] ШАГ 3-4. Сообщения пользователя 23.07.03 12:47  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Спасибо за ответы на мой пост. Вообще я себя чувствую немного не удобно, т.к. нагружаю Вас. Однако, прошу заметить, что Ваше мнение для меня очень важно и полезно.

Отвечу на замечания в конце сразу, когда мы все подведём черту топика.
Там будет, на мой взгляд, здорово из обсуждаемой модели выкинуть всё лишнее, чтобы получить НЕЧТО хорошее.
Кстати, не знаю как Вы, а я вижу, что с Вашей критикой у меня получается всё луше и луше.



*** ШАГ 3*

На предыдущем шаге 2 для различных групп объектов CWnd (диалогов, видов) предложено использовать различные экземпляры классов-потомков CMessanger c переопределёнными виртуальными методами FillControls,DisplayControls. Причиной было названо удобство организации передачи сообщений в пределах КАЖДОЙ ИЗ ГРУПП. Однако всё это опять только «косметика»…

Но есть причины разделения объектов на группы с различными месанджерами и функционального характера. В ряде случаев необходимо уметь посылать широковещательные сообщения всем членам группы. До сих пор сообщения, например, UM_ERASE обрабатывались только в теле FillControls,DisplayControls в том порядке, в котором мы определили:

switch(msg)
{
case UM_ERASE:
TV->ClearTree();
MAINPANEL->ClearControls();
PAGE_GENERAL->ClearControls();
PAGE2->ClearControls();

}

Если порядок обработки указанного сообщения для нас не важен (что вначале – TV, или MAINPANEL? см. врезку выше), то можно просто широковещательно рассылать сообщение UM_ERASE, например, при помощи функции SendMessage API, или одноимённого метода CWnd*. Сделаем это, определив для CMessanger функцию-член CMessanger:: SendMessage:

///////////////////////////////////////////////////
//
// file “utilwnd.h”
//
///////////////////////////////////////////////////

#include <afxtempl.h>

class CMessanger {
public:

class CWndInfo { // CWnd object info
public:
CWnd* m_pWnd;// pointer to CWnd registered object
UINT ID; // user symb.const. for CWnd registered object public:
CWndInfo() : m_pWnd(NULL), ID(0) {};
CWndInfo(CWnd* pWnd, UINT ID_WND = 0){
m_pWnd = pWnd; ID = ID_WND;
}
~CWndInfo() {};
};

public:

CMessanger() {};

// register CWnd object in CWndInfo array. Save pointer to CWnd object
// and user defined symbolic constant ID_WND for this CWnd object
void Register(CWnd* pWnd, UINT ID_WND)
{
m_aWnd.Add(new CWndInfo(pWnd, ID_WND));
}

// get CWnd* pointer from CWndInfo array by user defined constant ID_WND
CWnd* GetWnd(UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == m_aWnd.GetAt(i)->ID) return m_aWnd.GetAt(i)->m_pWnd;
return NULL;
}

//**** main message handling****
virtual bool FillControls (UINT message,UINT ID_WND = IDW_ALL) {return true;};
virtual bool DisplayControls(UINT message,UINT ID_WND = IDW_ALL) {return true;};

//**** additional message handling****
// broadcast (ID_WND == IDW_ALL) or direct message handling
void SendMessage(UINT message , UINT ID_WND = IDW_ALL,
WPARAM wParam = 0, LPARAM lParam = 0);

private:
CArray <CWndInfo*, CWndInfo*> m_aWnd;
};

///////////////////////////////////////////////////
//
// file “utilwnd.cpp”
//
///////////////////////////////////////////////////
#include "StdAfx.h"
#include "utilwnd.h"

// broadcast (ID_WND == IDW_ALL) or direct message handling
void CMessanger::SendMessage(UINT message , UINT ID_WND, WPARAM wParam, LPARAM lParam)
{
for (int i = 0; i < m_aWnd.GetSize(); i++) { CWnd* pWnd = m_aWnd.GetAt(i)->m_pWnd;
if ( ID_WND == IDW_ALL|ID_WND == m_aWnd.GetAt(i)->ID)
pWnd->SendMessage(message, wParam, lParam);
}
}
//-------------------------------------------------------------

Для того, чтобы СWnd объект реагировал на событие UM_XXX, которое послано всем объектам группы (ID_WND == IDW_ALL), или только ему, необходимо для его класса определить соответствующий обработчик:

//////////////////////////////////////////////////////////////////////////
//
// CPageGeneral dialog declaration
//
/////////////////////////////////////////////////////////////////////////

class CPageGeneral : public Cdialog {

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()

public:

afx_msg LRESULT OnSendMessage(WPARAM wParam, LPARAM lParam)
{
// Do something
return 0;
}

bool StartUp( void );
bool ClearControls( void );
bool Read ( void );


};


//////////////////////////////////////////////////////////////////////////
//
// CPageGeneral dialog implementation
//
/////////////////////////////////////////////////////////////////////////

BEGIN_MESSAGE_MAP(CPageGeneral, CDialog)

ON_MESSAGE(UM_NOTIFY, OnSendMessage)
END_MESSAGE_MAP()


Понятно, что при помощи анализа комбинации параметров wParam, lParam обработчика сообщения UM_NOTIFY можно легко разделить конкретные сообщения UM_STARTUP, UM_ERASE и т.д., и вызвать соответствующие обработчики-члены класса объекта, который получил сообщения. Например,

afx_msg LRESULT OnSendMessage(WPARAM wParam, LPARAM lParam)
{
if (wParam==UN_STARTUP)
return StartUp(); // см. выше определение класса
else if (wParam==UN_ERASE)
return ClearControls();// см. выше определение класса


return 0;
}

Как это можно использовать? Например, так:

void CPageGeneral::OnBnClickedEditButton()
{
// «одним махом» переводим все контролы в состояние ST_EDIT
// широковещательной рассылкой без использования
// функций методов FillControls, DisplayControls класса
// CMessanger
xxx.SendMessage(UM_NOTIFY, IDW_ALL, UM_EDIT, 0);
}

*PS к ШАГ 3**

1) Можно посылать широковещательные сообщения всем объектам сразу, без использования FillControls, DisplayControls, если последовательность обработки сообщений для всех объектов НЕ ВАЖНА. Не стоит быть наивным, полагая, что это всегда так и есть. Моя практика программирования ЭТОГО!!! показывает, что в СЛОЖНЫХ проектах не редки случаи, когда последовательность ВАЖНА. Не буду спорить о том, что зависимость от порядка обработки сообщений может быть вызвана только моим неумением программирования. По крайней мере, я об этом знаю и учитываю. Выбираю принцип такой – если, для ряда сообщений последовательность важна, то обработку выполняю, контролируя последовательность вызовов в FillControls, DisplayControls; если – нет, то обрабатываем широковещательно. Естественно, что одно не исключает другого, и можно комбинировать способы рассылки сообщений.

2) Если необходимо послать сообщение конкретному объекту, а не широковещательно всем, то в параметрах SendMessage заменяем макро IDW_ALL макроопределением IDW_XXX идентификатора требуемого объекта CWnd.

3) Может сложиться впечатление, что рассылка сообщений при помощи SendMessage приближает стиль программирования к парадигмам ООП, в отличие от использования FillControls, DisplayControls. Это было бы ложным утверждением. И SendMessage, и FillControls/DisplayControls в примерах выше вызывают ОДНИ И ТЕЖЕ МЕТОДЫ объектов CWnd (OnStartUp(), ClearControls(), см. определение диалога выше).

4) Я бы не противопоставлял возможность обработки сообщений при помощи FillControls/DisplayControls «альтернативному» методу доставки и анализа сообщений при помощи SendMessage. Я бы комбинировал одно и другое. У FillControls/DisplayControls есть одно преимущество – они позволяют прозрачно контролировать ОБЩУЮ общую логику событий приложения.

5) Забегая вперёд - о всё большей и большей схожести всего ЭТОГО!!! с медиатором и прочими музыкальными принадлежностями предлагаю высказаться другим. Мож просто медиатор взять, а остальное забыть?

****************************************************
*** ШАГ 4. Бродкасты*

На шаге 3 было показано, что в некоторых случаях, когда НЕ ВАЖНА последовательность обработки однотипных сообщений, весьма эффективны бодкасты при помощи механизма передачи сообщений API или соответствующих методов MFC, инкапсулирующих соответствующие вызовы API.

Однако для выполнения реальной работы с CWnd, например, с диалогом, который уловил UM_NOTIFY, в его обработчике UM_NOTIFY всё равно придётся анализировать WPARAM wParam, LPARAM lParam при помощи switch, или if. Согласитесь, немного всё продолжает оставаться корявым. И даже ещё в большей степени. То мы в FillControls/DisplayControls забубенили один свич, - то (что ещё хуже) до__бубенили ещё один свич в MFC-обработчик ON_MESSAGE(UM_NOTIFY, xxx). Да, всё плохо…

Тогда, для того, чтобы ввести в заблуждение доцента, преподающего ООП, и получить хоть тройку, скажем, что, дескать, наша модель ещё немножко и будет «почти ОО»; скажем, что, дескать, мы-то делаем конкретный проект, и у нас конкретные состояния приложения - enum app_state {ST_STARTUP, ST_CLOSE, ST_READ, и т.д.} myapp_state. Тогда решение на поверхности – для каждого из этих состояний приложения делаем свой бродкаст-обработчик EnableControls, ClearControls, ReadControls и др.:


///////////////////////////////////////////////////
//
// file “utilwnd.cpp”
//
///////////////////////////////////////////////////
class CMessanger {


public:
//**** main message handling****
virtual bool FillControls (UINT message,UINT ID_WND = IDW_ALL) {return true;};
virtual bool DisplayControls(UINT message,UINT ID_WND = IDW_ALL) {return true;};

//**** additional message handling****
// broadcast (ID_WND == IDW_ALL) or direct message handling
void SendMessage(UINT message , UINT ID_WND = IDW_ALL,
WPARAM wParam = 0, LPARAM lParam = 0);

//*special message handling*
//---------------------------------------
void EnableControls(bool status, UINT ID_WND = IDW_ALL)
{
SendMessage(UM_ENABLE_CONTROLS, ID_WND, status);
}
//---------------------------------------
void ClearControls(UINT ID_WND = IDW_ALL)
{
SendMessage(UM_CLEAR_CONTROLS, ID_WND);
}
//---------------------------------------
void ReadControls(UINT ID_WND = IDW_ALL)
{
SendMessage(UM_READ, ID_WND);
}
//---------------------------------------
};


Для того, что бы наш гипотетический диалог реагировал на эти бродкасты, редактируем месадж-мап его класса и определяем соответствующие afx_msg функции-члены:

//////////////////////////////////////////////////////////////////////////
//
// CPageGeneral dialog declaration
//
/////////////////////////////////////////////////////////////////////////

class CPageGeneral : public Cdialog {

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()

public:

afx_msg LRESULT OnSendMessage(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnEnableControlMessage(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnClearControlMessage(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnReadControlMessage(WPARAM wParam, LPARAM lParam);

bool StartUp( void );
bool ClearControls( void );
bool Read ( void );


};


//////////////////////////////////////////////////////////////////////////
//
// CPageGeneral dialog implementation
//
/////////////////////////////////////////////////////////////////////////

BEGIN_MESSAGE_MAP(CPageGeneral, CDialog)

ON_MESSAGE(UM_NOTIFY, OnSendMessage)
ON_MESSAGE(UM_ ENABLE_CONTROLS, OnEnableControlMessage)
ON_MESSAGE(UM_CLEAR_CONTROLS, OnClearControlMessage)
ON_MESSAGE(UM_READ, OnReadControlMessage)
END_MESSAGE_MAP()


Как это можно использовать? Например, так:

void CPageGeneral::OnBnClickedButton1()
{
// очистить все контролы в группе theMsgGroup1
theMsgGroup1.ClearControls();

// очистить контролы ОДНОГО диалога IDW_XXXXX
// в группе theMsgGroup2
theMsgGroup2.ClearControls(IDW_XXXXX);
}


*** ШАГ 5*

Завтра. Пост и так здоровый получился. Пишите.
[C++] Я так и не понял нескольких вещей 23.07.03 15:04  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> На предыдущем шаге 2 для различных групп объектов CWnd
> (диалогов, видов) предложено использовать различные
> экземпляры классов-потомков CMessanger c переопределёнными
> виртуальными методами FillControls,DisplayControls.
> Причиной было названо удобство организации передачи
> сообщений в пределах КАЖДОЙ ИЗ ГРУПП. Однако всё это опять
> только «косметика»…
Здесь важно понимать что ООП предполагает, что каждый объект сам знает как сделать то или иное действие, о котором его просят. Приведу простой пример. Мы создали класс Животное и задали для него метод Передвигаться. Допустим есть какой-то контейнер Сцена, на которой находятся абстрактные Животные. Так вот, Сцена может знать, что какое-то животное нужно переместить куда-то. НО!!! Различные Животные перемещаются по разному. Поэтому Сцена не должна непосредственно управлять руками/ногами/ложноножками для этого перемещения, а просто говорить Животному Передвинуться (и т.д. вниз по иерархии).

Ни в коем случае нельзя вводить глобальных обработчиков (даже для каких то групп). Нужно чтобы каждый КОНКРЕТНЫЙ объект САМ знал, как реагировать на тот или иной вызов. Обработчик должен быть методом класса (если речь об абстрактном классе, то виртуальным методом).

Именно поэтому, в твоем случае при переходе приложения в какое-то состояние, оно должно перевести только своих непосредственных детей. А те уж сами о себе позаботятся.

Теперь, собственно о вещах, которые я не понял.
> например, UM_ERASE обрабатывались только в теле
> FillControls,DisplayControls в том порядке, в котором мы
> определили:
>
> switch(msg)
> {
> case UM_ERASE:
Во первых, почему для тебя настолько важно использование виндового диспетчера сообщений. Зачем нужна цепочка SendMessage->GetMessage->DispatchMessage->(куча AfxXxxx методов различных MFC-шных классов)-> и в конце концов по MESSAGE_MAP-у к твоему обработчику? Не проще ли действительно сделать theApp.Erase(), а он уже вызовет что ему надо дальше.

> TV->ClearTree();
> MAINPANEL->ClearControls();
> PAGE_GENERAL->ClearControls();
> PAGE2->ClearControls();
Во-вторых. Большими буквами указаны макросы из "Мотивации"? ЗАЧЕМ???? Почему они тебе так дороги??? Если можно обойтись обычными мембер-переменными класса, а в случае страниц lParam-ом из TCITEM-а? Как это делать я написал в ответе на мотивацию.


> Если порядок обработки указанного сообщения для нас
> не важен (что вначале – TV, или MAINPANEL? см. врезку
Если сделать нормальную иерархию объектов, то рассылать нужно сверху-вниз слева-направо и тогда порядок всегда будет жестко задан - вот от него и отталкиваться впоследвие.

> выше), то можно просто широковещательно рассылать сообщение
> UM_ERASE, например, при помощи функции SendMessage API, или
Обоснуй необходимость SendMessage в конце то концов.

> 3) Может сложиться впечатление, что рассылка сообщений при
> помощи SendMessage приближает стиль программирования к
> парадигмам ООП, в отличие от использования FillControls,
> DisplayControls. Это было бы ложным утверждением. И
Глобальных обработчиков не должно быть ВООБЩЕ. Любой обработчик для конкретного объекта должет быть функцией-членом класса ЭТОГО объекта. И все.

Сравни это с маршрутизацией в TCP/IP. При получении пакета сервер знает, что для данного диапазона IP пакет нужно направить такому то серверу, тот сам решает, что делать дальше. Представь, теперь один глобальный маршрутизатор (в котором записаны ВСЕ возможные маршруты передачи пакетов). Если ты хочешь передать пакет от такого-то хоста такому-то, ты обращаешься к этому маршрутизатору и спрашиваешь по какому пути надо провести этот пакет. А теперь представь добавление нового хоста в такую сетку. Придется просмотреть ВСЕ маршруты на этом маршрутизаторе и еще добавить маршруты от нового хоста ко всем остальным. Или представь удаление хоста - опять пересмотр ВСЕХ записей и правка на что-то новое.

> 4) Я бы не противопоставлял возможность обработки
> сообщений при помощи FillControls/DisplayControls
> «альтернативному» методу доставки и анализа сообщений при
> помощи SendMessage. Я бы комбинировал одно и другое. У
> FillControls/DisplayControls есть одно преимущество – они
> позволяют прозрачно контролировать ОБЩУЮ общую логику
> событий приложения.
Этого быть не должно. Или обоснуй необходимость или не задавай вопрос, если сам все решил :-)

Если тебе близки такие вещи пиши лучше на Plain-C.

> BEGIN_MESSAGE_MAP(CPageGeneral, CDialog)
> …
> ON_MESSAGE(UM_NOTIFY, OnSendMessage)
> ON_MESSAGE(UM_ ENABLE_CONTROLS,
> OnEnableControlMessage)
> ON_MESSAGE(UM_CLEAR_CONTROLS,
> OnClearControlMessage)
> ON_MESSAGE(UM_READ, OnReadControlMessage)
> END_MESSAGE_MAP()
Ну возврщаясь к уже заданному вопросу. А зачем ехать из москвы в подмосковье через колыму?

Если ты хочешь, чтоб была вызвана эта функция почему бы просто не вызвать ее. Зачем тебе SendMessage?

А если не хочешь чтобы все было так просто, то я могу предложить еще несколько извращений со стеком и функциями стандартной библиотеки, причем сделать это на асме и чтоб все в конце концов приводилось к вызову функции.

> Как это можно использовать? Например, так:
>
> void CPageGeneral::OnBnClickedButton1()
> {
> // очистить все контролы в группе theMsgGroup1
> theMsgGroup1.ClearControls();
>
> // очистить контролы ОДНОГО диалога IDW_XXXXX
> // в группе theMsgGroup2
> theMsgGroup2.ClearControls(IDW_XXXXX);

Как уже было замечено: ГОРИЗОНТАЛИ - ЗЛО. Решать кого очищать а кого нет, должна не кнопка, а приложение. Кнопка может лишь по вертикали отрапортовать приложению, что хочет изменить состояние приложения.
[C++] Я так и не понял нескольких вещей 25.07.03 10:31  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
amirul!

В очередной раз спасибо, что «оторвал» своё время и смог ответить на мой пост. Извини, что не сразу ответил на твои вопросы. Отвечу не в том порядке, в котором они следовали.

*****************************
>> Или обоснуй необходимость или не задавай вопрос, если сам все решил :-)
A:
Я сам ничего НЕ решил. Может и сложилось у тебя такое впечатление, возможно, из-за моего дурацкого стиля изложения.
Обосную:))), см. ниже весь вопрос и ответ.

Сразу скажу, что все дальнейшие мои ответы с налётом изыска в части возможности использования широковещательных рассылок, для уменьшения кода (разумеется, текста С++). Т.е. речь о попытке одним вызовом сделать дополнительную работу по части операций, которая не была предусмотрена в иерархической модели, о которой ты пишешь. Почему вдруг я перескакиваю с одного на другое? Я хочу последующим (не этим) своим постом подвести черту в топике. Он и так уже всем, кроме меня самого надоел. Там появится фигурант CMsgItem. Поэтому, отвечая на твои замечания, сразу буду говорить «в том ключе» (про полезность или неполезность «горизонталей»).

Если же рассматривать только твою модель, то там, повторю свои предыдущие постинги, всё на пять баллов. Ну, что там ещё скажешь, кроме спасибо.

*****************************
>> Причиной было названо удобство организации передачи
>> сообщений в пределах КАЖДОЙ ИЗ ГРУПП. Однако всё это опять
>> только «косметика»…
> Здесь важно понимать, что ООП предполагает, что каждый объект сам знает…
A:
Объект знает, как себя перерисовать, передвинуть и т.д. Если использовать в дополнение к иерархической ООП-модели возможности бродкастинга («горизонталей»), то в ПРОСТЫХ реализациях всевозможных посредников бродкаст-рассылки, он (объект-наблюдатель) не узнает, когда ему будет РАЗРЕШЕНО Это сделать и КАК ИМЕННО, например, в зависимости от состояния субъекта наблюдателя. В том, что ты пишешь, такой проблемы не существует в принципе, поскольку в методе родителя программером «жестко» вбит порядок вызовов «одноимённых» методов детей, и возможно некая дополнительная обработка для разрешения коллизий, если таковая необходима.

Можно возразить, что тогда посредник, собственно выполняющий бродкаст, должен рассылать и обрабатывать месаги по более сложному протоколу. В самом простом случае будет инкапсулировать в сообщение наблюдателю инфу о своём состоянии, или сам наблюдатель (подписчик) будет каждый раз запрашивать такую инфу. Думаю, это очень сложные решения…

*****************************
>>Приведу простой пример. Мы создали класс Животное и задали для него метод >>Передвигаться.>>Допустим есть какой-то контейнер Сцена, на которой находятся абстрактные >>Животные. Так вот, >>Сцена может знать, что какое-то животное нужно переместить куда-то. >>НО!!! Различные >>Животные перемещаются по разному. Поэтому Сцена не должна >>непосредственно управлять руками/ногами/ложноножками для этого перемещения, а просто >>говорить Животному Передвинуться (и т.д. вниз по иерархии).

A:
1) Я и не говорил, что «Сцена» должна «непосредственно управлять руками/ногами/ложноножками». Даже в первом посте про BCB++, контролами формы (ручки и ножки) управляют приват мембер-функции xxxContlos(). Там xxxContlos() – методы формы, которые управляют только своими мембер-контролами. Я и подумать не мог, что это надо уточнять.

«Удобно для оконной ФОРМЫ BC++ Builder с большим числом …». Форма BCB++– это класс: public TForm, например:
//---------------------------------------------------------------------------
class TfrmMonitor : public TForm
{
__published: // IDE-managed Components
TPanel *PanelMain;
TTreeView *TV;
…..// ~ 100 компонент контролов
…..
private: // User declarations
….
…..
void DisplayControls( int _mode);
void FillControls( int _mode);
public: // User declarations
__fastcall TfrmMonitor(TComponent* Owner);
};

void DisplayControls( int _mode), void FillControls( int _mode) – здесь даже приват, хотя и не суть. Не понимаю, откуда сложилось впечатление, что я хочу из методов одного класса управлять непаблик мемберами (контролами) другого класса.

«….Такую обработку несложно построить в BC++, т.к. все контролы являются ЧЛЕНАМИ КЛАССА формы приложения. Т.е из указанных выше функций (МЕТОДОВ ФОРМЫ) видны все контролы…. »

2) Думаю, что если дополнительно «Сцене» не контролировать, как будут выполняться методы «Передвинуться» животных, то этого в общем случае недостаточно. Животные знают, как им «по разному» передвигаться, в том смысле, что один из них будет объектно-ориентированно ползти, а другой – копытами стучать. Но если «Сцена» тупая, или мы СОЗНАТЕЛЬНО не хотим её усложнять из соображений снижения накладных расходов на программирование, то Животные при перемещении превратятся в стадо. При перемещении сами себя поубивают.

Хорошо, скажешь ты, пусть это умные животные (ведь цирк же, а не в тундра), т.е. каждый знает – КУДА ему идти. Например, слон знает, что по команде «Пошёл» - надо идти прямёхонько в оркестровую яму и только туда, потому, что только тогда зрителям будет смешно (цирк же), а мышь знает, что надо идти подальше от слона, к выходу (иначе тот разбушуется и покрошит всё). Но дело в том, что мышь не знает, что бежать ей следует ТОЛЬКО после слона. Если она побежит раньше, то слон, узрев это, в силу своей боязни на генетическом уровне мышей начнёт нервничать. Тогда, держись….

ПРОСТЫЕ модели, выполняющие доставку мессаг бродкастом, не умеют создавать и контролировать ОЧЕРЁДНОСТЬ обработки мессаг и РЕЗУЛЬТАТЫ ЭТОЙ ОБРАБОТКИ. Можно переложить это на виртуальные методы наблюдателя, усложнить протокол обработки мессаг и т.д. Но тогда это ну оооооооочень усложнит модель. Хочется получить простое решение.


*****************************
>>Ни в коем случае нельзя вводить глобальных обработчиков (даже для каких то групп).

A:
1) Согласен, если вместо «нельзя» - «как правило, не рекомендуется, крайне не желательно». Почему? Потому, что есть люди, которые юзают гольный API (это не я, но про таких знаю), и некоторые из них именно так и делают.

2) Там предлагается не глобальный обработчик для групп, а переопределённый виртуальный метод CMessanger. Он (его мембер-метод) и есть один «глобальный обработчик» для одной группы. Глобальным является объект, класс которого наследует от CMessanger. Каждой группе – по объекту CMessanger. В ЭТОМ смысле каждой группе – по глобальному обработчику. Группа – это избыточно связанные объекты CWnd, предназначенные для выполнения подзадачи. «Избыточно» –в том смысле, что и так существует связь объектов в MFC, вернее их иерархическая модель в голове программиста при использовании чисто ООП подхода (твоего). Эта избыточность проявляется в установлении «горизонталей», для обеспечения возможности бродкаста.
Если считать CMessanger медиатором, то он без синглентона, который знает всех своих участников группы.

3) Да, там есть слова про то, «…. что для каждой из групп определить свою пару ЧИСТО глобальных функций {FillControls_1, DisplayControls_1}, {FillControls_2, DisplayControls_2}, …». Но дальше по тексту идёт обоснование - почему так делать не надо. Это обоснование не с позиции ложной «обязательности» соответствия парадигмам ООП, а с позиции здравого смысла по мере развития идеи, поиска простых механизмов взаимодействия объектов.


*****************************
>>Нужно чтобы каждый КОНКРЕТНЫЙ объект САМ знал, как реагировать на тот или >>иной вызов. Обработчик должен быть методом класса (если речь об абстрактном >>классе, то виртуальным методом).

A:
Ещё раз хочу подчеркнуть то, что только что уже написал выше. «…КОНКРЕТНЫЙ объект САМ знал…» - работает только в четко разработанной иерархической модели. Если попытаться ввести «горизонтали», то это не будет работать в общем случае, например, при бродкасте. НЕ МОЖЕТ «…КОНКРЕТНЫЙ объект САМ знал…» в СЛОЖНЫХ моделях, в которых мы хоть на шаг отступаем от ООП. И без прозрачного мененджера тогда не обойтись. Под простой моделью я понимаю, например, некий хрестоматийный графический редактор. Там – да. Каждая фигура, для которой переопределён виртуальный метод, пусть «OnDraw», перересует себя правильно по своему усмотрению. Отличительная черта такой «простоты» в том, что объекту не надо ничего знать о том, перерисовали себя остальные объекты (объекты графической базы), или нет, и как. Поэтому-то простота модели порождает простоту реализации – возможность отрисовки всех объектов п р о с т ы м б р о д к а с т о м. Для моего случая, там где можно, ЭТО используется, чтобы не перегружать логику приложения.

Например, очевидно, что сообщения UM_ERASE могут вполне рассылаться широковещательно, и обрабатываться синхронно. В альтернативу ООП очистки контролов начиная с рут-листов по схеме:

// ООП модель
void CMyApp:: ClearControls()
{
m_pMainDialog.ClearControls();
m_pTreeView.ClearControls();

m_pParent1.ClearControls();


m_pParentXXX.ClearControls();
и т.д.
}

А затем КАЖДЫЙ из листьев (родителей) перечисляет

void CParent1:: ClearControls()
{
m_pChield1. ClearControls();
m_pChield2. ClearControls();


m_pChieldXXX. ClearControls();
и т.д.
}

// Броадкаст рассылка
void CMyApp:: ClearControls()
{
Messanger.ClearControls();
}

Возможность броадкаста для обработки UM_ERASE связана с тем, что всё равно (вернее почти всё равно, т.к. нежелательные эффекты могут быть подправлены с небольшими накладными расходами), в какой последовательности будут удалены данные из элементов их отображающих. Эффект в коде сам видишь.

С другой стороны сообщения типа UM_ERASE_DATABASEDATA или даже UM_READ часто не могут быть обработаны путём простого широковещания. В первом случае для транзакции может быть выполнен откат, во втором случае один из CWnd-объектов может читать данные, которые зависят от предварительно полученных по UM_READ другим CWnd-объектом. Например, закладка CPageXXX должна перерисоваться, или не перерисовываться только после получения или неполучения данных и отрисовки CTreeView.

Эти «негативные эффекты» броадкаста известны продвинутой публике и в теории и на практике. Посмотри паттерн Observer. Например, это

«…
неожиданные обновления. Поскольку наблюдатели не располагают информацией друг о друге, им неизвестно и о том, во что обходится изменение субъекта. Безобидная, на первый взгляд, операция над субъектом может вызвать целый ряд обновлений наблюдателей и зависящих от них объектов. Более того, нечетко определенные или плохо поддерживаемые критерии зависимости могут стать причиной непредвиденных обновлений, отследить которые очень сложно.
Эта проблема усугубляется еще и тем, что простой протокол обновления не содержит никаких сведений о том, что именно изменилось в субъекте. Без дополнительного протокола, помогающего выяснить характер изменений, наблюдатели будут вынуждены проделать сложную работу для косвенного получения такой информации.
….»

Бороться с негативом ясно, как. Есть следующие возможности:
А) для бродкаст-манагера «моделировать» асинхронную обработку подписчиками. Сложность решения ворзастает неимоверно.

Б) делать умных подписчисиков, запрашивающих стэйт и другое, пока даже не знаю, что…

В) дробить сообщения на более мелкие, например, UM_READ на UM_READ и UM_XXX_READ, что также не универсально и может перегружать логику приложения событиями

Г) делать что-нибудь нууууууууууу, совсем уж простое. Что я ПОКА и делаю, упорно сохраняя FillControls и DisplayControls в качестве виртуальных методов будущего «медиатора», получая пинки и подзатыльники. Как можно использовать FillControls и DisplayControls для группы подписчиков тоже понятно – если можно обработать мессагу бродкастом, то выполнить бродкаст из тела этих методов CMyMessanger:public CMessanger. Если необходим анализ очерёдности, или ещё чего (о чём можно знать только реализуя это в дочке CMessanger конкретного проекта) – то сделаем это. Короче говоря, FillControls и DisplayControls – это дополнительные посредники, вернее дополнительное средство анализа в посреднике (медиаторе). Если так не делать, то прямая дорога к п. А)Б)В), имхо.



*****************************
>>Теперь, собственно о вещах, которые я не понял.

>>Во первых, почему для тебя настолько важно использование виндового диспетчера >>сообщений. Зачем нужна цепочка SendMessage->GetMessage->DispatchMessage->(куча >>AfxXxxx методов различных MFC-шных классов)-> и в конце концов по >>MESSAGE_MAP-у к твоему обработчику?
A:

«цепочка SendMessage->GetMessage->» для меня не важна, как самостоятельная ценность. Она может оказаться полезной при организации обработки сообщений об изменении состояния системы, если используется широковещательная рассылка. Я оставил ЭТО и ТОЛЬКО ПОКА, до того как мы подведём черту топика, как некую альтернативу ООП способу оповещения посредников, про который ты и сам всё знаешь, и про который я хотел бы дать инфу отдельным постом (если модераторы не шрафанут меня за такие длинные постинги). Ты спросишь- а на кой? На других форумах часто кивали в эту сторону, мол, чё те надо – возьми SendMessage от API или от MFC и наслаждайся жизнью. Т.е. есть программеры, которые считают, что этого вообще достаточно. Поэтому давай ПОКА оставим SendMessage жизнь, как альтернативу для тех, кто это любит. Когда будем подводить черту в топике, возмём топор и всё лишнее поотрубаем, как Церрретттеллли ;))).

*****************************
>> Не проще ли действительно сделать theApp.Erase(), а он уже вызовет что ему надо дальше.

A:
Да конечно проще. Но, можно как ты, см. выше, а можно с «нарушением правил уличного движения ООП», броадкастом, см. выше врезку а-ля кода. Последнее содержит меньше программерского кода. А на счёт правильности и последствий пока не знаю.


*****************************
> TV->ClearTree();
> MAINPANEL->ClearControls();
> PAGE_GENERAL->ClearControls();
> PAGE2->ClearControls();
>>>Во-вторых. Большими буквами указаны макросы из "Мотивации"? ЗАЧЕМ????

Почему >>>они тебе так дороги???

A:
Да вовсе не дороги. Я время экономлю, ссылаясь на предыдущие фрагменты кода ;))).

*****************************
>>> Если можно обойтись обычными мембер-переменными класса, а в случае страниц lParam-ом из TCITEM-а? Как это делать я написал в ответе на мотивацию.

A:
Да, посмотрел. Если честно, скопировал в избранное. Чистенький код. Хотя, как ты понимаешь, у меня есть класс таба. Иначе как вообще МФК-ой пользоваться. А чужой код, тем более стильный никогда не помешает. Так что, спасибо.


*****************************
>>> Если порядок обработки указанного сообщения для нас
>>> не важен (что вначале – TV, или MAINPANEL? см. врезку
>Если сделать нормальную иерархию объектов, то рассылать нужно сверху-вниз слева->направо и тогда порядок всегда будет жестко задан - вот от него и отталкиваться >впоследвие.

A:
Я уже писал, что буду думать про иерархию. Важный вопрос. И не готов я что-либо возразить. Если б знал, то не обращался бы в форум. Живучесть иерархичных решений, которые моделируются ООП понятна. Меня мучает другой вопрос. Когда придёт время переделывать проект, поскольку какой-то баран-прикладник не до конца озвучил, что ему надо от задачи в постановочной части, - насколько «…тогда порядок всегда будет жестко задан …» будет гирями.

*****************************
>> выше), то можно просто широковещательно рассылать сообщение
>> UM_ERASE, например, при помощи функции SendMessage API, или

>Обоснуй необходимость SendMessage в конце то концов.
A:

Выше я уже обосновывал. В принципе – можно в топку. Но мне надо, пропостить завтра или сегодня «почти завершение топика» (там появится на свет медиатор-уродец), и тогда будет альтернатива. Вернее SendMessage будет альтернативой. Я хотел бы в конце топика посоветоваться с народом – оставить анахронизьм, или убить гада.

*****************************
>> 3) Может сложиться впечатление, что рассылка сообщений при
>> помощи SendMessage приближает стиль программирования к
>> парадигмам ООП, в отличие от использования FillControls,
>> DisplayControls. Это было бы ложным утверждением. И

>Глобальных обработчиков не должно быть ВООБЩЕ. Любой обработчик для >конкретного объекта должет быть функцией-членом класса ЭТОГО объекта. И все.

A:
Я уже писал об этом чуть выше. Нет у меня глобальных обработчиков. Есть глобальные дочки CMessager с паблик обработчиками. Можно вызывать их (для разрешения коллизий) или напрямую вызывать мембер-члены «команды» медиатора.

*****************************
>> 4) Я бы не противопоставлял возможность обработки
>> сообщений при помощи FillControls/DisplayControls
>> «альтернативному» методу доставки и анализа сообщений при
>> помощи SendMessage. Я бы комбинировал одно и другое. У
>> FillControls/DisplayControls есть одно преимущество – они
>> позволяют прозрачно контролировать ОБЩУЮ общую логику
>> событий приложения.

>Этого быть не должно. Или обоснуй необходимость или не задавай вопрос, если сам все решил :-)

A:
Я ответил выше про FillControls/DisplayControls – они нужны только для разрешения коллизий широковещательной рассылки, если таковые, по мнению программиста, могут появиться при обработке «потенциально коллизионных» сообщений.

*****************************
>>Если тебе близки такие вещи пиши лучше на Plain-C.
A:
Не буду. Мне не близок не один из языков программирования, которые я знаю. Мне близка моя девчёнка;))))

*****************************
>> BEGIN_MESSAGE_MAP(CPageGeneral, CDialog)
> >…
> >ON_MESSAGE(UM_NOTIFY, OnSendMessage)
> >ON_MESSAGE(UM_ ENABLE_CONTROLS,
> >OnEnableControlMessage)
> >ON_MESSAGE(UM_CLEAR_CONTROLS,
> >OnClearControlMessage)
> >ON_MESSAGE(UM_READ, OnReadControlMessage)
> >END_MESSAGE_MAP()

>Ну возврщаясь к уже заданному вопросу. А зачем ехать из москвы в подмосковье через >колыму?
>Если ты хочешь, чтоб была вызвана эта функция почему бы просто не вызвать ее. Зачем >тебе SendMessage?

A:
Я оставил SendMessage ПОКА ничего не сказал про методы около-медиатора, вернее специальный класс CMsgItem, который позволит обращаться с CMessanger, как с медиатором.
Убъём, обязательно убъём!!!
Другое, дело, что я хочу посоветоваться в конце, мож и про бродкаст забыть, а пользоваться только ООП?
[c++] сообщения пользователя мотивация. да простит меня модератор. 22.07.03 09:48  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Спасибо, что поддержали дискуссию и не сразу меня размазали ;). Не скрою, у Вас есть очень полезные для меня идеи. Однако Ваши постинги показывают, что, скорее всего, Я ПЛОХО ОБЪЯСНИЛ, ЧТО ХОЧУ. Поэтому начну ещё раз с мотивации, а уж затем – и масло в огонь.

*** МОТИВАЦИЯ*
Пусть существует приложение MFC для работы, например, с базами данных. Стиль UI – explorer.:
-слева CTVView: public CTreeView
-справа CMainPanel: public CFormView.
На диалоге CMainPanel имеется CTabCtrl, который командует диалогами CPage1 (CPage1:public CDialog), CPage2,…, CPage20. Таким образом, общее число основных «независимых» классов, которые обрабатывают данные в видах справа и слева в CMainFrame – 22-25. Число контролов (кнопки, эдитбоксы, и т.д) для каждого класса от 5 до 10. Таким образом, общее число контролов составляет ~150 – 200.
Замечу, что объекты указанных классов (а значит и соответствующие контролы) «НЕЗАВИСИМЫ» в том смысле, что они почти ничего не знают друг о друге (идеология MFC). Если происходит некое СОБЫТИЕ, которое связано, например, с нажатием на кнопку «Редактировать» (CPage1::CButton m_StartEdit) на закладке CPage1::Page1, то остальные объекты приложения об этом ничего не знают. Обработчики кнопки CPage1::CButton m_StartEdit на закладке CPage1 не могут непосредственно повлиять, например, на состояние чекбоксов или эдитов на закладке CPage2, т.к. она (кнопка) не знает даже указателя на диалог CPage2. При переводе приложения в режим редактирования данных мы не сможем простым путём изменить состояние контролов других, «НЕЗАВИСИМЫХ» объектов, например, сделать их ReadOnly(false). Если бы указатель был известен (например, мы сделали extern CPage2* Page2), то при стандартном стиле программирования это тоже не очень помогает по трём обстоятельствам:
1)Код обработчика клика CPage1::m_StartEdit, типа
(CPage2*)GetDlgItem(IDC_xxx)->SetWindowsText(“…”), будет работать. Однако необходимо менять состояние не одного контрола, а, возможно, сотен, и не только на закладке CPage2, но и на других диалогах и тулбарах. Удовольствие написания соответствующих обработчиков, прямо скажем, не очень…
2)Если мы, упрямые, всё-таки изрядно постарались и сделали шаг 1), то, возможно, наделали ошибок по синхронизации отображения и заполнения данными сотен контролов. Ошибки могут проявиться потом, только не известно когда… Мои наблюдения и практика показывают, что В БОЛЬШИХ ПРОЕКТАХ, КОГДА НЕОБХОДИМО СИНХРОНИЗИРОВАТЬ ПОВЕДЕНИЕ БОЛЬШОГО ЧИСЛА ЭЛЕМЕНТОВ УПРАВЛЕНИЯ, КОНТРОЛЫ НЕ ДОЛЖНЫ ИЗМЕНЯТЬ СОСТОЯНИЕ ДРУГИХ КОНТРОЛОВ !!!НАПРЯМУЮ!!!. Т.е. попытка непосредственно в ТЕЛЕ обработчика нажатия кнопки порулить состоянием других контролов НАПРЯМУЮ почти всегда приведёт к краху в будущем по мере разрастания крупного проекта. Это утверждение, разумеется, не касается случаев, когда проекте имеется весьма обособленный код, например, для обработки 3-4 контролов некого модального диалога. Там контролы могут делать друг с другом напрямую всё, что угодно.
3) А масштабирование? Будет Вам счастяяяяя и удовольствия… См. пункты 1 и 2 выше.

Таким образом, необходимо искать способ управления элементами GUI, а вернее сказать, СТИЛЬ программирования, который прозрачно обеспечивает синхронизацию всего, что происходит на экране, особенно после варварских попыток пользователя понажимать на всё сразу;). Перед тем, как поставить точку в мотивации (плюс к тому, что было в предыдущем посте) скажу, что всё, что происходит на экране (в том числе и с контролами), на мой взгляд должно реагировать НЕ на события связанные с нажатиями пользователя на клавиатуру или мышь, а на СОБЫТИЯ приложения, на СОСТОЯНИЯ задачи (чтение данных, состояние редактирования, состояние создания новой порции данных, состояния сохранения данных и т.д.). И не застенчиво зависеть, время от времени, а постоянно (разумеется, речь идёт только о крупных проектах, и, конечно, не обо всех).

Вот дополнения к мотивации:
а) объекты и контролы приложения, должны реагировать, как правило, на события и состояния БИЗНЕС-ЛОГИКИ приложения. Думаю, достаточно определить не слишком большое число состояний enum app_state {ST_STARTUP, ST_CLOSE, ST_READ, и т.д.} myapp_state.
б) сотояние контролов должно зависеть на уровне логики приложения от app_state, а не от действий пользователя, например, нажатия клавиши мыши. Контрол может менять состояние app_state, и таким образом ОПОСРЕДОВАНО своё состояние. Т.е схема такова – (пользователь)->(нажатие на кнопку)->(изменения app_state)->(изменение состояния контрола, enable? readonly? и.д.)
в) передачу информации между объектами приложения о состоянии app_state разумно, на мой взгляд, выполнять в виде передачи сообщений app_message {UM_STARTUP, UM_CLOSE, UM_READ, и т.д.} myapp_message. Каждому значению app_state можно поставить в соответствие значение app_message.

а),б)в) выше – минимум. По мере реализации всего ЭТОГО!!!, возможно, следует ввести новые требования о возможности получения уведомлений о состояниях, асинхронной обработки сообщений и т.д. Т.е., возможно, дальше из ЭТОГО!!! получится некий паттерн типа медиатора вместе с обжект-объзервером (см., например, ссылку которую мне любезно подсказали форумчане http://ooad.asf.ru/patterns/patterninfo.asp?id=23 ). Но решение должно быть простым и прозрачным, как для тех, кто использует MFC, так и WinAPI при построении GUI. Поэтому последнее, обязательное требование г):

г) всё должно быть просто и понятно без лишней теоретизации и онаучивания на пустом месте. Это не должна быть ещё одна библиотека, довесок к MFC, или некий TSupperPuperActionList BC++ Builder. Это скорее должен быть стиль программирования с использованием очень простых по структуре объектов, которые легко можно воспроизвести в любой C++ среде.

Напоследок в «мотивации» покажу, как хотелось бы, чтобы выглядел С++ код, когда всё получится:

// стартуем приложение MFC VC++
BOOL CGkmApp::InitInstance()
{
InitCommonControls();
CWinApp::InitInstance();

// в следующих двух вызовах читаем данные, заполняем контролы
// и приводим их в приличный вид визибл-невизибл, энайб-дисайбл и т.д.
xxx.FillControls(UM_STARTUP);
xxx.DisplayControls(UM_STARTUP);

// запрещаем пользователю делать всякие глупости и портить данные,
// пока он сознательно не переведёт приложение в состояние
// ST_EDIT, нажав, например, на кнопку «Редактировать»
xxx.FillControls(UM_READ);
xxx.DisplayControls(UM_READ);

m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
//-------------------------------------------------------------
// Здесь пользователь нажимает кнопку «Редактировать» для перевода
// формы приложения в режим редактирования ST_EDIT
void CPageGeneral::OnBnClickedEditButton()
{
// здесь, до вызовов двух функций ниже
// все 200 контролов ещё недоступны для редактирования
xxx.FillControls(UM_EDIT);
xxx.DisplayControls(UM_EDIT);
// здесь все 200 контролов уже доступны для редактирования
// и пользователь может вводить данные
}
//-------------------------------------------------------------
// Здесь пользователь нажимает кнопку «Сохранить» для перевода
// приложения в режим сохранения результатов редактирования ST_SAVE,
// а затем возврата в состояние просмотра данных ST_READ
void CPageGeneral::OnBnClickedSaveButton()
{
// здесь все 200 контролов доступны для редактирования
// т.к. состояние ещё ВОЗМОЖНО ST_EDIT
xxx.FillControls(UM_SAVE);
xxx.DisplayControls(UM_SAVE);

xxx.FillControls(UM_READ);
xxx.DisplayControls(UM_READ);
// здесь все 200 контролов изменили своё состояние,
// и уже недоступны для редактирования
// Мы в состоянии ST_READ просмотра базы данных
}
//-------------------------------------------------------------

*** МАСЛО В ОГОНЬ*

Думаю, масло следует подливать понемногу, шаг за шагом. В предыдущем посте предложено тупое решение, когда указатели на все требуемые объекты, например, диалоги Page1,…, Page20 объявлены extern. На мой взгляд, это не такой уж плохой путь для простых по логике GUI. Так, что в простых случаях я так и буду делать, и других наворотов (см. далее шаг 1 и т.д.) мне и не надо.

Шаг 1.
Для более сложных приложений, если мы хотим «большой беспорядок привести к нескольким маленьким беспорядкам», заметим, что двадцать глобальных указателей – прямо скажем, не очень изящно. Ну, добавим все указатели в некий массив. Необходимость в таком массиве, вернее специальном классе CMessanger (язык не поворачивается сказать элементе паттерна) дополнительно обоснована далее, см. шаг 2. В функциях FillControls, DisplayControls нам нужно будет уметь извлекать из массива указатели на диалоги по некоторым символическим идентификаторам, которые мы знаем (сами задаём при добавлении указателя на объект в массив). Поэтому в элементах массива будем хранить указатели вместе с идентификаторами.

///////////////////////////////////////////////////
//file “utilwnd.h”
#include <afxtempl.h>

class CMessanger {
public:
class CWndInfo { // CWnd object info
public:
CWnd* m_pWnd;// pointer to CWnd registered object
UINT ID; // user symb.const. for CWnd registered object public:
CWndInfo() : m_pWnd(NULL), ID(0) {};
CWndInfo(CWnd* pWnd, UINT ID_WND = 0){
m_pWnd = pWnd; ID = ID_WND;
}
~CWndInfo() {};
};
public:
CMessanger() {};

// register CWnd object in CWndInfo array. Save pointer to CWnd object
// and user defined symbolic constant ID_WND for this CWnd object
void Register(CWnd* pWnd, UINT ID_WND)
{
m_aWnd.Add(new CWndInfo(pWnd, ID_WND));
}
// get CWnd* pointer from CWndInfo array by user defined constant ID_WND
CWnd* GetWnd(UINT ID_WND)
{
for (int i = 0; i < m_aWnd.GetSize(); i++)
if ( ID_WND == m_aWnd.GetAt(i)->ID) return m_aWnd.GetAt(i)->m_pWnd;
return NULL;
}
private:
CArray <CWndInfo*, CWndInfo*> m_aWnd;
};
extern CMessanger theMsg;

///////////////////////////////////////////////////
//file “utilwnd.cpp”
#include "StdAfx.h"
#include "utilwnd.h"

CMessanger theMsg;


Добавлять элементы массива theMsg будем при создании или инициализации объектов CWnd при помощи метода Register, например, вот так:

void CTVView::OnInitialUpdate()
{
CTreeView::OnInitialUpdate();

theMsg.Register(this,IDW_TVVIEW);
}

Определение идентификатора IDW_TVVIEW поместим в некий “usermessages.h”, как впрочем и макроопределения для символических констант других объектов (диалогов, видов и т.д)

///////////////////////////////////////////////////
//file “usermessages.h”

//#define IDW_ALL 0 in utilwnd.h

#define TV_VIEW (IDW_ALL + 1000) //for left CTVView: public CTreeView
#define MAIN_VIEW (IDW_ALL + 2000) //for right CMainView: public CView



#define IDW_TVVIEW TV_VIEW // Left TreeView
#define IDW_MAIN_PANEL (MAIN_VIEW + 1)// CMainPanel dialog in CMainView
// Page1 dialog in CMainPanel::m_TabCtrl
#define IDW_PAGE_GENERAL(MAIN_VIEW + 2)
// Page2 dialog in CMainPanel::m_TabCtrl
#define IDW_PAGE2 (MAIN_VIEW + 3)
// Page3 dialog in CMainPanel::m_TabCtrl
#define IDW_PAGE3 (MAIN_VIEW + 4)

Для обработки сообщений потребуется определить эти сообщения и определить глобальные функции обработчиков FillControls, DisplayControls. Сделаем это, дополнив “usermessages.h” (*.cpp)


///////////////////////////////////////////////////
//file “usermessages.h”


#include "utilwnd.h"

#define UM_STARTUP (WM_USER + 100)
#define UM_ERASE (WM_USER + 110)
#define UM_READ (WM_USER + 120)
#define UM_EDIT (WM_USER + 130)
bool FillControls (UINT msg);
bool DisplayControls (UINT msg);


///////////////////////////////////////////////////
//file “ usermessages.cpp”
#include "StdAfx.h"
#include "Gkm.h" // our main header for the application
#include "TVView.h" // treeview
#include "Pages.h" // dialogs

#include "usermessages.h"

CMessanger theMsg;

#define TV ((CTVView*)(theMsg.GetWnd(IDW_TVVIEW)))
#define MAIN_PANEL ((CMainPanel*)(theMsg.GetWnd(IDW_MAIN_PANEL)))
#define PAGE_GENERAL ((CPageGeneral*)(theMsg.GetWnd(IDW_PAGE_GENERAL)))
#define PAGE2 ((CPage2*)(theMsg.GetWnd(IDW_PAGE2)))
#define PAGE3 ((CPage3*)(theMsg.GetWnd(IDW_PAGE3)))

//------------------------------------------------------------
void FillControls(UINT msg)
{
switch(msg)
{
case UM_STARTUP:
FillControls(UM_ERASE);
FillControls(UM_READ);

break;
case UM_ERASE:
TV->ClearTree();
MAINPANEL->ClearControls();
PAGE_GENERAL->ClearControls();
PAGE2->ClearControls();


break;
case UM_READ:
TV->ReadData();
MAINPANEL->ReadData();
PAGE_GENERAL-> ReadData();
PAGE2->ReadData();
PAGE3->ReadData();

break;



}
return true;
}
//------------------------------------------------------------
void DisplayControls(UINT msg)
{
// Совершенно аналогично FillControls
}

Ну, вот и всё для шага 1. Теперь в первом приближении можно делать то, что я хотел в начале.

void CPageGeneral::OnBnClickedEditButton()
{
FillControls(UM_EDIT);
DisplayControls(UM_EDIT);
}

Шаг 2.
На шаге 1 причина добавления указателей в массив была чисто косметической. Дескать, так стильнее по сравнению отдельными глобальными указателями на диалоги и другие CWnd объекты. Действительно, пока что шаг 1 не добавляет ничего нового, кроме лишних хлопот.
Однако часто объекты проекта по ЛОГИКЕ ПРИЛОЖЕНИЯ представляют независимые ГРУППЫ, сообщения которых должны обрабатывать различные обработчики FillControls, DisplayControls. Например, диалоги и виды, описанные выше – это одна группа объектов. Если по нажатию некой кнопки создаётся модальный диалог, насыщенный другими боксами, барами и диалогами, то совокупность последних, очевидно представляет другую независимую группу. Под независимостью здесь понимается то обстоятельство, что объекты различных групп не пересекаются в части обработки сообщений. Таким образом, различные группы должны иметь различные обработчики FillControls, DisplayControls. Возвращаясь к BC++ Builder, – там это реализуется «автоматически», т.к. указанные функции являются членами классов различных форм (там обработчики я пишу для каждой навороченной по числу контролов формы). В MFC - копировать этот подход от BC бессмысленно.
Если мы не эстеты, то, конечно, можно для каждой из групп определить свою пару глобальных функций {FillControls_1, DisplayControls_1}, {FillControls_2, DisplayControls_2}, …, {FillControls_n, DisplayControls_n}. Работать будет… Однако гораздо удобнее в класс CMessanger добавить соответствующие виртуальные методы. Тогда CWnd-объекты каждой из групп будем регистрировать в своём глобальном месанджере. Более серьёзная причина использования для различных групп объектов различных CMessanger_XXX : public CMessanger приведена на шаге 3. Ну, а здесь, думаю, будут понятны изменения в определении CMessanger и в коде регистрации объектов разных групп и так.

class CMessanger {
public:

//**** main message handling****
virtual bool FillControls (UINT message, UINT ID_WND = IDW_ALL) {return true;};
virtual bool DisplayControls(UINT message,UINT ID_WND = IDW_ALL) {return true;};

};

Тогда для CWnd-объектов первой группы наследуем свой манагер сообщений:
class CMsg1: public CMessanger {
public:
bool FillControls(UINT message, UINT ID_WND = 0);
bool DisplayControls(UINT message, UINT ID_WND = 0);
};
extern CMsg1 Msg1;
//---------------------------------------------

И объекты первой группы регистрируем в Msg1:
Register(&Msg1, this, IDW_PAGE_GENERAL);
//---------------------------------------------

Для объектов второй группы наследуем свой манагер сообщений:
class CMsg2: public CMessanger {
public:
bool FillControls(UINT message, UINT ID_WND = 0);
bool DisplayControls(UINT message, UINT ID_WND = 0);
};
extern CMsg2 Msg2;
//---------------------------------------------
….
И объекты второй группы регистрируем в Msg2:
Register(&Msg2, this, IDW_XXXXXXX);
//---------------------------------------------

Шаг 3.
Завтра....


На сегодня для меня хватит. Пальцы уже болят. Остальное - (шаг 3, …, шаг N) завтра. Вначале хочется услышать Ваше мнение.

PS.
Ещё раз благодарю всех тех, кто откликнулся. Обещаю, что дальше не буду писать столь пространно и столь длинные посты. Это было необходимо в настоящем постинге, т.к. далее я буду только ссылаться на предыдущие свои постинги. Если, конечно, дискуссия состоится.
[C++] Ого :-) 22.07.03 11:24  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> Замечу, что объекты указанных классов (а значит и
> соответствующие контролы) «НЕЗАВИСИМЫ» в том смысле, что
> они почти ничего не знают друг о друге (идеология 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. Например, диалоги и виды, описанные выше –
Вот поэтому, нужно чтоб объект делал только то что знает как делать точно. То бишь приложение переводит в новое состояние своих детей, диалог - своих, а страницы просто переводят контролы в нужное состояние. А использование виртуальных функций при этом помогает отделить интерфейс от реализации.

> Ещё раз благодарю всех тех, кто откликнулся. Обещаю, что
> дальше не буду писать столь пространно и столь длинные
> посты. Это было необходимо в настоящем постинге, т.к. далее
> я буду только ссылаться на предыдущие свои постинги. Если,
> конечно, дискуссия состоится.
Да ничего. Модераторов тут много. Если что - амнистируем :-)
[C++] Ого :-) 25.07.03 07:52  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Спасибо, amirul, за постинг.

******************************************
>> Замечу, что объекты указанных классов (а значит и
>> соответствующие контролы) «НЕЗАВИСИМЫ» в том смысле, что
> >они почти ничего не знают друг о друге (идеология MFC).
>Нет, у MFC другая идеология. В плане абстракции данных он даже не совсем ООП, так как все >данные держит открытыми - непозволительная вольность. Я не очень люблю MFC, но совсем за >другие вещи.

A:
Я был не точен.

****************************************************
>переменные для обращения к объектам (см "Дополнение к дополнению"). Есть один глобальный >theApp. В нем можно хранить указатели на диалоги (но насколько я понял диалог один, так что >этого не надо). В эти диалоги из ClassWizard-а подобавлять переменных, через которые потом >можно будет обращаться к контролам (в частности CTabCtrl).

A:
В первом посте - один диалог для «затравки». Впрочем, я об этом уже тебе пропостил. На самом деле хотелось рассмотреть более общий случай, когда объектов достаточно много, так что выстроить их иерархию не то, чтобы трудно, а трудно уследить за всем по мере до_проектирования задачи.

Ты прав, если логика приложения не перегружена. Ну, было бы просто оболдуйством не делать так, как ты говоришь, в этом случа, и что соответствует ООП в полной мере.

Однако думаю, что хранить указатели в качестве «отдельных» приват членов CMyApp удобно в случае, если мы твёрдо стоим ТОЛЬКО на иерархической модели, не допуская «ГОРИЗОНТАЛЕЙ» ни при каких обстоятельствах, и если число объектов невелико. По существу то, что мы храним – это рут-листья.
Если же, например, в CMyApp хранить список указателей, то вызов части методов этих объектов можно упростить за счёт широковещательной рассылки соответствующей мессаги. Как ни крути, использование медиаторов, не медиаторов, рассылка сообщений или вызов виртуальных методов бродкастом – это будет «горизонталь». Почему не использовать, если удобно, объясни?


****************************************************
> xxx.FillControls(UM_STARTUP);
> xxx.DisplayControls(UM_STARTUP);
Я кстати не совсем понял, зачем эти две функции разделены, если они всегда идут парой.

A:
Скорее стереотип, чем необходимость. Как мне кажется - удобно для разделения логики получения данных, от логики их отображения. Разделение - косметическое, чтобы функции, ответственные за отображение, не маячили перед глазами, когда занят работой с данными. Началось присутствие твин-пикс давно, когда ещё только появились винды 2.0. Тогда не только программирование для вин, но и сами винды многие считали извратом. Стех времён тащу это. Инерция.
****************************************************
>>Мой вариант:
>>// стартуем приложение MFC VC++

и далее…

Да, стильно. Нет вопросов, если можно обойтись только одним объектом для хранения указателей на объекты, виртуальные методы которых будет вызывать одноименный метод theApp.


****************************************************

>>Я же говорю. С CPage-ами надо поступать проще:
>>class CPage: public CDialog {


>>public:
>>virtual void OnStartup() = 0;
>>virtual void OnRead() = 0;
>>virtual void OnEdit() = 0;
>>// ...
>>}

Ясно… Правильная обработка;))).


>>Вот поэтому, нужно чтоб объект делал только то что знает как делать точно. То бишь >>приложение переводит в новое состояние своих детей, диалог - своих, а страницы просто >>переводят контролы в нужное состояние. А использование виртуальных функций при этом >>помогает отделить интерфейс от реализации.

Ну что здесь скажешь, кроме да.
[C++] Мы тебя прощаем :))) 22.07.03 10:59  
Автор: PS <PS> Статус: Elderman
<"чистая" ссылка>
А тебе не кажется, что всю твою идею и то как ты её описал можно представить одной фразой:
"Нажимая на один контрол я хочу что бы изменялись многие"
Так ?

Тогда я не понимаю всех твоих проблемм. У тебя есть внутренее представление состояния твоей программной системы.
Нажимая котрол(ы) ты управляешь этим состоянием. А посылая контролам сообщение Update перересовываешь их в соответствие с этим состоянием.

Говоря другими словами: в обработчике WM_COMMAND кнопки ты меняешь какие то структуры (не думая о других элементах UI) а в конце рассылаешь "броадкастный" WM_UPDATE. Обрабочик этого сообщения каждого контрола сам все и отрисует у себя как надо.

>Обработчики
> кнопки CPage1::CButton m_StartEdit на закладке CPage1 не
> могут непосредственно повлиять, например, на состояние
> чекбоксов или эдитов на закладке CPage2, т.к. она (кнопка)
> не знает даже указателя на диалог CPage2. При переводе
> приложения в режим редактирования данных мы не сможем
> простым путём изменить состояние контролов других,
> «НЕЗАВИСИМЫХ» объектов, например, сделать их
> ReadOnly(false).

И слава богу что не можем... а то бы такого наворотили... И вообще, имхо, "горизонтали" - это зло. Все должно идти с верху в низ, и с низу в верх. Никаких горизонтальных связей !

>Если бы указатель был известен (например,
> мы сделали extern CPage2* Page2),

ага, а я знаю орлов которые с перепоя в какой нибудь ф-ии этот указатель тебе запросто невалидным сделают.... а ты потом все выходные проведешь в поиске ошибки.

> В
> БОЛЬШИХ ПРОЕКТАХ, КОГДА НЕОБХОДИМО СИНХРОНИЗИРОВАТЬ
> ПОВЕДЕНИЕ БОЛЬШОГО ЧИСЛА ЭЛЕМЕНТОВ УПРАВЛЕНИЯ, КОНТРОЛЫ НЕ
> ДОЛЖНЫ ИЗМЕНЯТЬ СОСТОЯНИЕ ДРУГИХ КОНТРОЛОВ !!!НАПРЯМУЮ!!!.
> Т.е. попытка непосредственно в ТЕЛЕ обработчика нажатия
> кнопки порулить состоянием других контролов НАПРЯМУЮ почти
> всегда приведёт к краху в будущем по мере разрастания
> крупного проекта.

Ты знаешь а я вчера на карте Америку обнаружил ! Вот ведь... :))))

> Вот дополнения к мотивации:
> а) объекты и контролы приложения, должны реагировать,
> как правило, на события и состояния БИЗНЕС-ЛОГИКИ
> приложения. Думаю, достаточно определить не слишком большое
> число состояний enum app_state {ST_STARTUP, ST_CLOSE,
> ST_READ, и т.д.} myapp_state.

Правильно, а все же если отделить отображение от данных ? Что если безнесс логика меняет структуры внутрение, а котрол получая лишь одно сообщение, на основе этих структур, перересовывается ? Мож так все же проще ?

> б) сотояние контролов должно зависеть на уровне
> логики приложения от app_state, а не от действий
> пользователя, например, нажатия клавиши мыши. Контрол может
> менять состояние app_state, и таким образом ОПОСРЕДОВАНО
> своё состояние. Т.е схема такова –
> (пользователь)->(нажатие на кнопку)->(изменения
> app_state)->(изменение состояния контрола, enable?
> readonly? и.д.)

Если это то же самое о чем я толкую, то нахрена я все это пишу...

> в) передачу информации между объектами приложения о
> состоянии app_state разумно, на мой взгляд, выполнять в
> виде передачи сообщений app_message {UM_STARTUP, UM_CLOSE,
> UM_READ, и т.д.} myapp_message. Каждому значению app_state
> можно поставить в соответствие значение app_message.

Нафига три, где достаточно одного ?

P.S. Дальше без комментариев... если честно, то дурно стало ;)))
[C++] Мы тебя прощаем :))) 25.07.03 04:54  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
*******************************
> А тебе не кажется, что всю твою идею и то как ты её описал
> можно представить одной фразой:
> "Нажимая на один контрол я хочу что бы изменялись многие"
> Так ?
>
> Тогда я не понимаю всех твоих проблемм. У тебя есть
> внутренее представление состояния твоей программной
> системы.
> Нажимая котрол(ы) ты управляешь этим состоянием. А посылая
> контролам сообщение Update перересовываешь их в
> соответствие с этим состоянием.
>
> Говоря другими словами: в обработчике WM_COMMAND кнопки ты
> меняешь какие то структуры (не думая о других элементах UI)
> а в конце рассылаешь "броадкастный" WM_UPDATE. Обрабочик
> этого сообщения каждого контрола сам все и отрисует у себя
> как надо.

А:
А я разве не про это написал, в том числе про бродкаст?
Кроме того, проблема, как я постил СТИЛЯ. В ряде случаев можно обойтись традиционным способом реагирования на события (не "броадкастный" мессагинг и вызов сответствующих обработчиков) при помощи непосредственного вызовов переопределенных "интерфейсных" методом классов, с тем, чтобы те передали эти вызовы по иерархии вниз. Ты и я в своём посте пишем про "плоскую" модель, когда объекты обмениваются мессагами, а не вызывают соответствующие изменению состояния методы. Это проще в некоторых ситациях, но не во всех. И я пытаюсь найти нечто среднее.

******************************
> >Обработчики
> > кнопки CPage1::CButton m_StartEdit на закладке CPage1
> не
> > могут непосредственно повлиять, например, на состояние
> > чекбоксов или эдитов на закладке CPage2, т.к. она
> (кнопка)
> > не знает даже указателя на диалог CPage2. При переводе
> > приложения в режим редактирования данных мы не сможем
> > простым путём изменить состояние контролов других,
> > «НЕЗАВИСИМЫХ» объектов, например, сделать их
> > ReadOnly(false).
>
> И слава богу что не можем... а то бы такого наворотили... И
> вообще, имхо, "горизонтали" - это зло. Все должно идти с
> верху в низ, и с низу в верх. Никаких горизонтальных связей
> !

А:
Не знаю, что и ответить про "горизонтали". В том смысле, в котором написано выше, я с тобой согласен. Но если посмотреть как работает паттерн "посредник" - это что, "верикаль" или "горизонталь". Это плоская, не иерархическая модель взаимодействия. Не думаю, что "паттерны проектирования" - это плохо.

******************************
> > В
> > БОЛЬШИХ ПРОЕКТАХ, КОГДА НЕОБХОДИМО СИНХРОНИЗИРОВАТЬ
> > ПОВЕДЕНИЕ БОЛЬШОГО ЧИСЛА ЭЛЕМЕНТОВ УПРАВЛЕНИЯ,
> КОНТРОЛЫ НЕ
> > ДОЛЖНЫ ИЗМЕНЯТЬ СОСТОЯНИЕ ДРУГИХ КОНТРОЛОВ
> !!!НАПРЯМУЮ!!!......
> Ты знаешь а я вчера на карте Америку обнаружил ! Вот
> ведь... :))))

A:
Зря смеёшься, сколько угодно таких орлов видел, в том числе и не пьющих.
Топик и для них.


******************************
> Правильно, а все же если отделить отображение от данных ?
> Что если безнесс логика меняет структуры внутрение, а
> котрол получая лишь одно сообщение, на основе этих
> структур, перересовывается ? Мож так все же проще ?
>

A:
А вот здесь на пять баллов. Хотя это и тема другого топика, но мысль очень здравая. Я даже в борланде не читал данные непосредственно в контролы, за исключением, например, TreeView, и других контролов "только для навигации по набору данных ". Самое прикольное, что обсуждая здесь вопросы архитектуры, мы вообще ничего не говорим о представлении данных. А живёт то всё вместе, хорошо если в разных квартирах.

********************************
> Нафига три, где достаточно одного ?

A:
Ты смеёшься? Какое состояние - ST_CLOSE ? ;)))


********************************
> P.S. Дальше без комментариев... если честно, то дурно стало
> ;)))

Думаешь мне легко !!!ЭТО!!! перечитывать. Там у меня всё по "шагам". Боюсь, чтобы следующий шаг не был в больничную койку в психушку. Она тут под боком;))))
[C++] Дополнения к нижесказанному 21.07.03 14:43  
Автор: amirul <Serge> Статус: The Elderman
<"чистая" ссылка>
> Такую обработку несложно построить в BC++, т.к. все
> контролы являются членами класса формы приложения. Т.е из
> указанных выше функций (методов формы) видны все контролы.
> Гораздо сложнее это реализовать в VC++. Это связано с тем,
В VC++ (в частности в MFC Class Wizard-е - вызывается клавишей Ctrl-W) можно добавлять переменные в сгенеренный класс. В частности в класс диалога можно добавить переменные TreeView-а и TabCtrl-а.

> что классы, в которых созданы члены-контролы ничего не
> знают друг о друге, и не видят соответствующих контролов в
> общем случае. Например, если Вы создали приложение с
Это правильно что не видят. Потому, что программист должен сам знать чего хочет, а среда разработки - только инструмент. Кроме того в общем случае на один и тот же контрол можно добавить много переменных разных типов, а можно и вообще не засорять пространство имен, если оно не нужно.

> CTreeView – СFormView, где на форму положили CTabCtrl и для
> каждой закладки создали по диалогу CPage1,…., CPage20, то
> для каждого класса!!! придётся писать соответсвующие
> обработчики bool FillControls(unsigned int msg), bool
> DisplayControls. При этом необходимо ещё найти приемлемый
> механизм обмена сообщениями между классами.

> У кого есть идеи? Предлагаю для затравки тупой метод
> решения задачи - использование глобальных указателей на
> каждый из диалогов. Умные – предложите Вы.
Совершенно необязательно даже в этом случае делать их глобальными. О них необходимо знать только диалогу - поэтому и поместить их надо мемберами в диалог. CTreeView через Class Wizard, а страницы - по мере создания (насколько я понял страницы добавляются только динамически, то бишь появляются только в рантайме, поэтому добавить переменные и для них через Class Wizard нельзя, так как он привязан к Design Time-у).

Но есть и более эффективный вариант (и более правильный с точки зрения ООП). С TreeView-ом разобрались, а для страниц правильнее сделать общий базовый класс, с описанием всех функций, необходимых для работы (скорее всего виртуальных, но это зависит от конкретной задачи). Далее при добавлении страницы в поле lParam записывать указатель на CPage (базовый класс). После таких манипуляций станет возможно в Class Wizard-е добавить только переменную для CTabCtrl-а. А перечислять все страницы при помощи CTabCtrl::GetItem(). Делаем обратное преобразование из lParam-а к (CPage *) и работаем с этим указателем через его виртуальные интерфейсы.

Кроме того, как уже было указано, использование case-ов не по CPP-овски. Вызов функции тоже можно считать сообщением, поэтому вместо FillControls(STARTUP) лучше делать прямой вызов ClearControls()-ов. Ну в крайнем случае сделать приватную helper-функцию, которая будет заниматься непосредственно перечислением, а в нее слать pointer-to-member на необходимую функцию (я конечно не уверен, что это можно делать с виртуальными функциями, но насколько я себе представляю это не так уж сложно реализовать, а значит скорее всего реализовано в CPP)
1  |  2 >>  »  




Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 1 s   Design: Vadim Derkach