информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
За кого нас держат?Портрет посетителяАтака на Internet
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
все доски
FAQ
IRC
новые сообщения
site updates
guestbook
beginners
sysadmin
programming
operating systems
theory
web building
software
hardware
networking
law
hacking
gadgets
job
dnet
humor
miscellaneous
scrap
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[c++] сообщения пользователя мотивация. да простит меня модератор. 22.07.03 09:48  Число просмотров: 1754
Автор: 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.
Ещё раз благодарю всех тех, кто откликнулся. Обещаю, что дальше не буду писать столь пространно и столь длинные посты. Это было необходимо в настоящем постинге, т.к. далее я буду только ссылаться на предыдущие свои постинги. Если, конечно, дискуссия состоится.
<programming> Поиск 






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


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