Flying Cow
[ Новые сообщения · Участники · Правила форума · Поиск · RSS ]
  • Страница 1 из 1
  • 1
Форум » Создание игр посредством прямого программирования » Уроки по WinApi » Обработчик сообщений
Обработчик сообщений
NEWДата: Пятница, 01.07.2011, 18:04 | Сообщение # 1
Сержант
Группа: Администраторы
Сообщений: 20
Репутация: 0
Статус: Offline
В этом уроке вы узнаете более подробно, что такое обработчик сообщений и мы забьём функции для обработки некоторых из стандартных сообщений.
Для начала я расскажу, что это за обработчик сообщений и что он делает более подробно. В прошлом уроке я достаточно подробно рассказал о цикле обработке сообщений. Сейчас же я расскажу, что такое сообщение (знаю, надо было конечно в обратном порядке это делать, но в том уроке и так много материала оказалось).
У каждого приложения в Windows есть очередь сообщений. Сообщения - это некие команды, которые поступают от операционной систему к нашему приложению. При поступлении нового сообщения к вашему приложению оно заносится в эту очередь и по мере того, как для вашего приложения выделяется часть процессорного времени оно начинает обрабатывать эти сообщения. Одно такое сообщение мы разобрали на прошлом уроке - это сообщение выхода, которое посылается нашему приложению при закрытии окна. Надеюсь вы меня поняли, потому что особенно сильно на этом вопросе я останавливаться не хочу. Если нет, то ничего страшного, разберётесь по ходу дела.
Взглянем ещё раз на нашу функцию, которая занимается обработкой сообщений:
Code

