"Графика для Windows средствами DirectDraw" |
Глава 6. DirectInput |
Давайте
отдохнем от DirectDraw и познакомимся с библиотекой DirectInput. Конечно,
эта книга посвящена компьютерной графике, но ввод информации пользователем
— необходимая часть любого графического приложения. Применение DirectInput
улучшает работу программ, так как ввод обнаруживается и обрабатывается
с максимальной скоростью. После краткого знакомства с DirectInput API
мы обсудим различные схемы получения пользовательского ввода, поддерживаемые
DirectInput. Знакомство закончится созданием двух приложений: Qwerty и
Smear. Программа Qwerty использует DirectInput для ввода с клавиатуры,
а Smear работает с мышью. DirectInput
поддерживает практически все устройства ввода, подключаемые к PC. Конечно,
речь идет лишь о тех устройствах, для которых существуют драйверы DirectInput.
Библиотека обеспечивает настолько исчерпывающую поддержку любых устройств
ввода, что она (скорее всего) сможет поддерживать и те устройства, которые
еще не изобретены. Наверное,
вас интересует, каким образом DirectInput обгоняет традиционные механизмы
Windows. DirectInput однозначно приходится выбирать для устройств, не
поддерживаемых Win32 API, но зачем использовать его для работы с
мышью и клавиатурой? В
зависимости от потребностей вашего приложения DirectInput может использоваться
для получения данных двух видов: непосредственных (immediate) и буферизованных
(buffered). Непосредственные данные описывают состояние устройства ввода
на момент запроса данных, а буферизованные данные используют концепцию
очереди для описания изменений в состоянии устройства (например, нажатий
клавиш или осевых смещений). Независимо
от конкретной схемы приложение в какой-то момент должно получить данные.
Чаще всего это делается путем опроса (polling) устройства через подходящие
промежутки времени. Например, приложение может проверять наличие новых
данных (непосредственных или буферизованных) при каждом обновлении экрана.
Чтобы
приложение могло задать нужную степень контроля над устройством, в DirectInput
используются уровни кооперации. DirectInput, как и DirectDraw, позволяет
установить монопольный (exclusive) и совместный (nonexclusive) режим доступа
для каждого устройства. Если приложение DirectInput обладает монопольным
доступом к устройству ввода, то никакое другое приложение заведомо не
сможет получить монопольного доступа к тому же устройству (хотя сможет
получить совместный доступ). Устройство,
возвращающее информацию об осевых смещениях (например, мышь или джойстик),
можно настроить так, чтобы оно возвращало относительные или абсолютные
данные. Относительные осевые смещения описывают перемещение по данной
оси по отношению к предыдущему положению, а абсолютные — текущую
позицию по данной оси. Приложение
DirectDraw в случае необходимости может уступить видеопамять другому приложению
и восстановить ее, когда исходное приложение снова получит фокус. В DirectInput
приложение тоже может потерять устройство и восстановить контроль над
ним перед тем, как продолжить работу. В таких случаях говорят, что приложение
захватывает устройство (acquire) или уступает его (unacquire).
Для получения данных необходимо захватить устройство. Приложение может
уступить устройство по требованию (доступ к устройству передается Windows
или другому приложению) или автоматически (например, если DirectInput
отбирает право доступа к устройству, чтобы передать его другому приложению).
До
выхода DirectX 3 библиотека DirectInput была построена на существующих
функциях Win32 и не поддерживала ввода с клавиатуры или от мыши. В DirectX 3
появились COM-интерфейсы для клавиатуры и мыши, но все остальные устройства
продолжали зависеть от функций Win32 (и особенно от функции joyGetPosEx()
). В DirectX 5 зависимость DirectInput от Win32 полностью устранена,
а все устройства ввода переведены на использование COM-интерфейсов. Работа
с устройствами ввода реализована через три интерфейса:
Первичным,
или главным, является интерфейс DirectInput . Создание его экземпляра
приводит к инициализации библиотеки, а все остальные интерфейсы DirectInput
могут быть инициализированы лишь с помощью его функций. Инициализация
DirectInput происходит в тот момент, когда вы получаете указатель
на интерфейс DirectInput функцией DirectInputCreate()
. Затем полученным интерфейсом можно воспользоваться для создания экземпляров
интерфейса DirectInputDevice , составления списка доступных устройств
и даже для вызова панели управления DirectInput . Интерфейс
DirectInput содержит следующие четыре функции:
Значения
GUID остальных устройств можно получить функцией EnumDevices()
. Доступ
ко всем устройствам ввода, представленным в DirectInput, осуществляется
через интерфейс DirectInputDevice . Интерфейс DirectInputDevice
содержит следующие функции:
Функции Acquire() и Unacquire() устанавливают и разрывают связь между устройством ввода и DirectInput. Перед тем как получать от устройства данные, необходимо захватить его. Функция Acquire() используется для начального захвата устройства и для восстановления нарушенной связи с устройством. Обычно связь между устройством ввода и DirectInput разрывается из-за того, что приложение теряет фокус ввода. Функция Unacquire() используется, как правило, для того, чтобы вернуть Windows право доступа к устройству. Например, при работе с меню необходимо уступить объекты DirectInputDevice , представляющие мышь и клавиатуру, чтобы Windows могли нормально обработать выбор команды меню. Функция GetCapabilities() с помощью структуры DIDEVCAPS описывает возможности устройства, в том числе количество кнопок, количество осей и поддержку обратной связи. Кроме того, в структуру включаются описания конкретных особенностей (или ограничений) устройства, влияющих на его использование. Например, установка флага DIDC_POLLEDDEVICE означает, что от устройства можно получать лишь непосредственные данные. Функция GetDeviceData() получает от устройства буферизованные данные. Она позволяет извлечь из буфера один или несколько элементов, а также просмотреть его содержимое (то есть прочитать элементы буфера без их удаления). Функция GetDeviceInfo() заполняет структуру DIDEVICEINSTANCE информацией об устройстве. В структуру заносятся значения GUID экземпляра и продукта для данного устройства, а также строки с неформальными описаниями. Ту же самую информацию можно получить функцией EnumDevices() интерфейса DirectInput , так что, строго говоря, эта функция не является обязательной. Функция GetDeviceState() предназначена для получения непосредственных данных от устройства. Она определяет состояние устройства на момент вызова функции. Например, с ее помощью можно узнать об одновременном нажатии двух клавиш. Функция SetDataFormat() описывает формат, в котором вводимые данные возвращаются устройством. Приложение может определить собственный формат, а в DirectInput предусмотрены три стандартных формата для стандартных устройств:
Функция SetEventNotification() , особенно часто используемая в многопоточных приложениях, предназначена для обработки оповещений. При изменении состояния данного устройства DirectInput сигнализирует о наступлении события. Таким образом, поток может перейти в режим блокировки (например, с помощью функции WaitForSingleObject() ) и ожидать изменений в устройстве ввода. При наступлении события поток автоматически отреагирует на него. Каждое устройство ввода содержит один или несколько объектов. Для клавиатуры объект представляет отдельную клавишу. Для мыши объекты представляют каждую кнопку и каждую ось. Функция EnumObjects() составляет список объектов заданного устройства и возвращает для каждого объекта GUID и строку. Строка содержит неформальное описание объекта (например, «ось X» или «Right Shift»). GUID описывает тип объекта и может принимать одно из следующих значений:
Функция GetObjectInfo() позволяет получить ту же информацию, что и EnumObjects() , но без предварительного составления списка объектов. Интересующий вас объект задается значением его смещения или идентификатора. Функции GetProperty() и SetProperty() применяются для просмотра и установки параметров устройств (свойств), отсутствующих в DirectInput API. В DirectInput предусмотрен ряд стандартных свойств (например, свойства autocenter и deadzone для джойстиков), однако эти функции могут применяться и для других, нестандартных свойств. В частности, нас будет интересовать свойство buffersize (определяется константой DIPROP_BUFFERSIZE ), с помощью которого задается размер буфера для хранения буферизованных данных. По умолчанию размер буфера равен нулю, поэтому для работы с буферизованными данными придется задать свойство buffersize . Функция SetCooperativeLevel() определяет степень контроля над заданным устройством. Допускаются следующие значения:
Вызов
функции SetCooperativeLevel() наряду с вызовом SetDataFormat()
необходим для получения данных от устройства.
class QwertyWin : public DirectDrawWin { public: QwertyWin(); protected: //{{AFX_MSG(QwertyWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int SelectDriver(); int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces(); void DrawScene(); void RestoreSurfaces(); private: LPDIRECTINPUT dinput; LPDIRECTINPUTDEVICE keyboard; BOOL esc_pressed; LPDIRECTDRAWSURFACE esc_up, esc_dn; LPDIRECTDRAWSURFACE space_up, space_dn; LPDIRECTDRAWSURFACE q_up, q_dn; LPDIRECTDRAWSURFACE w_up, w_dn; LPDIRECTDRAWSURFACE e_up, e_dn; LPDIRECTDRAWSURFACE r_up, r_dn; LPDIRECTDRAWSURFACE t_up, t_dn; LPDIRECTDRAWSURFACE y_up, y_dn; LPDIRECTDRAWSURFACE rctrl_up, rctrl_dn; LPDIRECTDRAWSURFACE lctrl_up, lctrl_dn; LPDIRECTDRAWSURFACE lalt_up, lalt_dn; LPDIRECTDRAWSURFACE ralt_up, ralt_dn; }; Прежде
чем двигаться дальше, обратите внимание на отсутствие обработчика
OnKeyDown() . Во всех программах, рассмотренных нами ранее, функция
OnKeyDown() обрабатывала сообщения от клавиатуры. В программе
Qwerty мы пользуемся услугами DirectInput и потому не нуждаемся в
OnKeyDown() .
Функция
OnCreate() инициализирует и настраивает DirectInput, а функция
OnDestroy() освобождает объекты DirectInput. Функция OnActivate()
, вызываемая MFC при получении или потере фокуса, будет использована
для повторного захвата клавиатуры. Инициализация DirectInput и DirectDraw выполняется в функции OnCreate() . DirectInput инициализируется версией OnCreate() класса QwertyWin, а DirectDraw — версией из DirectDrawWin . Функция QwertyWin::OnCreate() приведена в листинге 6.2. Листинг 6.2 . Функция QwertyWin::OnCreate() int QwertyWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); if (r!=DI_OK) { AfxMessageBox("DirectInputCreate() failed"); return -1; } r = dinput->CreateDevice( GUID_SysKeyboard, &keyboard, 0 ); if (r!=DI_OK) { AfxMessageBox("CreateDevice(keyboard) failed"); return -1; } r = keyboard->SetDataFormat( &c_dfDIKeyboard ); if (r!=DI_OK) { AfxMessageBox("keyboard->SetDataFormat() failed"); return -1; } r=keyboard->SetCooperativeLevel( GetSafeHwnd(), DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); if (r!=DI_OK) { AfxMessageBox("keyboard->SetCooperativeLevel() failed"); return -1; } if (DirectDrawWin::OnCreate(lpCreateStruct)==-1) return -1; return 0; } Прежде
всего обратите внимание — версия OnCreate() базового класса
вызывается лишь в конце функции. Это сделано для того, чтобы при неудачной
инициализации DirectInput программа выводила окно сообщения и прекращала
работу без инициализации DirectDraw. HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); Первый
аргумент - логический номер экземпляра приложения, получаемый функцией
AfxGetInstanceHandle() . Второй аргумент — номер версии
DirectInput. В нашем случае используется константа DIRECTINPUT_VERSION
, она определяется DirectInput в зависимости от версии SDK, использованной
для компиляции приложения. Различные версии DirectInput более подробно
рассматриваются в этой главе ниже. Третий аргумент DirectInputCreate()
— адрес инициализируемого указателя, а четвертый — показатель
агрегирования COM, который обычно равен нулю (агрегированием называется
разновидность наследования, используемая в COM). Если инициализация DirectInput
проходит успешно (то есть если DirectInputCreate() возвращает
DI_OK ), указатель dinput может использоваться для работы
с DirectInput. r = dinput->CreateDevice( GUID_SysKeyboard, &keyboard, 0 ); Функция
CreateDevice() интерфейса DirectInput применяется для инициализации
устройств DirectInput. В нашем случае первым аргументом является стандартная
константа GUID_SysKeyboard , показывающая, что мы собираемся
работать с системной клавиатурой. Второй аргумент — адрес указателя
keyboard , через который мы впоследствии будем обращаться к клавиатуре.
Третий аргумент — показатель агрегирования COM, в нашем случае он
должен быть равен нулю. r = keyboard->SetDataFormat( &c_dfDIKeyboard ); Функции
SetDataFormat() интерфейса DirectInputDevice передается
единственный аргумент — константа стандартного формата c_dfDIKeyboard
. Программа Qwerty работает лишь с одним устройством (клавиатурой),
но, как мы убедимся в программе Smear, формат данных должен задаваться
отдельно для каждого устройства, используемого программой. r=keyboard->SetCooperativeLevel( GetSafeHwnd(), DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); Функция
SetCooperativeLevel() получает два аргумента: логический номер
окна и набор флагов, определяющих уровень кооперации. Функция GetSafeHwnd()
определяет логический номер окна, а флаги DISCL_FOREGROUND
и DISCL_NONEXCLUSIVE задают нужный уровень кооперации. Флаг активного
режима DISCL_FOREGROUND присутствует потому, что на время активности
другого приложения нам не потребуется ввод от клавиатуры, а флаг DISCL_NONEXCLUSIVE
— потому, что DirectInput не позволяет установить монопольный
доступ к клавиатуре.
Итак, мы инициализировали DirectInput и подготовили клавиатуру к работе; теперь необходимо захватить ее. Для этой цели используется функция OnActivate() , потому что клавиатуру приходится захватывать при каждой активизации нашего приложения. Функция OnActivate() выглядит так: void QwertyWin::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { DirectDrawWin::OnActivate(nState, pWndOther, bMinimized); if (nState!=WA_INACTIVE && keyboard) { TRACE("keyboard->Acquire()\n"); keyboard->Acquire(); } } После
вызова версии OnActivate() базового класса мы проверяем, происходит
ли активизация приложения (функция OnActivate() вызывается и
в случае деактивизации, когда активным становится другое приложение).
Если проверка дает положительный результат, мы вызываем функцию Acquire()
интерфейса DirectInputDevice . Теперь
по указателю на интерфейс клавиатуры можно определить состояние отдельных
клавиш. В нашей программе это происходит в функции DrawScene()
, перед обновлением экрана. Функция DrawScene() приведена в листинге 6.3.
Листинг 6.3 . Функция QwertyWin::DrawScene() void QwertyWin::DrawScene() { static char key[256]; keyboard->GetDeviceState( sizeof(key), &key ); //---------- Клавиши QWERTY -------- if ( key[DIK_Q] & 0x80 ) BltSurface( backsurf, q_dn, 213, 70 ); else BltSurface( backsurf, q_up, 213, 70 ); if ( key[DIK_W] & 0x80 ) BltSurface( backsurf, w_dn, 251, 70 ); else BltSurface( backsurf, w_up, 251, 70 ); if ( key[DIK_E] & 0x80 ) BltSurface( backsurf, e_dn, 298, 70 ); else BltSurface( backsurf, e_up, 298, 70 ); if ( key[DIK_R] & 0x80 ) BltSurface( backsurf, r_dn, 328, 70 ); else BltSurface( backsurf, r_up, 328, 70 ); if ( key[DIK_T] & 0x80 ) BltSurface( backsurf, t_dn, 361, 70 ); else BltSurface( backsurf, t_up, 361, 70 ); if ( key[DIK_Y] & 0x80 ) BltSurface( backsurf, y_dn, 393, 70 ); else BltSurface( backsurf, y_up, 393, 70 ); //---------------- LEFT CONTROL --------------- if ( key[DIK_LCONTROL] & 0x80 ) BltSurface( backsurf, lctrl_dn, 50, 180 ); else BltSurface( backsurf, lctrl_up, 49, 180 ); //---------------- RIGHT CONTROL --------------- if ( key[DIK_RCONTROL] & 0x80 ) BltSurface( backsurf, rctrl_dn, 490, 180 ); else BltSurface( backsurf, rctrl_up, 490, 180 ); //---------------- LEFT ALT --------------- if ( key[DIK_LMENU] & 0x80 ) BltSurface( backsurf, lalt_dn, 100, 260 ); else BltSurface( backsurf, lalt_up, 100, 260 ); //---------------- RIGHT ALT --------------- if ( key[DIK_RMENU] & 0x80 ) BltSurface( backsurf, ralt_dn, 440, 260 ); else BltSurface( backsurf, ralt_up, 440, 260 ); //---------------- SPACE ----------------- if ( key[DIK_SPACE] & 0x80 ) BltSurface( backsurf, space_dn, 170, 340 ); else BltSurface( backsurf, space_up, 170, 340 ); //---------- ESCAPE ------------- if ( key[DIK_ESCAPE] & 0x80 ) { BltSurface( backsurf, esc_dn, 0, 0 ); esc_pressed=TRUE; } else { BltSurface( backsurf, esc_up, 0, 0 ); if (esc_pressed) PostMessage( WM_CLOSE ); } primsurf->Flip( 0, DDFLIP_WAIT ); } Состояние устройства определяется функцией GetDeviceState() интерфейса DirectInputDevice . Тип и размер второго аргумента GetDeviceState() зависят от типа устройства, а также от формата данных, заданного функцией SetDataFormat() . Для клавиатуры функция должна получать массив из 256 байт, где каждый байт соответствует одной клавише. В DirectInput предусмотрен набор клавиатурных констант, которые используются как индексы массива и позволяют ссылаться на нужные клавиши. DirectInput обозначает нажатие клавиши установкой старшего бита того байта, который представляет данную клавишу. Объявление массива и вызов функции GetDeviceState() находятся в верхней части листинга 6.3 , я снова привожу их: static char key[256]; keyboard->GetDeviceState( sizeof(key), &key ); Адрес
массива клавиш передается во втором аргументе GetDeviceState()
. Первый аргумент определяет размер данных в байтах. if ( key[DIK_Q] & 0x80 ) BltSurface( backsurf, q_dn, 213, 70 ); else BltSurface( backsurf, q_up, 213, 70 ); Константа
DIK_Q определяет индекс клавиши Q в массиве. Мы проверяем
значение старшего бита; если бит установлен, значит, клавиша Q
нажата, и мы копируем поверхность, изображающую клавишу Q в нажатом
состоянии ( q_dn ), функцией BltSurface() . Если клавиша
не нажата, копируется поверхность q_up . Завершить работу DirectInput несложно — для этого достаточно освободить все интерфейсы DirectInput. В нашей программе это происходит в функции OnDestroy() : void QwertyWin::OnDestroy() { DirectDrawWin::OnDestroy(); if (dinput) dinput->Release(), dinput=0; if (keyboard) { keyboard->Unacquire(); keyboard->Release(), keyboard=0; } }
#include В
приведенном фрагменте присутствуют директивы include для всех
заголовочных файлов программы. Символическая константа DIRECTINPUT_VERSION
должна быть определена до включения файла dinput.h (номер
версии определяется старшим байтом).
Программа Smear Хотя
программа Smear демонстрирует работу с мышью, она также использует клавиатуру
для проверки клавиши Escape. Работа с клавиатурой уже рассматривалась
на примере программы Qwerty, поэтому мы не будем надолго задерживаться
на ней. Основная
функциональность программы Smear обеспечивается классом SmearWin
(см. листинг 6.4). class SmearWin : public DirectDrawWin { public: SmearWin(); protected: //{{AFX_MSG(SmearWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL CreateFlippingSurfaces(); private: int SelectDriver(); int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces(); void DrawScene(); void RestoreSurfaces(); private: BOOL InitKeyboard(); BOOL InitMouse(); private: LPDIRECTINPUT dinput; LPDIRECTINPUTDEVICE mouse; LPDIRECTINPUTDEVICE keyboard; LPDIRECTDRAWSURFACE sphere; int x, y; }; В
классе объявлены три обработчика:
Функция
OnCreate() инициализирует DirectInput, а также готовит к работе
мышь и клавиатуру. Функция OnDestroy() освобождает объекты DirectInput,
инициализированные функцией OnCreate() . Функция OnActivate()
захватывает клавиатуру в начале работы и при повторной активизации
приложения. Функция OnCreate() инициализирует DirectInput, а затем инициализирует мышь и клавиатуру функциями InitMouse() и InitKeyboard() . Она выглядит так: int SmearWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); r if (r!=DI_OK) { AfxMessageBox("DirectInputCreate() failed"); return -1; } if (InitMouse()==FALSE) return -1; if (InitKeyboard()==FALSE) return -1; if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; return 0; } DirectInput
инициализируется функцией DirectInputCreate() . При успешном
вызове в переменную dinput заносится указатель на созданный объект
DirectInput . Остальные аргументы DirectInputCreate()
рассматривались в программе Qwerty.
Листинг 6.5 . Функция InitMouse() BOOL SmearWin::InitMouse() { HRESULT r; r = dinput->CreateDevice( GUID_SysMouse, &mouse, 0 ); if (r!=DI_OK) { TRACE("CreateDevice(mouse) failed\n"); return FALSE; } r = mouse->SetDataFormat( &c_dfDIMouse ); if (r!=DI_OK) { TRACE("mouse->SetDataFormat() failed\n"); return FALSE; } r = mouse->SetCooperativeLevel( GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ); if (r!=DI_OK) { TRACE("mouse->SetCooperativeLevel() failed\n"); return FALSE; } DIPROPDWORD property; property.diph.dwSize=sizeof(DIPROPDWORD); property.diph.dwHeaderSize=sizeof(DIPROPHEADER); property.diph.dwObj=0; property.diph.dwHow=DIPH_DEVICE; property.dwData=64; r = mouse->SetProperty( DIPROP_BUFFERSIZE, &property.diph ); if (r!=DI_OK) { TRACE("mouse->SetProperty() failed (buffersize)\n"); return FALSE; } return TRUE; } Функция
InitMouse() включает в себя четыре этапа:
Функция CreateDevice() интерфейса DirectInput (первый этап) создает экземпляр интерфейса DirectInputDevice , представляющего системную мышь: r = dinput->CreateDevice( GUID_SysMouse, &mouse, 0 ); DirectInput
предоставляет константу GUID_SysMouse , поэтому для получения
нужного GUID можно обойтись без составления списка системных устройств.
Если приложение должно поддерживать аппаратные конфигурации, в которых
используется более одной мыши, придется вызывать функцию EnumDevices()
. r = mouse->SetDataFormat( &c_dfDIMouse ); В
DirectInput предусмотрен формат c_dfDIMouse для стандартных данных
мыши, поэтому эта задача оказывается простой. DirectInput также содержит
форматы данных для клавиатур и джойстиков, так что в большинстве приложений
вам не придется определять нестандартные форматы. r = mouse->SetCooperativeLevel( GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ); Как
и в программе Qwerty, при вызове этой функции используются флаги совместного
( DISCL_NONEXCLUSIVE ) и активного ( DISCL_FOREGROUND
) режимов доступа. Первый означает, что во время работы нашей программы
другое приложение может получить монопольный доступ к мыши, а второй —
что, находясь в фоновом режиме, наше приложение не получает ввод от мыши.
DIPROPDWORD property; property.diph.dwSize=sizeof(DIPROPDWORD); property.diph.dwHeaderSize=sizeof(DIPROPHEADER); property.diph.dwObj=0; property.diph.dwHow=DIPH_DEVICE; property.dwData=64; r = mouse->SetProperty( DIPROP_BUFFERSIZE, &property.diph ); Функция
SetProperty() получает два аргумента: величину, которая определяет
задаваемое свойство, и адрес структуры DIPROPDWORD . Среди прочего
эта структура содержит значение свойства.
Инициализация клавиатуры выполняется функцией InitKeyboard() : BOOL SmearWin::InitKeyboard() { HRESULT r; r = dinput->CreateDevice( GUID_SysKeyboard, &keyboard, 0 ); if (r!=DI_OK) { TRACE("CreateDevice(keyboard) failed"); return FALSE; } r = keyboard->SetDataFormat( &c_dfDIKeyboard ); if (r!=DI_OK) { TRACE("keyboard->SetDataFormat() failed\n"); return FALSE; } r=keyboard->SetCooperativeLevel( GetSafeHwnd(), DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); if (r!=DI_OK) { TRACE("keyboard->SetCooperativeLevel() failed\n"); return FALSE; } return TRUE; } Инициализация клавиатуры происходит так же, как и в программе Qwerty. void SmearWin::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { DirectDrawWin::OnActivate(nState, pWndOther, bMinimized); if (nState!=WA_INACTIVE) { if (keyboard) { TRACE("keyboard->Acquire()\n"); keyboard->Acquire(); } if (mouse) { TRACE("mouse->Acquire()\n"); mouse->Acquire(); } } } Функция Acquire() вызывается для каждого устройства независимо от того, уступалось ли оно. DirectInput игнорирует лишние вызовы Acquire() . Листинг 6.6 . Функция SmearWin::DrawScene() void SmearWin::DrawScene() { static char key[256]; keyboard->GetDeviceState( sizeof(key), &key ); if ( key[DIK_ESCAPE] & 0x80 ) PostMessage( WM_CLOSE ); BOOL done=FALSE; while (!done) { DIDEVICEOBJECTDATA data; DWORD elements=1; HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { switch(data.dwOfs) { case DIMOFS_X: x+=data.dwData; break; case DIMOFS_Y: y+=data.dwData; break; } } else if (elements==0) done=TRUE; } BltSurface( primsurf, sphere, x, y, TRUE ); } Функция
DrawScene() сначала проверяет состояние клавиатуры функцией
GetDeviceState() интерфейса DirectInputDevice . Если была
нажата клавиша Escape , она посылает сообщение WM_CLOSE
, что приводит к завершению приложения. О функции GetDeviceState()
и проверке состояния клавиш рассказано в программе Qwerty, поэтому
сейчас мы займемся кодом, относящимся к мыши. DrawScene() в цикле
извлекает элементы буфера мыши. Для получения данных, а также для проверки
отсутствия элементов при пустом буфере используется функция GetDeviceData()
интерфейса DirectInputDevice . typedef struct { DWORD dwOfs; DWORD dwData; DWORD dwTimeStamp; DWORD dwSequence; } DIDEVICEOBJECTDATA, *LPDIDEVICEOBJECTDATA; Для
мыши поле dwOfs определяет тип события. В DirectInput определены
следующие константы, описывающие ввод от мыши:
Программа
Smear реагирует только на перемещение мыши по осям x и y
, поэтому после вызова функции GetDeviceData() поле
dwOfs сравнивается с константами DIMOFS_X и DIMOFS_Y
. while (!done) { DIDEVICEOBJECTDATA data; DWORD elements=1; HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { switch(data.dwOfs) { case DIMOFS_X: x+=data.dwData; break; case DIMOFS_Y: y+=data.dwData; break; } } else if (elements==0) done=TRUE; } Третий
аргумент GetDeviceData() используется двояко. Значение, передаваемое
функции, определяет количество элементов, извлекаемых из буфера.
В нашем случае используется всего одна структура DIDEVICEOBJECTDATA
, поэтому передается число 1. При возврате из функции
это значение показывает количество полученных элементов. BltSurface( primsurf, sphere, x, y, TRUE ); Обратите внимание: поверхность изображения копируется непосредственно на первичную поверхность, как говорилось при обсуждении структуры программы Smear. void SmearWin::OnDestroy() { DirectDrawWin::OnDestroy(); if (dinput) dinput->Release(), dinput=0; if (keyboard) { keyboard->Unacquire(); keyboard->Release(), keyboard=0; } if (mouse) { mouse->Unacquire(); mouse->Release(), mouse=0; } } Функция
OnDestroy() просто освобождает каждый объект DirectInput (и вызывает
одноименную функцию базового класса). |