Глава 4.
Классы окон библиотеки MFC
Труднее всего научиться общему языку.
Лешек Кумор
Вспомним, что Windows — это операционная система, которая прежде всего работает с окнами. Со своей стороны, подавляющую часть библиотеки MFC составляют классы окон. Пришло время подробно познакомиться и с теми и с другими, а также с их взаимодействием.
Объекты оконных классов библиотеки MFC, естественно, отличаются от окон Windows, но тем не менее тесно с ними связаны. Полное и правильное понимание взаимосвязей между ними является залогом успеха при создании приложений.
Как и любой другой объект C++, объект "окно" создается и уничтожается конструктором и деструктором класса, соответственно. Это с одной стороны. А с другой, окно Windows — это внутренняя структура данных операционной системы, которая формируется функцией интерфейса .прикладного программирования Win32 API Create, а разрушается функцией Destroy Window. В системе Windows каждое окно идентифицируется своим дескриптором, значение которого хранится в переменной m_hWnd объекта CWnd (рис. 4.1).
Рис. 4.1. Объект класса CWnd связан с окном Windows через дескриптор m_hWnd
Несколько возможных способов создания и работы с окнами мы рассмотрели в предыдущей главе, поэтому здесь акцентируем наше внимание на тех типах окон, которые имеются в Windows.
Окна, определенные в системе Windows
Существует всего три основных типа окон:
- Перекрывающиеся (overlapped)
- Всплывающие или вспомогательные (popup)
- Дочерние (child)
из которых программист может создавать множество самых разнообразных объектов, комбинируя предопределенные биты стиля, имеющие имена с префиксом WS_ (от window style — стиль окна). Рассмотрим более подробно эти биты, их влияние на внешний вид и некоторые свойства окон. Начнем с простого перечисления:
WS_BORDER
Задает окно, имеющее рамку без заголовка
WS_CAPTION
Задает окно, имеющее заголовок и рамку. Как правило, этот стиль используется для перекрывающихся окон и не может применяться совместно со стилем WS_DLGFRAME
WS_CHILD
Устанавливает дочернее окно; не может использоваться совместно со стилем WS_POPUP
WS_CLIPCHILDREN
Исключает область, занятую дочерним окном, из области рисования родительского окна; используется только для родительских окон
WS_CLIPSIBLINGS
Исключает все другие дочерние окна из своей области рисования; другими словами, если дочерние окна перекрываются, а этот стиль не указан, то при изменении рабочей области одного из окон могут быть испорчены рабочие области других дочерних окон; этот стиль используется только вместе со стилем WS_CHILD
WS_DISABI_ED
Задает неактивное окно, т. е. сразу после его создания окно недоступно для ввода с клавиатуры или с помощью мыши
WS_DLGFRAME
Задает окно, имеющее двойную рамку и не имеющее заголовка
WS_GROUP
Определяет первый элемент управления группы окон, к которым пользователь может переходить при помощи клавиш со стрелками; все элементы управления, определенные с этим стилем, заканчивают текущую и начинают новую группу (одна группа заканчивается там, где начинается другая)
WS_HSCROLL
Задает окно, имеющее горизонтальную полосу прокрутки
WS_VSCROLL
Задает окно, имеющее вертикальную полосу прокрутки
WS_MAXIMIZE
Задает отображение окна в развернутом виде
WS_MAXIMIZEBOX
Задает окно, имеющее кнопку "Развернуть"; если окно является дочерним окном элемента управления, то этот флаг стиля используется под другим именем — WS_TABSTOP
WS_MINIMIZE
Задает отображение окна в виде пиктограммы; используется только со стилем WS_OVERLAPPED
WS_MINIMIZEBOX
Задает окно, имеющее кнопку "Свернуть"; если окно является дочерним окном элемента управления, то этот флаг стиля используется под другим именем — WS_GROUP
WS_OVERLAPPED
Устанавливает, что окно является перекрывающимся; обычно имеет заголовок и рамку
WS_POPUP
Задает всплывающее окно; не может использоваться совместно со стилем WS_CHILD
WS_SYSMENU
Задает окно, имеющее пиктограмму системного меню в полосе , заголовка
WS_TABSTOP
Назначается одному или нескольким элементам управления для того, чтобы между ними можно было перемещаться с помощью клавиши табуляции <ТаЬ>
WS_THICKFRAME
Задает окно, имеющее утолщенную рамку, при помощи которой можно изменять размер окна
WS_VISIBLE
Устанавливает режим, когда окно становится видимым сразу после создания; этот стиль в основном используется для окон диалога
Помимо перечисленных стилей окон Windows, в файле <winuser.h> определены стили, являющиеся комбинацией наиболее часто используемых стилей.
WS_OVERLAPPEDWINDOW
Комбинация стилей WSjDVERLAPPED, WS_CAPTION, WS_SYSMENU, WSJTHICKFRAME, WSJ/IINIMIZEBOX и WS_MAXIMIZEBOX
WS_POPUPWINDOW
Комбинация стилей WS_POPUP, WS_BORDER и WS_SYSMENU
При работе с окнами часто также используются дополнительные или расширенные биты стиля:
WS_EX_ABSPOSITION
Определяет, что позиция окна задается в абсолютных единицах относительно левого верхнего угла экрана
WS_EX_ACCEPTFILES
Определяет, что окно допускает перетаскивание файлов (drag-and-drop)
WS_EX_CUENTEDGE
Придает "трехмерный эффект" границе окна, клиентская область при этом как бы вдавлена в окно
WS_EX_CONTEXTHELP
Включает знак вопроса в заголовок окна; когда пользователь нажимает на этот знак, то курсор изменяется на указатель в виде знака вопроса; если теперь выбрать какое-либо дочернее окно — оно получит сообщение WM_HELP
WS_EX_CONTROLPARENT
Позволяет пользователю перемещаться по дочерним окнам родительского окна, используя для этого клавишу табуляции <ТаЬ>
WS_EX_DLGMODALFRAME
Задает окно, имеющее удвоенную границу, что при использовании совместно со стилем WS_CAPTION позволяет создавать окно с полосой заголовка
WS_EX_LEFT
Устанавливает для окна свойство левого выравнивания (по умолчанию)
WS_EX_LEFTSCROLLBAR
Задает расположение полосы прокрутки (если она имеется) с левой стороны рабочей (клиентской) области окна
WS_EX_LTRREADING
Используется по умолчанию и определяет естественный порядок (слева направо) для большинства языков ввода и чтения текста
WS_EX_MDICHILD
Задает дочернее окно MDI
WS_EX_NOPARENTNOTIFY
Определяет, что дочернее окно, созданное с этим стилем, не посылает сообщения WM_PARENTNOTIFY своему родительскому окну при создании или уничтожении
WS_EX_RIGHT
Устанавливает в зависимости от класса окна групповое свойство правого выравнивания
WS_EX_RIGHTSCROLLBAR
Задает расположение полосы прокрутки (если она имеется) с правой стороны рабочей (клиентской) области окна; используется по умолчанию
WS_EX_RTLREADING
Определяет порядок ввода и чтения текста справа налево
WS_EX_SMCAPTION
Задает окно, имеющее уменьшенную высоту полосы заголовка; используется при создании "плавающих" панелей инструментов
WS_EX_STATICEDGE
Определяет окно, имеющее трехмерный стиль границы, заданный для использования в элементах, к которым пользователь не получает доступа (статические элементы управления)
WS_EX_TOOLWINDOW
Задает окно, которое используется для создания "плавающей" панели инструментов; такое окно имеет укороченную полосу заголовка и не отображается на панели задач
WS_EX_TOPMOST
Задает окно, которое будет располагаться поверх всех окон (созданных без этого стиля); для установки или сброса этого бита можно использовать функцию SetWindowPos
WS_EX_TRANSPARENT
Задает прозрачное окно, т. е. не закрывающее другие окна, расположенные "под ним"; сообщение WM_PAINT такое окно получает до того, как это сообщение получают все окна того же уровня, но расположенные "под ним"
WS_EX_WINDOWEDGE
Задает окно, имеющее приподнятую границу для создания "трехмерного эффекта"
Так же, как в случае основных стилей, для расширенных определены два комбинированных дополнительных стиля (WINUSER.H);
WS_EX_OVERLAPPEDWINDOW
Комбинация стилей WS_EX_CLIENTEDGE и WS_EX_WINDOWEDGE
WS_EX_PALETTEWINDOW
Комбинация стилей WS_EX_WINDOWEDGE, WS_EX_SMCAPTION и WS_EX_TOPMOST
После того как мы перечислили все возможные стили окон, осталось сказать несколько слов об упомянутых выше основных типах окон Windows. Напомним, что их всего три.
Примечание
Наиболее полным семантическим эквивалентом английского термина "popup window" является термин "окно-поплавок". Мы будем называть такие окна всплывающими.
Какие же возможности предоставляет нам библиотека MFC в плане создания и работы с окнами? Если обратимся к иерархии классов MFC, то вспомним, что все классы, имеющие какое-либо отношение к окнам, являются производными (прямо или косвенно) от единственного класса CWnd (рис. 4.2).
Рис. 4.2. Иерархия оконных классов
Этот класс, а также специальные производные от него классы, включая CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CView и CDialog, имеющиеся в библиотеке, спроектированы таким образом, чтобы сделать за нас большую часть обязательной, но рутинной работы с различными типами окон Windows.
Класс CWnd — это базовый класс для всех окон, созданных на базе библиотеки MFC (рис. 4.3). Помимо предоставления всем своим производным классам большого числа функций для работы с окнами, он также служит для создания разнообразных дочерних окон.
Рис. 4.3. CWnd— базовый класс всех окон библиотеки MFC
Из имеющихся компонентов класса CWnd для нас на данном этапе интерес представляет один:
HWND CWnd::m_hWhd
m_hWnd является дескриптором окна Windows, присоединенного к объекту класса CWnd. Это достаточно важный параметр, особенно для главного окна приложения, т. к. он позволяет библиотеке взять на себя всю работу для корректного завершения приложения, когда пользователь закрывает его главное окно.
Класс CWnd содержит практически все функции, необходимые для работы с окнами. Таких функций более двухсот и их можно разбить на несколько категорий, каждая из которых включает функции, направленные на решение определенного круга задач. Приведем список категорий с кратком характеристикой входящих в них функций:
Оставшиеся категории функций имеют достаточно специфическую направленность и будут обсуждаться в соответствующих разделах книги. К ним относятся следующие категории: функции для работы с полосами прокрутки, каретками, элементами блока диалога, меню, таймерами, всплывающими подсказками, а также функции для работы с буфером обмена и обработчики сообщений.
Класс CWnd служит для выполнения двух основных целей: предоставление интерфейса всем другим оконным классам библиотеки MFC и создание дочерних окон Windows. В связи с этим мы ограничились здесь простым перечислением основных реализованных в классе функций. Многие из них мы рассмотрим при обсуждении конкретных примеров программ.
Рис. 4.4. Основной класс для создания фреймов
В отличие от своего базового класса CWnd класс CFrameWnd (рис. 4.4) служит для создания перекрывающихся или всплывающих окон и поддерживает однодокументный интерфейс Windows (SDI). С этим классом (и производными от него) тесно связано понятие фрейма (frame window). Под ним понимается окно, которое координирует взаимодействие пользователя с приложением. Оно отражается на экране в виде рамки с необязательной строкой состояния и стандартными элементами управления. Это окно отвечает за управление размещением своих дочерних окон и других элементов рабочей области. Кроме того, фрейм переадресует команды своим представлениям (специальным дочерним окнам) и может отвечать на сообщения от элементов управления.
Примечание
Понятие "представление" (view) очень тесно связано с архитектурой "документ/представление", которую мы рассмотрим в одной из следующих глав.
Класс CFrameWnd спроектирован таким образом, чтобы взять на себя выполнение всех основных функций приложения Windows на базе интерфейса SDI:
Из определенных в классе CFrameWnd компонентов мы остановимся на одном:
Startic AFX_DATA const CRect CFramewnd: : rectDefault
Компонент определяет константный объект класса CRect, который предписывает Windows самостоятельно установить размеры и положение создаваемого окна и задается следующим образом:
const AFX_DATADEF CRect CFrameWnd::rectDefault(
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0
0;
Функции этого класса можно сгруппировать по следующим категориям:
Помимо своего основного назначения — поддержки SDI-приложений — класс CFrameWnd служит базовым для классов CMDIFrameWnd и CMDlChildWnd, которые созданы специально для организации приложений на базе MDI. Но о них несколько позже.
Еще раз подчеркнем важный момент — на базе этого класса легко и удобно создавать фреймы окон. А мы пока переходим к "развитию" нашего первого приложения. А именно — к созданию и работе с различными типами окон Windows.
Пора более подробно рассмотреть процесс создания и работы с окнами. В этом разделе мы познакомимся с тем, как
В результате мы должны получить нечто похожее на представленное на
рис. 4.5.
Рис. 4.5. Различные типы окон приложения Styles
Создание главного окна SDI-приложения
В рассмотренной в предыдущей главе программе First мы образовали класс нашего окна из базового CWnd. Это допустимый, но очень неэффективный путь. Пришло время познакомить вас с другим возможным подходом для создания главного окна SDI-приложения.
Примечание
Такой подход стал поддерживаться только в шестой версии Visual C++. До этого все необходимые действия приходилось выполнять вручную. Но даже если вы пользуетесь Visual C++ 6, рекомендую не пропускать эту главу, поскольку здесь вы найдете подробнейшее описание не только того кода, который генерирует AppWizard, но и много другой полезной информации.
Прежде всего обратим внимание на базовый класс нашего главного окна:
class CMainFraime : public CFrameWnd
{
...
};
Надеюсь, здесь уже нет необходимости объяснять, почему в качестве базового был выбран класс CFrameWncr. Действительно, этот класс спроектирован таким образом, чтобы предоставить максимум удобств для работы с перекрывающимися и всплывающими окнами. А, как вы помните, в качестве главного окна приложения наиболее приемлемым является именно перекрывающееся окно. Теперь нам надо подробно рассмотреть функцию CStylesApp::lnitInsnance. где, как вы помните, и начинается создание главного окна:
BOOL CStylesApp::Triitlnstance()
{
// Создаем объект класса CMain.Frame
CMainFrame ""pMainWnd = new CMainFrame;
r_pMainWnd =- pMainWnd;
// Создаем собственно окно Windows
pMainWnd->LoadFrame (IDR^MAINFRAME);
// Определяем размеры экрана
int cx = : : GetSystemMetrics (SM CXSCREEN) /8 ;
int cy = : :GetSystemMetrics (SM CXSCREEN) /4 ;
// В зависимости от них устанавливаем размеры окна
// и выводим его на экран
pMainWnd->SetWindowPos (NULL,
cx,
cy,
6*cx,
2*cy,
SWP_NOZORDER | SWP_SHOWWINDOW);
return TRUE;
}
Как видите, процесс создания окна состоит из трех шагов. Прежде всего необходимо создать объект "главное окно":
CMainFrame *pMainWnd = new CMainFrame;
Следующий шаг состоит в вызове специальной функции, которая создает окно Windows и присоединяет его к нашему объекту:
pMainWnd->LoadFrame(IDR_MAINFRAME);
И наконец, следует отобразить окно на экране, чтобы пользователь мог с ним взаимодействовать:
pMainWnd->SetWindowPos (NULL,
cx,
су,
6*сх,
2*су,
SWP_NOZORDER | SWP_SHOWWINDOW);
Создание объекта класса не должно вызвать никаких вопросов, и поэтому рассмотрим процесс создания окна Windows или, используя терминологию MFC, фрейма окна.
Из двух функций, реализованных в классе CFrameWnd, в данном случае мы остановили свой выбор на виртуальной функции CFrameWnd::LoadFrame. которая берет на себя задачу регистрации оконного класса, используя для этого предопределенный шаблон, и извлекает из файла ресурсов, задаваемого идентификатором, остальные необходимые для создания окна Windows параметры:
virtual BOOL CFrameWnd::LoadFrame (
UINT nIDResource,
DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW | FWS ADDTOT1TLE,
CWnd *pParentWnd = NULL,
CCreateContext *pContext = NULL)
Функция возвращает значение TRUE, если создание фрейма прошло успешно, и FALSE — в противном случае. Она имеет следующие параметры: nIDResource — идентификатор ассоциированных с фреймом разделяемых ресурсов, который определяет меню, таблицу командных клавиш (accelerator keys), пиктограмму и строку, помещаемую в заголовок окна; pParentWnd— указатель на родительское окно данного фрейма (необязательный параметр и для главного окна должен быть равен NULL— ведь главное окно не может иметь "родителя"); pContext— указатель на структуру CCreateContex, которая определяет объекты, связанные с данным фреймом; dwDefaultStyle— стиль фрейма (необязательный параметр). Дополнительно к стандартным стилям окна могут быть использованы еще и следующие:
FWS_ADDTOTITLE
В конец заголовка окна будет добавлена дополнительная текстовая строка
FWS_PREFIXTITLE
Перед заголовком окна будет помещено имя документа
FWS_SNAPTOBAR
Размеры окна будут подогнаны под размеры панели элементов управления
Основное назначение функции LoadFrame — максимальное упрощение процесса создания фрейма окна. Используя идентификатор ресурсов, функция извлекает из них необходимые параметры и... вызывает функцию СFrameWnd::Create, рассмотренную в предыдущей главе. Функция загружает ресурс меню и выполняет функцию CWnd::CreateEx, которая регистрирует имя оконного класса, заполняя структуру WNDCLASS. и вызывает виртуальную функцию PreCreoteWindow.
Здесь мы немного приостановимся, поскольку сначала надо разобраться с ресурсами. Вы обратили внимание, что единственным обязательным параметром функции LoadFrame является идентификатор ресурсов? Говоря точнее, обязательным является только один ресурс, а именно — меню. И если его не определить, после запуска приложения получим сообщение об ошибке (рис. 4.6).
Рис. 4.6. Неопределение меню — единственного обязательного ресурса для работы с функцией LoadFrame — приводит к ошибке во время выполнения приложения
Поэтому следующим нашим шагом будет создание простейшего меню.
Используя мощные возможности Visual C++, создать меню чрезвычайно просто выполните приведенную ниже последовательность действий, и у вас сразу все заработает.
1. Раскройте вкладку Resource View в окне Workspace, щелкните правой кнопкой мыши на Styles Resource и в появившемся контекстном меню выберите пункт Insert. В результате вы увидите окно Insert Resource (рис. 4.7), в котором необходимо выделить надпись Menu и нажать кнопку New. Результат ваших действий представлен на рис. 4.8.
2. Щелкните правой кнопкой мыши на "пунктирном прямоугольнике" и в появившемся контекстном меню выберите пункт Properties. Введите название пункта меню (я использовал стандартное — Файл) в поле Caption (рис. 4.9) и обратите внимание, что под исходной полосой меню появился новый "прямоугольник". Выделите его и введите название в поле Caption, а также идентификатор в поле ID (можно выбрать один из стандартных, как мы делаем в данном конкретном случае — ID_APP_EXIT).
Рис. 4.7. Диалог для выбора типа создаваемого ресурса
Рис. 4.8. Исходная точка для создания нашего меню
Рис. 4.9. В этом окне можно определить параметры элементов меню (да и других ресурсов тоже)
Примечание
Для того чтобы каждый раз не приходилось пользоваться правой кнопкой мыши, нажмите кнопку Keep Visible, расположенную в левом верхнем углу окна ... Properties. В результате окно всегда будет доступно.
3. Осталось присвоить соответствующий идентификатор самому созданному ресурсу. Для этого в окне Workspace выделите строку IDR_MENU1 и в поле ID окна Menu Properties введите IDR_MAINFRAME — именно такой идентификатор мы использовали в функции LoadFrame.
4. Вновь скомпилируйте и запустите приложение.
Результат, как видите, совершенно другой — все работает, ничего не "падает", и окно ведет себя аналогично тому, как это было в нашем первом примере. Более того, выбрав пункт меню "Выход", вы увидите, что приложение действительно закрылось. И это несмотря на то, что мы не добавили ни строчки кода. Благодаря выбранному нами идентификатору библиотека сама разобралась в том, чего мы хотим, и все сделала.
Итак, на данный момент мы подошли к тому же результату, что и в первом приложении, только "с другой стороны". Однако вернемся к нашему главному окну.
Создание главного окна приложения (продолжение)
Мы остановились на том, что в процессе выполнения функции PreCreateWindow система Windows посылает в окно следующие сообщения: WM_GETMINMAXINFO, WM_NCCREATE, WM_NCCALKSIZE и WM_ CREATE. Если при создании окна был определен стиль WS_VISIBLE, то в окно дополнительно посылаются все сообщения, необходимые для активизации и отображения окна на экране. Таким образом, переопределяя функцию PreCreateWindow и/или обработчик любого из представленных сообщений Windows, можно изменять параметры оконного класса и собственно окна как до, так и после создания окна Windows.
Рассмотрим, как этот механизм реализован в нашей программе. Начнем с переопределения функции CFrameWnd::PreCreateWindow. Для этого:
1. Откройте окно ClassWizard, выбрав пункт меню View ClassWizard или нажав аналогичную кнопку на панели инструментов (это для тех, кто как и я предпочитает работать с кнопками панели инструментов, а не через меню).
2. В раскрывающемся списке Class name выберите класс CMainFrame, a в списке Messages— PreCreateWindow (рис. 4.10). Нажмите кнопку Add Function (или дважды щелкните левой кнопкой мыши по имени PreCreateWindow) и затем кнопку Edit Code, чтобы непосредственно перейти на то место проекта, где мы будем выполнять дальнейшие действия.
Рис. 4.10. ClassWizard существенно облегчает процесс создания новых функций
3. Внесите в переопределенную функцию необходимые изменения. В нашем случае она примет вид:
BOOL CMainFrame:: PreCreateWindow (GREATEST/ROOT& cs)
{
cs.lpszName = "Главное окно приложения \"Стили\"";
return CFrameWnd::PreCreateWindow(cs);
}
Единственный параметр, передаваемый в эту функцию, — ссылка на структуру CREATESTRUCT, которая определяет параметры инициализации, посылаемые в оконную процедуру, и содержит следующие поля:
typedef struct tagCREATESTRUCT{
LPVOID IpCreateParams;
HANDLE hlnctance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCSTR IpszName;
LPCSTR IpszClass;
DWORD dwExStyie;
}CREATESTRUCT;
Назначение полей этой структуры: IpCreateParams — указатель на данные, используемые при создании окна; hlnstance — дескриптор модуля, который владеет окном; hMenu— идентификатор меню, используемого окном; hwndParent — дескриптор окна-родителя, если этот параметр равен NULL, то новое окно является окном верхнего уровня; су, сх, у, х— целые числа, задающие соответственно ширину и высоту окна, а также его положение относительно своего владельца; IpszName— символьная строка, задающая имя окна; IpszClass— символьная строка, содержащая имя класса, которому принадлежит окно; dwExStyie — определяет атрибуты расширенного стиля окна.
Мы использовали функцию PreCreateWindow, чтобы не заниматься процедурой регистрации класса окна, возложив эту задачу на библиотеку, поскольку принятые по умолчанию установки нас вполне устраивают. Единственное, что нам следует заменить, это заголовок окна:
cs . IpszName = "Главное окно приложения \"Стили\ "";
Это один из возможных способов присвоения имени окну. После того как окно Windows создано и присоединено к объекту фрейма окна библиотеки MFC, ему посылается сообщение WM_CREATE. Для этого сообщения в библиотеке (точнее, в классе CWnd) создан специальный обработчик — OnCreate, который можно переопределить под свои потребности:
int CMainFrame: :OnCreate (LPCREATESTRUCT IpCreateStruct)
{
if (CFrameWnd::OnCreateiIpCreateStruct: = -1)
renurn -1;
return 0;
}
Этот обработчик сообщения — одно из возможных мест, где создаются и присоединяются к окну произвольные элементы пользовательского интерфейса, о чем мы обязательно поговорим в дальнейшем.
Для правильного завершения процесса создания окна не забывайте вызывать перед какими-либо своими действиями обработчик базового класса CFrameWndr.OnCreate. Вы, конечно же, понимаете, что последняя фраза относится к случаю самостоятельного создания прототипа этой функции — если воспользоваться услугами ClassWizard, то каркас функции будет вам уже предоставлен.
После того как фрейм создан, его необходимо вывести на экран. Библиотека предоставляет несколько функций для реализации этого процесса. Для отображения нашего главного окна мы остановились на одной из них — функции СWnd::SetWindowPos.
BOOL CWnd::SetWindowPos (
const CWnd *pWndlnsertAfter,
int x,
int y,
int cx,
int cy,
UINT nFlags)
Вызов этой функции позволяет изменить как размеры, так и расположение дочерних и всплывающих окон относительно экрана и других окон. Она имеет следующие параметры: pWndlnsertAfter — указывает на объект CWnd, после которого будет расположено окно. Вместо этого указателя можно использовать одно из следующих предопределенных значений, задающих расположение окна относительно других окон:
wndBottom
Разместить окно под другими окнами (внизу стека Z-no-рядка); если окно имело статус wndTopMost то этот статус теряется — удаляется флаг расширенного стиля WS_EX_TOPMOST
wndTop
Разместить окно над другими окнами (на вершине стека Z-порядка)
wndTopMost
Разместить окно над всеми другими окнами, имеющими статус wndTopMost; эта позиция сохраняется (флаг расширенного стиля WS_EX_TOPMOST остается установленным), даже когда окно находится в неактивном состоянии
wndNoTopMost
Переместить окно выше всех окон, имеющих статус wndTop, но ниже окон, имеющих статус wndTopMost
х, у, сх, су— задают новые значения, соответственно, горизонтального и вертикального расположения окна, его ширины и высоты. nFlags — определяет параметры размера и позиции окна и может принимать одно из следующих значений:
SWP_DRAWFRAME
Задает рамку вокруг окна, определенную в классе окна
SWP_HIDEWINDOW
Делает окно невидимым
SWP_NOACTIVATE
Блокирует активизацию окна
SWP_NOMOVE
Предписывает окну сохранение текущей позиции; параметры х и у игнорируются
SWP_NOREDRAW
Предписывает не перерисовывать окно; после перемещения приложение должно перерисовать окно самостоятельно
SWP_NOSIZE
Задает сохранение текущих размеров; параметры сх и су игнорируются
SWP_NOZORDER
Задает сохранение текущего расположения окна относительно других окон, при этом параметр pWndlnsertAfter игнорируется
SWP_SHOWWINDOW
Отображает окно
Примечание
Windows располагает окна на экране согласно их положению в так называемом Z-порядке, который определяет позицию окна в стеке перекрывающихся окон. Стек окна ориентирован вдоль воображаемой оси (Z-оси) внутрь экрана. Окно, расположенное на вершине стека, перекрывает все другие окна, а расположенное внизу— перекрывается всеми другими окнами. Приложение не может активизировать окно без того, чтобы не перенести его на самый верх Z-порядка. Кроме того, расположение окна относительно экрана и других окон может изменяться без каких-либо ограничений.
Мы использовали функцию SetWindowPos для указания размеров и расположения окна, а также для того, чтобы сделать его видимым.
Примечание
Как я уже говорил, существуют и другие способы решения этой задачи, многие из них также будут рассмотрены.
Следующий вопрос, на котором мы остановимся— как создаются дочерние окна.
Примечание
Поскольку любое порожденное окно программисты зачастую называют "дочерним", то часто возникает некоторая путаница. Вы должны совершенно четко отличать стиль WS_CHILD от более широкого разговорного понятия "дочерний".
Теперь, после создания основного окна приложения, можно переходить к созданию некоторого дополнительного пространства для отображения информации и/или для получения команд от пользователя. Каждому "ребенку", как и основному окну, можно приписать конкретный стиль таким образом, чтобы приспособить его внешний вид и назначение к стоящим перед ним задачам. В приложении Styles мы разбили создаваемые окна на два набора. Окна первого располагаются на экране независимо от положения основного окна и имеют стиль WS_POPUP, а для окон из второго набора установлен стиль WS_CHILD и, следовательно, они не могут покинуть пределы рабочей области основного окна.
Для создания дочерних окон необходимо проделать следующие действия:
Есть и другие действия, но к ним мы перейдем постепенно.
Первое из перечисленных в списке действий совершим, воспользовавшись мастером ClassWizard.
1. Откроем окно ClassWizard и в нем нажмем кнопку-меню Add Class, где выберем элемент New (рис. 4.11).
Рис. 4.11. Создаем новый класс
2. В результате на экране появится диалоговое окно New Class, в поле Name которого вводим имя вновь создаваемого класса — у меня это CChildWnd, а в раскрывающемся списке Base class — имя базового класса, в данном случае CWnd (рис. 4.12).
3. Нажимаем кнопку ОК и новый класс создан.
4. Повторяем действия, описанные в п. 1. Только теперь не будем создавать новых файлов, а воспользуемся уже существующими. Для этого нажмем кнопку Change и в диалоговом окне Change files выберем файлы <childwnd.h> и <childwnd.cpp> (рис. 4.13).
5. В качестве базового класса в данном случае используем класс CFrameWnd, поскольку CWnd не стоит использовать для создания всплывающих окон (рис. 4.14).
Примечание
Такое разбиение не продиктовано необходимостью и произведено лишь для наглядности. Дело в том, что, как мы упоминали, на базе класса CWnd можно создавать только дочерние окна (имеющие стиль WS^CHILD). Его мы и решили использовать для этих целей. На базе же класса CFrameWnd могут быть созданы окна любого из трех имеющихся типов. Поэтому для создания всплывающих окон именно он и был использован в качестве базового.
Рис. 4.12. Присваиваем имя и выбираем базовый класс для вновь создаваемого
Рис. 4.13. Не будем создавать новых файлов
Рис. 4.14. Для всплывающих окон в качестве базового используем класс CFrameWnd
Как видите, создать новый класс чрезвычайно просто. К сожалению, это высказывание справедливо не всегда — не из всех классов библиотеки MFC можно создавать свои с помощью мастера ClassWizard.
Следующим шагом является создание объектов новых классов и их привязка к главному окну приложения. Последовательность действий при этом следующая:
1. В определении класса CMainFrame вводим объекты новых классов
class CMainFrame : public CFrameWnd
{
...
CChildWnd *apChildWnd[5];
CPopupWnd *apPopupWnd[5];
...
};
2. В конструкторе класса CMainFrame устанавливаем указатели на эти объекты в NULL, чтобы иметь гарантии корректности создания окон
CMainFrame::CMainFrame()
{
for(int i = 0; i < 5; i++)
{
apChildWnd[i] = NULL;
apPopupWnd[i] = NULL;
}
}
3. Теперь осталось вернуться к обработчику сообщения WM_CREATE:
extern char szNameWnd[128];
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStructi
{
if (CFrameWnd::OnCreate(IpCreateStruct) == -1)
return -1;
// Создаем пять дочерних окон for (int i = 0; i < 5; i++)
{
apChildWnd[i] = new CChildWnd;
apChildWnd[i]->Create(NULL, NULL, WS_VISIBLE,
CRectfO, 0, 0, 0), this, (UINT)i);
}
// Установка нового курсора для класса дочерних окон
::SetClassLong(apChildWnd[0]->GetSafeHwnd() , GCL_HCURSOR,
(LONG)AfxGetApp()->LoadStandardCursor(IDC_IBEAM));
// Создаем четыре всплывающих окна
for(i =0; i < 4; i++)
{
apPopupWnd[i] = new CPopupWnd;
apPopupWnd[i]->Create(NULL, NULL, 0,
rectDefault, this);
// Указываем номер окна в поле данных, ассоциированных с окном
::SetWindowLong(apPopupWnd[ij->Get.SafeHwnd() ,
GWL_USERDATA, i);
// Формируем новый заголовок окна
sprintf(szNameWnd, "Окно %d", i ) ;
apPopupWnd[i]->SetWindowTex~(szNameWndi);
// Создаем перекрывающееся окно
apPopupWnd[4] = new CPopupWnd;
apPopupWnd[4]->Create(NULL, NULL, 0, rectDefaulr) ; ::SetWindowLong(apPopupWnd[i]->GetSafeKwnd(),
GWLJJSERDATA, 4);
sprintf(szNameWnd, "Окно %d", i);
apPopupWnd[4]->SetWindowText(szNameWnd) ;
// Устанавливаем новый курсор для класса всплывающих окон ::SetClassLong(apPopupWnd[0]->GetSafeHwnd. (), GCL_HCURSOR,
(LONG}AfxGerApp(i->LoadStandardCursor(IDC_UPARROW));
// Создаем и устанавливаем новый цвет фона для всплывающих окон
HBRUSH hNewBrush = ::CreareSolidBrush;(RG3(255, 255, 220)); ::SetClassLong(apPopupWnd[3]->GetSafeHwnd(),
GCL_HBRBACKGROUND, (LONG ) hNewBrush) ; .
// Отображение всплывающих окон
ford = 0; i < 5; i++)
{
apPopupWnd[i]->ShowWindow(SW_3HOWNORMAL);
apPopupWnd Гi]->UpdateWindcw () ;
}
}
Как видите, при ближайшем рассмотрении это не более, чем создание десяти окон, принадлежащих двум новым классам.
В основном процесс создания окон обоих типов стандартен: создаются объекты классов, после чего вызывается некоторая функция для создания окна Windows. Чтобы продемонстрировать как можно больше предоставляемых библиотекой возможностей, в текст программы включены дополнительные строки. Но обо всем по порядку. Начнем с дочерних окон, имеющих установленный бит стиля WS_CHILD (процесс создания объекта класса ничем не отличается от уже рассмотренных, и мы больше не будем останавливаться на этом вопросе). Итак, вызов функции CWnd::Create;
apChildWnd[i]->Create(NULL, NULL, WS_VISIBLE,
CRect(0, 0, 0, 0}, this, (UINT)i);
Подчеркнем некоторые моменты. Во-первых, не определен класс Windows, к которому относится окно, — эту задачу мы переложили на плечи библиотеки. Во-вторых, установив бит стиля WS_VISIBLE, мы задали режим, когда окно сразу же после создания выводится на экран. В-третьих, окно мы превратили в точку и поместили его в левый верхний угол экрана. В-четвертых, окно имеет "родителя" — пятый параметр определен как this. И. наконец, в качестве последнего параметра мы передаем номер создаваемого окна. Как и при создании главного окна, мы переопределяем функцию
BOOL CChildWnd::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style i= adwStyles[nCount];
sprintf (szNameWnd, "Окно *d", nCount!; cs.ipszName = szNameWnd;
return CWnd::PreCreateWindow(cs);
}
и функцию-обработчик сообщения WM_CREATE
int CChildWnd::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
if (CWnd::OnCreate(IpCreateStruct) == -1)
return -1;
MoveWindow(nCount++ * 118, 24, 116, 96);
return 0;
}
Первая заполняет поля style и IpszName структуры CREATESTRUCT соответственно значениями стиля и имени конкретного окна, а вторая — устанавливает размер и положение вновь созданного окна (напомним, что мы задали окну нулевые размеры). Со структурой CREATESTRUCT мы уже познакомились, поэтому рассмотрим изящную функцию CWnd::Move\Window, которая реализована в двух вариантах:
void CWnd::MoveWindow (
int x,
int. y,
int nWidth,
int nHeight,
BOOL bRepaint = TRUE)
и
void CWnd::MoveWindow(
LPCRECT IpRect,
BOOL bRepaint = TRUE)
С помощью любой из этих реализаций можно одновременно изменить размер и положение окна, задаваемые в параметрах х, у, nWidth, nHeight или IpRect. Последний параметр функции определяет, будет ли окно перерисовываться после перемещения. Если этот параметр равен TRUE (по умолчанию), то объект "окно" получает сообщение Windows WM_PAINT. Кроме того, функция посылает еще и сообщение WM_GETMINMAXINFO, которое вы также можете обработать.
Примечание
Здесь необходимо отметить, что позиция дочернего окна определяется относительно координат его владельца, в то время как для всплывающего окна положение задается относительно экрана.
Как мы уже отметили, задача регистрации оконного класса была поручена библиотеке. Нам лишь остается "подправить" некоторые поля в соответствии с собственными нуждами. Один из возможных путей состоит в применении функций API SetClassLong. GetClassLong, SetWindowLong и GetWindowLong. Возможно, вы захотите использовать их в своих программах, поэтому рассмотрим эти функции подробнее.
DWORD SetClassLong (
HWND hWnd,
int nlndex,
LONG dwNewLong)
Эта функция устанавливает в структуре WNDCLASS, описывающей оконный класс для hWnd, новое значение dwNewLong в поле, определяемом параметром nlndex, который может принимать одно из следующих значений:
GCL_CBCLSEXTRA
Устанавливает размер (в байтах) дополнительной памяти, предназначенной для использования всеми окнами, создаваемыми на базе этого класса; размер памяти, уже ассоциированной с классом, при этом не изменяется
GCL_CBWNDEXTRA
Устанавливает размер (в байтах) дополнительной памяти, предназначенной для использования отдельно каждым окном, создаваемым на базе этого класса; размер памяти, уже ассоциированной с окном, при этом не изменяется
GCL_HBRBACKGROUND
Устанавливает дескриптор кисти для фона, ассоциированного с этим классом
GCL_HCURSOR
Устанавливает дескриптор курсора, ассоциированного с этим классом
GCL_HICON
Устанавливает дескриптор пиктограммы, ассоциированной с этим классом
GCL_HMODULE
Устанавливает дескриптор модуля, который зарегистрировал этот класс
GCL_MENUNAME
Устанавливает адрес строки меню, которая идентифицирует ресурс меню, ассоциированного с этим классом
GCL_STYLE
Устанавливает биты стиля оконного класса GCL_WNDPROC Устанавливает адрес оконной процедуры, ассоциированной с классом
При успешном выполнении функция возвращает значение указанного слова из структуры класса окна или нулевое значение — в случае ошибки. Дополнительную информацию об ошибке можно получить, вызвав функцию API GetLastError.
Мы воспользовались функцией SetClassLong для того, чтобы установить новые значения для курсора и цвета фона.
Обратную задачу — получение информации из структуры класса окна — выполняет функция GetClassLong.
DWORD GetClassLong (
HWND hWna,
int nIndex).
Для работы с дополнительной памятью в структуре окна интерфейс API предоставляет также две функции:
DWORD SetWindowLong (
HWND hWnd,
int nlndex,
LONG dwNewLong)
и
DWORD GetWindowLong (
HWND hWnd,
int nlndex)
По принципу действия эти функции аналогичны только что рассмотренным, но работают с дополнительной памятью в структуре окна. Параметр nIndex при этом может принимать следующие значения:
GWL_EXSTYLE
Устанавливает новый расширенный стиль окна
GWL_STYLE
Устанавливает новый стиль окна
GWL_WNDPROC
Устанавливает новый адрес оконной процедуры
GWL_HINSTANCE
Устанавливает новый дескриптор приложения
GWL_ID
Устанавливает новый идентификатор окна
GWL_USERDATA
Устанавливает 32-разрядное значение, ассоциированное с конкретным окном и предназначенное для использования приложением, которым это окно создано
Если дескриптор hWnd определяет блок диалога, то доступными являются следующие значения:
DWL_DLGPROC
Устанавливает новый адрес процедуры блока диалога
DWL_MSGRESULT
Устанавливает значение, возвращаемое при обработке сообщения в процедуре блока диалога
DWL_USER
Устанавливает новую дополнительную информацию для приложения, напр., идентификатор или указатель
Для функции GetWindowLong, кроме перечисленных, определено еще одно доступное значение:
GWL_HWNDPARENT
Возвращает дескриптор родительского окна или NULL, если его нет
Мы использовали функцию SetWindowLong для записи в дополнительную память номера созданного окна. Другими словами, у первого окна в этом поле будет 0, у второго — 1, у третьего — 2 и т. д. Позже, во время изображения окон на экране, программа воспользуется этими значениями для идентификации окон.
Перед тем как закончить обсуждение вопроса об изменении характеристик оконных классов и собственно окон, осталось отметить способ получения дескриптора окна Windows. Для этой цели в классе CWnd предусмотрена специальная функция
HWND CWnd::GetSafeWnd ()
Эта функция позволяет получить дескриптор окна Windows, хранящийся в компоненте класса CWnd::m_hWnd, или NULL, если к объекту не присоединено никакое окно.
Но вернемся к нашей программе. Нам осталось разобраться в создании всплывающих окон. Обратите внимание на два несколько отличающихся вызова функции CFrameWnd::Create:
apPopupwnd[i]->Create(NULL, NULL, 0, rectDefault, this);
и
apPopupWnd[4]->Create(NULL, NULL, 0, rectDefault);
и связанное с этим отличие в поведении всплывающих окон 3 и 4. Для четвертого окна мы не указали родителя, и поэтому оно ведет себя достаточно независимо от нашего главного окна.
Второй момент, на который хотелось бы обратить ваше внимание, связан с еще одним способом изменения стилей созданных окон. Обратимся к тексту:
int CPopupWnd::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
ModifyStyle((DWORD)-1, adwStyles[nCount] );
...
return 0;
}
Как видите, для установки стиля окна мы воспользовались функцией класса CWnd — ModifyStyle:
BOOL CWnd::MbdifyStyle (
DWORD dwRemove,
DWORD dwAdd,
UINT nFlags = 0)
Эта функция позволяет удалить (dwRemove) или установить (dwAdd) определенные атрибуты стиля окна. Если установлен параметр nFlags, то вызывается функция API ::SetWindowPos с этим параметром, являющимся комбинацией одного или нескольких значений: SWP_NOMOVE, SWP_NOACTIVATE, SWP_NOSIZE и SWP_NOZORDER.
Для изменения флагов расширенного стиля можно использовать функцию Modify Style Ex.
BOOL CWnd::MbdifyStyleEx (
DWORD dwRemove,
DWORD dwAdd,
UINT nFlags = 0)
Попутно упомянем две функции, которые позволяют узнать текущие обычный и расширенный стили окна:
DWORD CWnd::GetStyle ()
и
DWORD CWnd::GetExStyle ()
При рассмотрении вопроса о создании главного окна приложения мы обошли молчанием конструктор и деструктор объекта этого окна. Вернемся теперь к их содержимому:
CMainFrame: : CMainFrame ()
{
forfint 1=0; i < 5; i++)
{
apChildWndfi] = NULL;
apPopupWnd[i] = NULL;
}
}
CMainFrame::~CMainFrame()
{
for(int i=0; i < 5; i++)
if(apChildWnd[i] != NULL)
delete apChildWnd[i];
}
}
Как видите, мы явно освобождаем только ту память, которую занимали объекты, образованные из класса, производного от CWnd. При использовании
в качестве базового других классов (в нашем случае CFrameWnd) явно освобождать память не надо, т. к. объекты выполняют это сами.
Мы создали главное окно приложения Styles таким образом, что пользователь не может изменить его размеры. Более того, окно не изменит своих размеров даже тогда, когда вы попытаетесь максимизировать его. Такое поведение окна достигается введением соответствующей обработки сообщения Windows WM_GETMINMAX1NFO, которая выполняется обработчиком:
void CMainFrame::OnGetMinMaxInfо(MINMAXINFO FAR* IpMMI)
}
int :x = : :GetSystemMetrics (SM CXSCREEN) / 8;
int ey = :: GetSystemMetrics (SM_CYSCREEN)/4;
lpMMI->ptMinTrackSize.x = 6*cx;
lpMMI->ptMinTrackSize.y = 2*cy;
lpMMI->ptMaxTrackSize.x = 6*cx;
lpMMI->ptMaxTrackSize.y = 2*cy;
lpKMI->ptMaxPosition.x = Cx;
lpMMI->ptMaxPosition.y = cy;
CFrameWnd::OnGetMinMaxInfо(IpMMI);
}
Обработчику в качестве параметра передается указатель на структуру типа MINMAXINFO, которая содержит информацию о максимально возможных размерах и позиции окна, а также о минимальных и максимальных изменяемых размерах:
typedef struct tagMINMAXINFO (
POINT ptReserved;
POINT ptMaxSize;
POINT ptMaxPosition;
POINT ptMinTrackSize;
POINT ptMaxTrackSize;
} MINMAXINFO;
Поля этой структуры имеют следующие назначения: ptReserved— зарезервировано для внутреннего использования; ptMaxSize— устанавливает размеры окна, которые оно может иметь в развернутом виде; ptMaxPosition — устанавливает значение, которое должен иметь левый верхний угол развернутого окна; ptMinTrackSize— устанавливает минимальные размеры, до которых пользователь может сжать границы окна; ptMaxTrackSize— устанавливает максимальные размеры, до которых пользователь может растянуть границы окна.
Как видите, все очень просто. Последнее, что нам осталось рассмотреть — каким образом можно получить информацию о системных параметрах.
Эта функция позволяет получить размеры (высоту и ширину) изображаемы* элементов Windows, а также другую информацию. Все размеры даются в пикселах.
int GetSystemMetrics (int nlndex)
Значение nIndex определяет параметр, который вы хотите получить (возвращаемые функцией значения могут изменяться в зависимости от типа используемого дисплея и установок пользователя). Приведем небольшой список наиболее часто используемых параметров:
SM_CMOUSEBUTTONS
Число кнопок мыши; если мышь не установлена, то возвращается 0
SM_CXDORDER, SM_CYDORDER
Ширина и высота рамки окна
SM_CXFULLSCREEN, SM_CYFULLSCREEN
Ширина и высота рабочей области максимизированного окна
SM_CXICON, SM_CYICON
Ширина и высота большой пиктограммы, заданные по умолчанию; обычно они равны 32, но могут изменяться в зависимости от установок пользователя
SM_CXMIN, SM_CYMIN
Минимальные допустимые ширина и высота окна
SM_CXMINTRACK, SM_CYMINTRACK
Минимальные величины, до которых пользователь может уменьшить ширину и высоту окна; для их изменения необходимо обработать сообщение Windows WM_GETMINMAXINFO
SM_CXSCREEN, SM_CYSCREEN
Ширина и высота экрана
SM_CYCAPTION
Высота области заголовка окон
SM_CYMENU
Высота полосы меню
SM_SWAPBUTTON
He равен 0, если левая и правая кнопки мыши обменялись своими функциями
SM_CXEDGE, SM_CYEDGE
Горизонтальный и вертикальный размеры трехмерной рамки
SM_CXFIXEDFRAME, SM_CYFIXEDFRAME
Толщина (по горизонтали и вертикали) рамки окна, которое имеет заголовок, но не может изменить свои размеры
SM_CXMAXIMIZED, SM_CYMAXIMIZED
Предельно допустимые размеры развернутого окна
SM_CXMAXTRACK, SM_CYMAXTRACK
Максимальные величины, до которых пользователь может увеличить ширину и высоту окна; для их изменения необходимо обработать сообщение Windows WM_GETMINMAXINFO
SM_CXMINIMIZED, SM_CYMINIMIZED
Предельно допустимые размеры свернутого окна
SM_CXSIZEFRAME, SM_CYSIZEFRAME
Толщина (по горизонтали и вертикали) рамки окна, которое может изменять свои размеры
SM_CXSMICON, SM_CYSMICON
Рекомендуемые размеры маленькой пиктограммы, которая обычно используется в заголовке окна
SM_CYSMCAPTION
Высота области уменьшенного заголовка
SM_NETWORK
В возвращаемом значении установлен самый младший бит, если сеть присутствует; остальные биты зарезервированы для будущего использования
SM_SECURE
Функция возвращает ненулевое значение, если установлена защита, и 0 — в противном случае
Большинство из стилей окна, использованных в приложении Styles, достаточно ясны. Поэтому мы остановимся только на одном из них — WS_DISABLED. Окна, имеющие такой стиль, абсолютно неподвижны, и ими нельзя манипулировать ни с помощью мыши, ни с помощью клавиатуры. Обратите также внимание, что у такого окна и курсор не соответствует заданному в оконном классе. Для доступа к этому свойству окон в классе CWnd реализована специальная функция
BGOL CWnd::EnableWindow (BOOL bEnable = TRUE)
Разрешает (bEnable = TRUE) или запрещает (bEnable = FALSE) доступ к окну для мыши и клавиатуры. Если при вызове функции изменилось состояние окна, то до возврата из нее Windows посылает окну сообщение WM_ENABLE.
Для того чтобы узнать текущее состояние окна в смысле его доступности, можно использовать функцию
BOOL CWnd::IsWindowEnable ()
Возвращает значение TRUE, если окно доступно, и FALSE — в противном случае.
Основная цель этой главы и рассмотренного в ней приложения — знакомство с основными классами библиотеки MFC, отвечающими за создание и работу практически всех типов окон Windows. Исключение составляют окна так называемых MDI-приложений — приложений многодокументного (или многооконного) интерфейса, которые мы рассмотрим несколько позже.
Здесь я попытался максимально подробно рассмотреть основные аспекты программирования для Windows с использованием библиотеки классов MFC
и показать, что слухи о сложности этого процесса "несколько преувеличены". Разобравшись в основах, изложенных в данной главе, вы уже прошли большую часть пути: программирование для Windows — это прежде всего работа с окнами.
Единственный механизм, которым мы достаточно активно пользовались, но о котором мало что сказали — работа с сообщениями. Поэтому, на мой взгляд, будет разумно отвлечься на этот чрезвычайно важный вопрос и рассмотреть его более глубоко и "теоретизированно".