//--------------------------------------------------
// Обработка сообщений
//--------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
  switch (message)  
  {
  // После закрытия окна
  case WM_DESTROY:
   PostQuitMessage(0);
   break;

  // События по умолчанию
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

Она принимает следующие аргументы:
HWND hWnd - дескриптор окна, которому адресовано текущее сообщение (ведь один и тот же обработчик сообщений можно назначить не для одного, а сразу для нескольких окон).
UINT message - идентификатор сообщения, которое поступило в данный момент.
WPARAM wParam, LPARAM lParam - некие параметры, которые передались вместе с сообщением.
В теле самой функции мы видим переключатель (switch) по переменной, которая хранит в себе идентификатор поступившего сообщения. В нём у нас пока записан обработчик только одного сообщения. Это сообщение, отправляемое системой сразу после уничтожения окна - WM_DESTROY. Для всех остальных сообщений вызывается системная функция DefWindowProc(), которое, как и говорится в её названии, обрабатывает все остальные сообщения по умолчанию.
Для каждого события в Windows определён свой макрос, создана своя константа. Их название начинается с приставки WM_. Давайте сделаем обработчик сообщения нажатия на клавиши. Этот обработчик будет выглядеть следующим образом:
Code

  // Обработка события нажатия на клавиши
  case WM_KEYDOWN:
   break;

Как вы видите мы использовали сообщения WM_KEYDOWN. Это сообщение посылается в том случае, если была нажата (именно нажата, сообщение отпускание нажатой клавиши - WM_KEYUP) какая-либо клавиша в тот момент когда наше окно было активно. Если бы в этот момент было активно не наше окно, а какое-нибудь другое, то это сообщение отправилось бы ему. Рассмотрим, как узнать, какая именно клавиша была нажата, ведь нам мало того, что какая-то клавиша на клавиатуре была нажата, нам нужно знать, что это за клавиша. Для этого служит аргумент wParam. В нём находится номер нажатой клавиши. То есть, что бы узнать какая клавиша была нажата достаточно считать значение из этой переменной. Казалось бы всё просто, но даже здесь есть свои хитрости. Одна из них заключается в том, что при нажатии не всех клавиш вызывается это сообщение. Я нашёл две такие клавиши, это Alt и F10. Почему это так я не знаю, но зато знаю как сделать, что бы можно было обработать нажатие и на эти клавиши:
Code

  // Обработка события нажатия на клавиши
  case WM_SYSKEYDOWN:
  case WM_KEYDOWN:
   break;

Как вы видите, обработка сообщения нажатия (а также отпускания - WM_SYSKEYUP) этих клавиш вынесена отдельно.Не будем гадать почему это так и зачем так сделано просто примем во внимание. Чтож, обработчик событий нажатия на клавиши мы сделали, рассмотрим 2 варианта проверки, какая клавиша будет нажата. 1 и наиболее простой, тривиальной и не всегда работающий так, как этого бы хотелось это прям там же, в обработке этого сообщения с помощью условного оператора (или опять же с помощью переключателя) сравнивать текущее значение с тем, которое мы бы хотели видеть. Например:
Code

   if ( wParam == VK_SPACE )
   {
    //.....
   }
   if ( wParam == 'L' )
   {
    //.....
   }
   switch (wParam)
   {
   case VK_RETURN:
    //......
    break;
   }

Как вы видите константы с идентификаторами клавиш начинаются с VK_, а для проверки нажатия какой-нибудь символьной клавиши надо задавать её прописной вариант на латинице. Ну как я сказал, это не самый удачный метод. Конечно для некоторых случаев его достаточно.
Рассмотрим недостатки этого метода. Ну один из них заключается в том, что мы можем проверить нажатие только одной клавиши, и причём последней. Допустим если вы зажмете например [Ctrl] и после зажмёте какую-нибудь клавишу, то аргумент wParam примет значение этой клавиши. И что самое неприятное, при отпускании одной из клавиш сообщение о нажатой клавиши больше не поступает, даже если оставить вторую нажатой.
Рассмотрим второй метод. Вся его хитрость заключается в том, что мы просто создаём массив логических переменных (bool). При нажатии клавиши элементу этого массива с индексом, равным идентификатору нажатой клавиши, присвоим значение true. А при отпускании соответственно false. Вот и вся хитрость. Объявим этот массив и запишем необходимый программный код в обработчик сообщений:
Code

//......

// Нажатые клавиши
bool  g_WKeys[256];

//......

  // Обработка события нажатия на клавиши
  case WM_SYSKEYDOWN:
  case WM_KEYDOWN:
   g_WKeys[wParam] = true;
   break;

  // Обработка события отпускания клавиши
  case WM_SYSKEYUP:
  case WM_KEYUP:
   g_WKeys[wParam] = false;
   break;

//......

Нам осталось только проверить значения в этом массиве. Для этого создадим функцию:
Code

//--------------------------------------------------
// Обработка событий нажатия на клавиши
//--------------------------------------------------
void KeyDown()
{
  // Нажатие на клавишу [ ]
  if ( g_WKeys[VK_SPACE] )
  {
   // Обновление сцены
   //UpdateScene();
   g_WKeys[VK_SPACE] = false;
  }

  // Нажатие на клавиши [Alt] + [Enter]
  if ( g_WKeys[VK_MENU] && g_WKeys[VK_RETURN] )
  {
         // Если окно максимизированно (развёрнуто)
   if ( IsZoomed( g_hWnd ) )
    // восстанавливаем до нормального размера
    ShowWindow( g_hWnd, SW_NORMAL );
   else
    // , иначе разворачиваем на весь экран
    ShowWindow( g_hWnd, SW_MAXIMIZE );
   g_WKeys[VK_MENU] = g_WKeys[VK_RETURN] = false;
  }

  // Нажатие на клавишу [Esc]
  if ( g_WKeys[VK_ESCAPE] )
  {
   // Выход из программы
   ShutDown();
   g_WKeys[VK_ESCAPE] = false;
  }
}

и поместим её вызов куда-нибудь, где она будет постоянно вызываться. Например в цикл обработки сообщений:
Code

  while( GetMessage( &msg, NULL, 0, 0 ) )
  {
   TranslateMessage( &msg );
   DispatchMessage( &msg );

   // Проверка нажатых клавиш
   KeyDown();
    
   // Обновление сцены
   //UpdateScene();
  }

Как вы заметили я сразу добавил обработку нажатий на некоторые клавиши: [Escape] - выход из программы, [Alt] + [Enter] - разворачивание окна на весь экран и восстановление обратно, а также [ ] ([Пробел]) - обновление сцены. Причём сама функция UpdateScene() присутствует в двух экземплярах (в обработчике нажатий клавиш и в цикле обработке сообщений) и в обоих случаях закоменчена. Я просто решил привести сразу готовый каркас, в котором практически нечего не понадобится менять в будущем, в основном добавлять код в имеющиеся функции (ещё несколько будут приведены дальше). А в функции UpdateScene() всегда будут идти какие-то вычисления и вывод результата. И вывод её сделан в нескольких местах из-за того, что в зависимости от содержимого этой функции её придётся вызывать либо постоянно, либо при нажатии клавиши, ну и практически всегда при перерисовки окна (этот вызов будет описан дальше), если вы будете выводить какие-то графические элементы.
Так же вы видите, что в обработке сообщений нажатий клавиш в конце клавиша "отпускается" (обработчик события не вызовется пока вы не нажмёте её ещё раз). Это нужно для того, что бы событие не произошло несколько раз, так как компьютер работает довольно быстро и он успеет не один раз вызвать эту функцию до того, как вы отпустите клавишу.
Создадим функции для обработки ещё нескольких событий. Это событие изменения размера формы, событие её прорисовки и событие завершение программы. В результате наш обработчик сообщений будет выглядеть следующим образом:
Code

//--------------------------------------------------
// Обработка сообщений
//--------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
  switch (message)  
  {
   // Обработка события нажатия на клавиши
   case WM_SYSKEYDOWN:
   case WM_KEYDOWN:
    g_WKeys[wParam] = true;
    break;

   // Обработка события отпускания клавиши
   case WM_SYSKEYUP:
   case WM_KEYUP:
    g_WKeys[wParam] = false;
    break;

   // Обработка событий прорисовки
   case WM_PAINT:
    //UpdateScene();
    break;

   // Изменение размеров окна
   case WM_SIZE:
    Resize();
    break;

   // Выход из программы
   case WM_DESTROY:
    ShutDown();
    break;

   // События по умолчанию
   default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

Здесь думаю всё понятно. Функция Resize() вызывается при изменениях размеров окна, ShutDown() - при закрытии окна. UpdateScene() вызовется (если убрать коментарий) при поступлении сообщения о перерисовке. Оно приходит обычно в тех случаях, когда либо изменилась видимость какой-либо части окна (например унесли окно за границу, или другое окно перекрыло текущее) или были изменены её размеры. Также можно принудительно послать приложению сообщение о перерисовке. Но об этом потом. Сейчас рассмотрим что представляют собой все эти вспомогательные функции:
Code

//--------------------------------------------------
// Обновление сцены
//--------------------------------------------------
void UpdateScene()
{
}

//--------------------------------------------------
// Изменение размеров окна
//--------------------------------------------------
void Resize()
{
  // Прямоугольная область
  RECT rc;
  // Получение размеров клиентской области окна
  GetClientRect( g_hWnd, &rc );
  // Изменение значения переменных, отвечающих за ширину и высоту окна
  g_WWidth = rc.right;
  g_WHeight = rc.bottom;
  // Обновление сцены
  //UpdateScene();
}

//--------------------------------------------------
// Завершение работы приложения
//--------------------------------------------------
void ShutDown( int n = 0 )
{
  // Завершение работы приложения с кодом n
  PostQuitMessage(n);
}

Так как мы пока что ещё знакомимся с основами WinApi функция UpdateScene() пока ещё пустая, но в дальнейшем в основном все действия будут происходить в ней.
В функции Resize() мы получаем новые ширину и высоту окна (хотя точнее будет сказать клиентской области - то, что находится внутри границы окна, куда мы можем выводить какую-нибудь информацию) и заносим их в переменные g_WWidth и g_WHeight. Подробнее мы опишем что здесь происходит на следующих уроках. пока только скажем что нужно объявить глобальные переменные g_WWidth и g_WHeight, в которых и будет хранится размеры нашего окна (повторюсь, клиентской области).
Функция ShutDown() принимает один целочисленный аргумент и пока что всё что она делает, это завершает приложение с тем кодом, который она получила. По умолчанию он равен 0, что означает, что всё завершилось хорошо. Если где-нибудь возникает ошибка, то можно просто вызвать эту функцию и передать ей какое-нибудь значение, в ней корректно освободить все ресурсы и завершить приложение с переданным кодом, который в сможете увидеть в своём компиляторе.
На этом мы закончим наш урок. Ниже прилагается полный листинг, а также архив с откомпилированный программой и файлом программного кода.
Code

// Заголовочные файлы
#include <windows.h>

//--------------------------------------------------
// Глобальные переменные
//--------------------------------------------------
// Дескриптор окна
HWND g_hWnd = NULL;
// Размеры окна
int   g_WWidth;
int   g_WHeight;

// Нажатые клавиши
bool  g_WKeys[256];

//--------------------------------------------------
// Прототипы функций
//--------------------------------------------------
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

//--------------------------------------------------
// Регистрация класса и создание окна
//--------------------------------------------------
HRESULT WindowCreate( HWND *hWnd, HINSTANCE hInstance, int nCmdShow, //основные аргументы
        LRESULT (WINAPI *WndProc)(HWND, UINT, WPARAM, LPARAM), //обработчик сообщений
        int left = CW_USEDEFAULT, int top = CW_USEDEFAULT, //положение окна
        int width = 640, int height = 480, //размер
        TCHAR name[] = L"Window", DWORD style = WS_OVERLAPPEDWINDOW, //заголовок и стиль отображения
        TCHAR classname[] = L"MainWindow", HWND hWndParent = NULL ) //название класса и дескриптор родительского окна
{
  // Регистрируем класс окна
  WNDCLASSEX wc;
  wc.cbSize = sizeof(WNDCLASSEX); // Размер структуры в байтах
  wc.style          = CS_HREDRAW | CS_VREDRAW; // Стиль окна
  wc.lpfnWndProc    = WndProc; // Указатель на оконную процедуру
  wc.cbClsExtra     = 0; // Резервирование памяти для  
  wc.cbWndExtra     = 0; //дополнительной информации
  wc.hInstance      = hInstance; // Дескриптор приложения
  wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION); // Дескриптор основного значка
  wc.hCursor        = LoadCursor(NULL, IDC_ARROW); // Дескриптор курсора
  wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1); // Кисть для фона
  wc.lpszMenuName   = NULL; // Имя ресурса меню
  wc.lpszClassName  = classname; // Имя класса
  wc.hIconSm        = LoadIcon(NULL, IDI_APPLICATION); // Дескриптор малого значка
  if( !RegisterClassEx(&wc) )
   return E_FAIL;

  // Создаем окно
  *hWnd = CreateWindow(
   classname, // Имя класса
   name, // Заголовок окна
   style, // Внешний вид (стиль) окна
   left, top, // Координаты положения окна на экране
   width, height, // Размеры окна
   hWndParent, // Дескриптор родительского окна
   NULL, // Дескриптор меню
   hInstance, // Дескриптор приложения
   NULL ); // Указатель на данные создания окна

  //Если не удалось создать окно - выходим из функции
  if( !(*hWnd) )
   return E_FAIL;

  // Возвращаем успех
  return S_OK;
}

