пользователей: 30398
предметов: 12406
вопросов: 234839
Конспект-online
РЕГИСТРАЦИЯ ЭКСКУРСИЯ

Обработка сообщений. Карты сообщений.

Обработка сообщений. Циклы обработки сообщений

Если и существует некоторая особенность, отличающая программирование в Windows от других областей программирования, то это сообщения. Сообщения являются тем средством, с помощью которого операционная система может дать знать приложению, что что-то произошло, например пользователь нажал клавишу на клавиатуре или щелкнул кнопкой мыши, или передвинул мышь, или подготовил принтер к выводу информации. Окно, а каждый информационный элемент на экране есть своего рода окно, также может посылать сообщения другому окну и, как правило, большинство окон реагирует на полученное сообщение тем, что пересылает его дальше, третьему окну, слегка видоизменив. Значительную помощь в организации работы с сообщениями оказывает MFC, скрывая от программиста многие подробности процесса.

Хотя операционная система и использует целые числа для идентификации событий, в тексте программы мы будем иметь дело с символьными идентификаторами. Огромное количество директив #define связывает символьные идентификаторы с соответствующими числами и позволяет программистам в разговоре между собой в кругу посвященных манипулировать словечками вроде WM_PAINT и WM_SIZE. Префикс WM означает Window Message (сообщение Windows). Фрагмент перечня сообщений представлен в листинге 3.1.

#define WM_SETFOCUS                     0x0007

#define WM_KILLFOCUS                    0x0008

#define WM_ENABLE                       0x000A

#define WM_SETREDRAW                    0x000B

#define WM_SETTEXT                      0x000C

#define WM_GETTEXT                      0x000D

#define WM_GETTEXTLENGTH                0x000E

#define WM_PAINT                        0x000F

#define WM_CLOSE                        0x0010

#define WM_QUERYENDSESSION              0x0011

#define WM_QUIT                         0x0012

#define WM_QUERYOPEN                    0x0013

#define WM_ERASEBKGND                   0x0014

#define WM_SYSCOLORCHANGE               0x0015

#define WM_ENDSESSION                   0x0016

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

Обработка разных сообщений выполняется разными компонентами операционной системы и приложения. Например, когда пользователь передвигает мышь по полю окна, формируется сообщение WM_MOUSEMOVE, которое передается окну, а окно, в свою очередь, передает это сообщение операционной системе. И уже последняя перерисовывает указатель мыши в новом месте. Когда пользователь щелкает левой кнопкой мыши на экранной кнопке, кнопка, которая также есть особый вид окна, получает сообщение WM_LBUTTONDOWN. В процессе обработки этого сообщения кнопка часто формирует новое сообщение для окна, в котором она находится, причем это сообщение гласит: "Ой, на мне щелкнули!".

Библиотека MFC позволяет программистам в подавляющем большинстве случаев полностью отстраниться от сообщений нижнего уровня, таких как WM_MOUSEMOVE и WM_LBUTTONOOWN. Программист может полностью сосредоточиться на сообщениях более высокого уровня, которые гласят что-нибудь вроде "Выбран третий элемент такого-то списка" или "Произошел щелчок на кнопке Move". Поступают такого рода сообщения в те программы, которые пишет программист, и в компоненты операционной системы точно так же, как и сообщения нижнего уровня. Единственная разница в том, что MFC берет на себя значительную часть работы по обработке сообщений низкого уровня и позволяет заметно облегчить распределение сообщений между разными классами объектов, на уровне которых и будет производиться их обработка.

Циклы обработки сообщений

Сердцем любой Windows-программы является цикл обработки сообщений (Message Loop), который практически всегда находится в функции WinMain(). Эта функция в Windows-приложениях играет ту же роль, что и функция Main () в DOS-приложениях,— ее вызывает операционная система сразу же после загрузки приложения в память. Текст типичной функции WinMain() Представлен в листинге 3.2.

Листинг 3.2.Tипичнaя функцияWinMain()

int АРIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR IpCmdLine,

int nCmdShow) зн

MSG   msg;

if( !InitAppIication( hInstance))

return (FALSE);

if( !Initlnstance( hInstance,nCmdShow))

return (FALSE);

while( GetMessage( &msg, NULL, 0, 0)) зн

TranslateMessage( &msg);

DispatchMessage( &msg):

зн return (msg.wParam);зн

В С-программах для Windows, похожих на эту, функция InitApplication() вызывает RegisterWindow(), a InitInstance() — CreateWindow(). Затем наступает очередь цикла обработки сообщений. Он представляет собой типичную циклическую конструкцию С на базе оператора whi1е, внутри которой вызывается функция GetMessage(). Эта функция API заполняет msg кодом сообщения, которое операционная система распределила для этого приложения, и почти всегда возвращает TRUE. Таким образом, цикл повторяется снова и снова до тех пор, пока работает приложение. Единственный вариант, при котором GetMessage() возвращает FALSE, — получение сообщения WM_QUIT.

