<< Назад Вернуться к списку Дальше >>

6. Написание первой программы на WinAPI. Указатели на функции. Сообщения Windows. План. 1. Первая нормальная программа на WinAPI. 2. Структуры. 3. Указатели на функции. Callback функции. 4. Сообщения в Windows. 5. Петля сообщений. Очередь сообщений. 6. Обработчики сообщений. В прошлый раз мы написали программу под windows с одним окном, но это окно было стандартным окном сообщения. Пришла пора написать программу с окном, определенным самостоятельно. Сперва напишем программу, а потом разберем, что же она делает. // тут был текст программы... Скачать рукописный вариант (271 Kb) Прежде чем приступить к разбору программы необходимо обсудить некоторые не рассмотренные ранее особенности языка С++. В прошлый раз мы научились определять свои типы данных. Другим методом определения типа данных является определение структуры. Структура - это набор параметров, характеризующих один объект, собранных в одном месте. Например, точка. Ее описание должно содержать ее координаты и цвет. Вот как пишется описание структуры: struct point { int x; int y; int z; int color; }; После этого описания можно использовать тип point для создания и использования переменных: point a; a.x = 10; a.y = 20; a.z = 30; a.color = 123; Также переменные этого типа можно передавать в функции: func(a); Но при этом эта функция должна иметь такой прототип: void func(point a); В записанной программе используется несколько структур Windows. Это PAINTSTRUCT, WNDCLASSEX и MSG. Мы рассмотрим из чуть-чуть попозже. Кроме того, типом также является описание функции. Сразу приведу пример, функции: int func1(int a, char b); int func2(int c, char d); int func3(int b, char a); являются переменными одного типа. От чего зависит тип функции: 1. От типа возвращаемого значения. 2. От количества входных параметров. 3. От типов входных параметров. 4. От порядка типов входных параметров. От чего не зависит тип функции: 1. От названия функции (это всего лишь название переменной этого типа). 2. От имен входных параметров (это названия переменных, используемых в теле функции, в которые записываются значения входных параметров). Из-за второго пункта прототипы функций часто записывают так: int func(int, int); Таким образом, в программе могут быть определены несколько функций одного типа. Тогда можно ввести тип указателя на функцию. Переменная этого типа будет содержать адрес функции. Описание типа указателя на функцию: int (*PFunc)(int, char); // указатель на функцию, которая принимает целое число и символ и возвращает целое число Теперь у нас есть тип PFunc и можно создавать переменные этого типа: PFunc f1, f2, f3; и присваивать им значения: f2 = func2; f3 = func3; // эти функции определены выше f1 = f2; теперь f1 и f2 стали как бы другими именами функции func2, а f3 - функции func3. Т.е. можно их использовать: int result = f1( 12, 'a'); // вызовется функция func2 Где это используется в нашей программе? Строка: winclass.lpfnWndProc = WindowProc; В ней в поле lpfnWndProc структуры winclass pзаписывается адрес функции WindowProc. Непосредственно в нашей программе вызова этой функции через этот указатель не происходит, Windows делает это автоматически в необходимый ей момент. Мы же только предоставляем ей возможность для этого. Подобные функции часто используются в программировании для Windows и называются callback функциями. Теперь можно приступить к разбору программы. Повторю, что программы для Windows начинаются с выполнения функции WinMain. C нее мы и начнем изучение нашей программы. В начеле создаются переменные winclass и msg, являющиеся стуктурами, и hWnd - дескриптор окна. Затем задаются поля структуры winclass. В этой структуре 12 полей и все их надо задать: winclass.cbSize = sizeof(WNDCLASSEX); // размер структуры в байтах winclass.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; // стили окна winclass.lpfnWndProc = WindowProc; // указатель на функцию обработчика сообщений winclass.cbClsExtra = 0; // дополнительная память для класса winclass.cbWndExtra = 0; // дополнительная память для окна winclass.hInstance = hInst; // дескриптор приложения, ресурсы которого используются winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // большая иконка для окна winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // маленькая иконка для окна winclass.hCursor = LoadCursor(NULL, IDC_ARROW); // курсор, который будет отображаться в окне winclass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); // цвет фона winclass.lpszMenuName = NULL; // меню winclass.lpszClassName = WINDOW_CLASS_NAME; // название класса окна Данная структура описывает класс окна. Что это такое? Каждое окно в Windows принадлежит к какому-то классу окон. Окна из одного класса имеют одинаковый внешний вид и свойства. Существует много стандартных классов окон. Окно одиного из них мы делали в прошлый раз. Это было окно MessageBox-а. Для этих окон не надо создавать новый класс. Но сейчас мы хотим сделать окно, не похожее ни на какое другое. Для этого необходимо создать новый класс окон и зарегистрировать его в Windows. Этим мы сейчас и займемся. Стректура WINDOWCLASSEX полностью определяет класс окон. Задав поля экземпляра этой структуры мы можем зарегистрировать новый класс окна: if (!RegisterClassEx(&winclass)) return 0; Здесь вызывается функция RegisterClassEx, которая регистрирует новый класс окон и возвращает не ноль, если регистрация прошла успешно и 0 - если случилась какая-то ошибка. В случае ошибки программа наша выполняться дальше не может, поэтому надо прекратить ее выполнение. Если же все прошло хорошо, то теперь можно создавать окна этого класса. Для этого вызывается функция CreateWindowEx: hWnd = CreateWindowEx(NULL, WINDOW_CLASS_NAME, "My Window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 400, 400, NULL, NULL, hInst, NULL); Эта функция создает окно класса WINDOW_CLASS_NAME с заголовком "My Window", верхним левым углом в координатах (0,0) и размерами 400х400. Окно принадлежит к приложению с дескриптором hInst, т.е. к текущему приложению. Остальные параметры пока неважны. Если при создании окна произошла какая-то ошибка, то эта функция вернет 0. Тогда выполнение программы необходимо прекратить. Чтобы понять то, что написано дальше, надо немного отвлечься и поговорить о принципах работы всех Windows приложений. В отличие от консольных приложений, которые работают постоянно до тех пор, пока не сделают все что нужно, программы Windows все время ждут. Ждут каких-то действий пользователя, сообщений от операционной системы, сети ... Да и правильно, зачем что-то постоянно делать, если обычно надо только нарисовать окно и все, а когда его закрыли, нарисовать на его месте фон, что был за ним. Во время этого простоя процессор может быть занят чем-то другим. Таким образом, программа Windows постоянно чего-то ждет. Но как сообщить ей, что возникла необходимость в каких-то действиях? Для этого и существуют сообщения. Сообщения могут исходить из самых разных источников. Это могут быть сообщения клавиатуры, мыши, операционной системы, сети. Программист сам может определять свои сообщения. Теперь уже программа ждет не просто так, она ждет возникновения какого-нибудь сообщени. И как только оно произошло, она делает соответствующие ему действия и опять переходит в режим ожидания. Но как быть, если несколько сообщений пришло практически одновременно? Чтобы ни одно сообщение не пропало все они помещаются в очередь сообщений (аналогия с очередью за колбасой), а затем по одному берутся из нее и обрабатываются. Эти действия и представлены в заключительных строках функции WimMain: while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } Функция GetMessage проверяет, есть ли в очереди необработанное сообщение. Как только оно появляется функция GetMessage заполняет структуру msg и завершается. Затем начинается выполнение цикла. TranslateMessage(&msg); DispatchMessage(&msg); Первая функция производит проверку того, не нажаты ли "быстрые" клавиши. После чего запускается функция DispatchMessage, которая выполняет всю работу по обработке сообщения. В частности, она проверяет, какому окну приложения пришло сообщение, находит описание класса этого окна и в нем узнает указатель на функцию, которая должна обрабатывать сообщения, приходящие к этому окну. В нашем случае это функция WindowProc. Этот цикл сообщения также называется петлей сообщений. Пока он выглядит бесконечным. Но существует сообщение, при получении которого функция GetMessage возвращает 0, после чего цикл завершается. Это сообщение приходит после закрытия окна приложения. Теперь рассмотрим функцию WindowProc. Она получает четыре параметра: hWnd - дескриптор окна, которому пришло сообщение msg - идентфикатор сообщения, по этому числу можно понять, что же все-таки произошло wParam - дополнительный параметр lParam - второй дополнительный параметр Дополнительные параметры содержат в себе дополнительную информацию о сообщении. Например, если это сообщение о перемещении мышки, то там хранятся ее новые координаты. Вся функция WindowProc состоит из большого switch, в котором перебираются номера сообщений и, если найдено то которое произошло, то выполняются соответствующие действия. В нашей программе обрабатываются только три сообщения, заданные константами WM_CREATE, WM_PAINT и WM_DESTROY. (название сообщений составляются из префикса WM_ - window message и типа сообщения). WM_CREATE - приходит перед созданием окна WM_PAINT - приходит, когда необходимо перерисовать содержимое окна WM_DESTROY - приходит после уничножения окна На самом деле видов сообщений гораздо больше, чем три. Их несколько сотен. Но нет необходимости обрабатывать их все. Существует стандантный обработчик сообщений, который делает какие-то стандартные операции для каждого сообщения. Это функция DefWindowProc с тем же набором параметров, что и WindowProc. Самостоятельно надо описывать только реакции на те события, которые должны отличаться от стандартного поведения. Это и происходит в нашей программе - если msg имеет значение одно из трех перечисленных выше, то выполняются соответствующие действия, после чего делается return 0, т.е. выход из функции. Если же msg не равно ни одной из этих констант, то вызывается функция DefWindowProc, которая выполняет стандартные действия. Подробности обработки сообщений и вызываемые для этого функции мы обсудим в следующий раз. В итоге же у нас должно получиться окно в левом верхнем углу экрана с черным фоном и заголовком "My Window".

<< Назад Вернуться к списку Дальше >>

Хостинг от uCoz