//--------------------------------------------------
// Инициализация
//--------------------------------------------------
bool Init( HINSTANCE hInstance, int nCmdShow )
{
  // Создание основного окна
  if( FAILED( WindowCreate( &g_hWnd, hInstance, nCmdShow, WndProc ) ) )
   return false;

  //Отображаем окна на экране
  ShowWindow( g_hWnd, nCmdShow );
  UpdateWindow(g_hWnd);

  // Возврат успеха
  return true;
}

//--------------------------------------------------
// Пост-инициализация
//--------------------------------------------------
void PostInit()
{
}

//--------------------------------------------------
// Обновление сцены
//--------------------------------------------------
void UpdateScene()
{
}

//--------------------------------------------------
// Изменение размеров окна
//--------------------------------------------------
void Resize()
{
  // Прямоугольная область
  RECT rc;
  // Получение размеров клиентской области окна
  GetClientRect( g_hWnd, &rc );
  // Изменение значения переменных, отвечающих за ширину и высоту окна
  g_WWidth = rc.right;
  g_WHeight = rc.bottom;
  // Обновление сцены
  //UpdateScene();
}

//--------------------------------------------------
// Завершение работы приложения
//--------------------------------------------------
void ShutDown( int n = 0 )
{
  // Завершение работы приложения с кодом n
  PostQuitMessage(n);
}