При работе с сообщениями, поступающими с клавиатуры, некоторую часть предварительной обработки берет на себя функция API TranslateMessage(). Ее назначение состоит в следующем. Прикладной части программы нет дела до сообщений наподобие "Нажата клавиша <А>" и «Отпущена клавиша <А>". Прикладную часть интересует только то, какую литеру (символ) ввел пользователь, т.е. ее вполне удовлетворит сообщение "Введен символ А". Вот это преобразование — нескольких сообщений о деталях процесса в одно сообщение о его сути — и выполняет функция TranslateMessage(). Она перехватывает сообщения WM_KEYDOWN и WM_KEYUP и вместо них посылает сообщение WM_CHAR. Если пользоваться библиотекой MFC, то такие мелочи, как ввод символа А, проходят, как правило, мимо вас. Пользователь вводит текст в текстовое поле или в другой элемент управления, и забота программиста— извлечь введенный текст из этого объекта после того, как пользователь щелкнет на ОК.

Функция API DispatchMessage() вызывает, в свою очередь, функцию WndProc() того окна, для которого предназначено сообщение. Типичная функция WndProc() в С-программе для Windows представляет собой огромный оператор switch с отдельными case для каждого сообщения, которое приложение намеревается самостоятельно обрабатывать. Текст ее приведен в листинге 3.3.

Листинг 3.3. Типичная функция WndProc()

LONG APIENTRY MainWndProc( HWND hwnd,   // Дескриптоо окна.

UINT msg,       //Тип сообщения.

UINT wParam,    //' Дополнительная информация.

LONG IParam)    //Дополнительная информация.

зн

switch(msg) зн

case WM_MOUSEMOVE:

// Обработка перемещения мыши.

break;

case WM_LBUTTONDOWN:

// Обработка щелчка левой кнопки мыши.

break;

case WM_ RBUTTONDOWN:

// Обработка щелчка правой кнопки мыши.

break;

case WM_PAINT :

/,/ Перерисовать окно. break;

case WM_DESTROY :   // Сообщение: окно будет уничтожено.

PostQuitMessage( 0);

return 0;

break;

defauIt:

return (DefWindowProc( hwnd, msg, wParam, IParam));зн

return (0);зн

Вы, конечно, можете себе представить, какой длины достигает подобная функция в более или менее порядочном приложении. MFC решает проблему следующим образом—информация о сообщениях, которые должны обрабатываться, расположена поближе к функциям. которые и должны выполнять обработку. Таким образом, отпадает необходимость в огромных операторах switch, в которых сосредоточено распределение сообщений.

Карты сообщений

Использование карты сообщений (Message maps) лежит в основе подхода, который реализуется в MFC для программирования Windows-приложений. Суть его состоит в том, что от разработчика требуется только написать функции обработки сообщений и включить в свой класс карту сообщений, которая фактически скажет: "Я буду обрабатывать такое-то сообщение". После этого главная программа будет отвечать за то, чтобы сообщение было передано именно той функции, которая будет его обрабатывать.

Карта сообщений состоит из двух частей: одна— в файле заголовка для класса .h, а другая — в соответствующем файле реализации .срр. Они, как правило, формируются мастерами, хотя в некоторых случаях вы можете сделать это (или частично отредактировать их) и самостоятельно. В листинге 3.4 представлена часть текста файла заголовка одного из классов простого приложения ShowString.

Листинг 3.4. Карта сообщений из файла ShowString h

//2зн AFX_MSG(CShowStringApp)

arx_msg void OnAppAbout();

// ВНИМАНИЕ!! Здесь ClassWizard будет добавлять и

// удалять функции-члены.

//НЕ РЕДАКТИРУЙТЕ текст в этих блоках!

//2знAFX_MSG

DECLARE_MESSAGE_MAP()

Карта сообщений для приложения Employee класса CEmployeeView:

- В файле EmployeeView.h

//знзнAFX_MSG(CEmployeeView)

          afx_msg void OnRecordAdd();

          afx_msg void OnRecordDelete();

          afx_msg void OnSortId();

          afx_msg void OnSortName();

          afx_msg void OnSortRate();

          afx_msg void OnSortDepartment();

          afx_msg void OnFilterDepartment();

          afx_msg void OnFilterId();

          afx_msg void OnFilterName();

          afx_msg void OnFilterRate();

          afx_msg void OnFileSaveAs();

          afx_msg void OnRecordFind();

          //знзнAFX_MSG

          DECLARE_MESSAGE_MAP()

 

Здесь объявляется функция OnAppAbout(). Специальным образом оформленный комментарий позволяет ClassWizard определить, какие именно сообщения перехватываются этим классом. DECLARE_MESSAGE_MAP — это макрос, расширяемый препроцессором компилятора Visual C++, в котором объявляются переменные и функции, принимающие участие в этом фокусе с перехватом сообщений.

Карта сообщений в файле . срр, как показано в листинге 3.5, также достаточно проста.

 

BEGIN_MESSAGE_MAP(CShowStringApp, CwinApp)

//знзнAFX_MSG_MAP(CshowStringApp)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

//знзнAFX_MSG_MAP

// Стандартные команды для файловых документов.

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

// Стандартные команды настройки принтера.

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()

Карта сообщений для приложения Employee класса CEmployeeView:

- в файле EmployeeView.cpp

 

BEGIN_MESSAGE_MAP(CEmployeeView, CRecordView)

          //знзнAFX_MSG_MAP(CEmployeeView)

          ON_COMMAND(ID_RECORD_ADD, OnRecordAdd)

          ON_COMMAND(ID_RECORD_DELETE, OnRecordDelete)

          ON_COMMAND(ID_SORT_ID, OnSortId)

          ON_COMMAND(ID_SORT_NAME, OnSortName)

          ON_COMMAND(ID_SORT_RATE, OnSortRate)

          ON_COMMAND(ID_SORT_DEPARTMENT, OnSortDepartment)

          ON_COMMAND(ID_FILTER_DEPARTMENT, OnFilterDepartment)

          ON_COMMAND(ID_FILTER_ID, OnFilterId)

          ON_COMMAND(ID_FILTER_NAME, OnFilterName)

          ON_COMMAND(ID_FILTER_RATE, OnFilterRate)

          ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs)

          ON_COMMAND(ID_RECORD_FIND, OnRecordFind)

          //знзнAFX_MSG_MAP

END_MESSAGE_MAP()

 

Некоторые макросы карты сообщений

Макросы BEGIN_MESSAGE_MAP и END_MESSAGE_MAP, так же как DECLARE_MESSAGE_MAP в файле заголовка, объявляют члены (переменные и функции), которые программа должна использовать для того, чтобы разобраться в картах всех объектов системы. Существует довольно большой набор макросов, используемых для работы с картой сообщений. Некоторые из них перечислены ниже.

•DECLARE_MESSAGE_MAP. Используется в файле заголовка для того, чтобы объявить, что в файл собственно текста программы будет включена карта сообщений.

•BEGIN_MESSAGE_MAP. Отмечает начало карты сообщений в тексте программы.

•END_MESSAGE_MAP. Отмечает конец карты сообщений в тексте программы.

•ON_COMMAND. Используется для того, чтобы перенаправить обработку некоторой команды функции-члену класса.

  • ON_CONTROL. Используется, для того, чтобы перенаправить обработку кода извещения от элемента управления, введенного программистом, функции-члену класса.

•ON_MESSAGE. Используется для того, чтобы перенаправить обработку некоторого сообщения, введенного программистом, функции-члену класса.

•ON_UPDATE_COMMAND_UI. Используется для того, чтобы перенаправить обновление, связанное с заданной командой, функции-члену класса.

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

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

Что происходит с картой сообщений

Компоненты, которые добавлены в карту сообщений файла заголовка, могут быть истолкованы следующим образом: "Существует функция OnAppAbout(), которая не имеет параметров". Компонент, который добавлен в собственно текст программы (файл .срр). означает: "Когда придет сообщение от команды ID_APP_ABOUT, вызовите OnAppAbout()".

Каждое приложение имеет объект, который является наследником класса CWinApp, и имеет функцию-член Run(). Эта функция обращается к CWinThread:: Run(), которая значительно длиннее, чем WinMain(), но имеет точно такой же цикл обработки сообщений — вызов GetMessage(), вызов TranslateMessage() и вызов DispatchMessage(). Почти все объекты-окна используют тот же самый класс окна, и ту же самую функцию WndProc(), но теперь названную AfxWndProc(). Функция WndProc(), как вы уже видели, знает дескриптор окна hWnd, для которого предназначено сообщение. Библиотека MFC, в свою очередь, содержит нечто, называемое картой дескрипторов (handle map), — таблицу дескрипторов окон и указателей объектов. Таким образом, главная программа может, используя всю эту информацию, найти указатель на объект cWnd*. Далее она вызывает WindowProc()— виртуальную функцию этого объекта. Кнопки или окна представления, естественно, имеют разные реализации этой функции, но волшебные свойства полиморфизма приводят к тому, что вызывается именно та реализация, которая нужна.

Функция WindowProc() вызывает OnWndMsg() — функцию C++, которая собственно и обрабатывает сообщения. Во-первых, она проверяет, что же это было — сообщение, команда или код извещения. Предположим, поступило сообщение. Тогда функция просматривает карту сообщений для своего класса, используя члены класса (переменные и функции), которые были установлены макросами BEGIN_MESSAGE_MAP, END_MESSAGE_MAP и DECLARE_MESSAGE_MAP. Помимо всего прочего, эти макросы организуют доступ к компонентам карты сообщений базового класса. Это означает, что если класс является производным от CView, но не перехватывает сообщений, которые обычно перехватываются базовым классом, то сообщение будет перехвачено функцией класса CView.

 

 


18.01.2020; 20:02
хиты: 46
рейтинг:0
для добавления комментариев необходимо авторизироваться.
  Copyright © 2013-2024. All Rights Reserved. помощь