//--------------------------------------------------
// Обработка событий нажатия на клавиши
//--------------------------------------------------
void KeyDown()
{
  // Нажатие на клавишу [ ]
  if ( g_WKeys[VK_SPACE] )
  {
   // Обновление сцены
   //UpdateScene();
   g_WKeys[VK_SPACE] = false;
  }

  // Нажатие на клавиши [Alt] + [Enter]
  if ( g_WKeys[VK_MENU] && g_WKeys[VK_RETURN] )
  {
         // Если окно максимизированно (развёрнуто)
   if ( IsZoomed( g_hWnd ) )
    // восстанавливаем до нормального размера
    ShowWindow( g_hWnd, SW_NORMAL );
   else
    // , иначе разворачиваем на весь экран
    ShowWindow( g_hWnd, SW_MAXIMIZE );
   g_WKeys[VK_MENU] = g_WKeys[VK_RETURN] = false;
  }

  // Нажатие на клавишу [Esc]
  if ( g_WKeys[VK_ESCAPE] )
  {
   // Выход из программы
   ShutDown();
   g_WKeys[VK_ESCAPE] = false;
  }
}

//--------------------------------------------------
// Обработка сообщений
//--------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
  switch (message)  
  {
   // Обработка события нажатия на клавиши
   case WM_SYSKEYDOWN:
   case WM_KEYDOWN:
    g_WKeys[wParam] = true;
    break;

   // Обработка события отпускания клавиши
   case WM_SYSKEYUP:
   case WM_KEYUP:
    g_WKeys[wParam] = false;
    break;

   // Обработка событий прорисовки
   case WM_PAINT:
    //UpdateScene();
    break;

   // Изменение размеров окна
   case WM_SIZE:
    Resize();
    break;

   // Выход из программы
   case WM_DESTROY:
    ShutDown();
    break;

   // События по умолчанию
   default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

//--------------------------------------------------
// С этой функции начинается выполнение программы
//--------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  // Создание окон и элементов управления
  if ( !Init( hInstance, nCmdShow ) )
   return 0;

  // После их создания
  PostInit();

  // Цикл обработки сообщений
  MSG msg = {0};
  while( GetMessage( &msg, NULL, 0, 0 ) )
  {
   TranslateMessage( &msg );
   DispatchMessage( &msg );

   // Проверка нажатых клавиш
   KeyDown();
    
   // Обновление сцены
   //UpdateScene();
  }

  return (int) msg.wParam;
}
Прикрепления: Ex02WindowMessa.rar (5.9 Kb)



 
Форум » Создание игр посредством прямого программирования » Уроки по WinApi » Обработчик сообщений
  • Страница 1 из 1
  • 1
Поиск:

Copyright Flying Cow © 2024Сайт создан в системе uCoz