Книга: Графика для Windows средствами DirectDraw

Программа Smear

Программа Smear

Перейдем к поддержке мыши в DirectInput. Она будет рассматриваться на примере программы Smear, которая отображает поверхность в центре экрана и затем сдвигает изображение в соответствии с перемещением мыши (см. рис. 6.2).

Структура приложения

Хотя программа Smear демонстрирует работу с мышью, она также использует клавиатуру для проверки клавиши Escape. Работа с клавиатурой уже рассматривалась на примере программы Qwerty, поэтому мы не будем надолго задерживаться на ней.


Рис. 6.2. Программа Smear

Если работа с клавиатурой в обеих программах построена на непосредственных данных, то ввод с мыши в программе Smear осуществляется с помощью буферизованных данных. При этом необходимо задать размер буфера (для непосредственных данных это не нужно). По умолчанию размер буфера равен нулю, поэтому для работы с буферизованными данными необходимо выбрать подходящее значение. Хотя для ввода с мыши можно было бы воспользоваться и непосредственными данными, в нашем приложении это приведет к худшему результату. С помощью буферизованных данных можно определить не только новое положение мыши, но и путь, по которому она туда попала. Эта информация обеспечивает более гладкий и точный вывод.

Часть программы Smear, работающая с DirectDraw, отличается от всех остальных программ книги, потому что в данном случае не применяется переключение страниц — вместо этого мы непосредственно обновляем содержимое экрана. Такой подход имеет ряд последствий.

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

Во-вторых, мы уже не можем просто стереть фоновое изображение. В других программах мы стираем весь вторичный буфер, строим новый кадр и обновляем экран; никакого мерцания при этом не возникает. Стирание вторичного буфера в программе Smear вызовет заметное мерцание, потому что стертый фон будет отображаться во время вывода нового кадра. Программа названа Smear (то есть «размазывание») как раз потому, что фон не стирается, в результате при перемещении поверхности остается смазанный след.

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

Класс SmearWin

Основная функциональность программы Smear обеспечивается классом SmearWin (см. листинг 6.4).

Листинг 6.4. Класс SmearWin

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()

• OnDestroy()

• OnActivate()

Функция OnCreate() инициализирует DirectInput, а также готовит к работе мышь и клавиатуру. Функция OnDestroy() освобождает объекты DirectInput, инициализированные функцией OnCreate(). Функция OnActivate() захватывает клавиатуру в начале работы и при повторной активизации приложения.

Затем следует переопределенная функция DirectDrawWin::CreateFlippingSurfaces(). Нам не нужна переключаемая первичная поверхность, которая по умолчанию предоставляется классом DirectDrawWin, поэтому мы переопределяем эту функцию и создаем первичную поверхность, неспособную к переключению страниц.

Следующие пять функций присутствуют в большинстве наших программ. Внимания среди них заслуживает разве что функция DrawScene(). Она обнаруживает ввод с клавиатуры или от мыши и соответствующим образом обновляет экран.

Затем класс SmearWin объявляет функции InitMouse() и InitKeyboard(). Функция OnCreate() возлагает на них ответственность за инициализацию устройств.

Наконец, мы объявляем несколько переменных. Переменная dinput — указатель на интерфейс DirectInput, она используется для работы с DirectInput после инициализации. Переменные mouse и keyboard указывают на интерфейсы DirectInputDevice, они инициализируются функциями InitMouse() и InitKeyboard() соответственно. Указатель на поверхность sphere и целые переменные x и y предназначены для вывода и позиционирования единственной поверхности приложения.

Инициализация DirectInput

Функция OnCreate() инициализирует DirectInput, а затем инициализирует мышь и клавиатуру функциями InitMouse() и InitKeyboard(). Она выглядит так:

int SmearWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {
 HRESULT r=DirectInputCreate(AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0);
 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.

Затем мы вызываем функции, которые инициализируют мышь и клавиатуру. Они рассматриваются ниже. Функция OnCreate() завершается вызовом версии OnCreate() базового класса, инициализирующим DirectDraw.

Инициализация мыши

Функция InitMouse() (см. листинг 6.5) готовит мышь к работе.

Листинг 6.5. Функция InitMouse()

BOOL SmearWin::InitMouse() {
 HRESULT r;
 r = dinput->CreateDevice(GUID_SysMouse, &mouse, 0);
 if (r!=DI_OK) {
  TRACE("CreateDevice(mouse) failedn");
  return FALSE;
 }
 r = mouse->SetDataFormat(&c_dfDIMouse);
 if (r!=DI_OK) {
  TRACE("mouse->SetDataFormat() failedn");
  return FALSE;
 }
 r = mouse->SetCooperativeLevel(GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
 if (r!=DI_OK) {
  TRACE("mouse->SetCooperativeLevel() failedn");
  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() включает в себя четыре этапа:

1. Создание объекта DirectInputDevice, представляющего мышь.

2. Определение формата данных, получаемых от мыши.

3. Установку уровня кооперации для мыши.

4. Инициализацию буфера данных устройства.

Функция CreateDevice() интерфейса DirectInput (первый этап) создает экземпляр интерфейса DirectInputDevice, представляющего системную мышь:

r = dinput->CreateDevice(GUID_SysMouse, &mouse, 0);

DirectInput предоставляет константу GUID_SysMouse, поэтому для получения нужного GUID можно обойтись без составления списка системных устройств. Если приложение должно поддерживать аппаратные конфигурации, в которых используется более одной мыши, придется вызывать функцию EnumDevices().

Если вызов функции CreateDevice() прошел успешно, переменной mouse присваивается указатель на созданный объект DirectInputDevice. Третий аргумент должен быть равен нулю, если только вы не пользуетесь агрегированием COM.

На втором этапе функция SetDataFormat() интерфейса DirectInputDevice сообщает DirectInput формат ожидаемых данных:

r = mouse->SetDataFormat(&c_dfDIMouse);

В DirectInput предусмотрен формат c_dfDIMouse для стандартных данных мыши, поэтому эта задача оказывается простой. DirectInput также содержит форматы данных для клавиатур и джойстиков, так что в большинстве приложений вам не придется определять нестандартные форматы.

На третьем этапе определяется уровень кооперации для мыши:

r = mouse->SetCooperativeLevel(GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);

Как и в программе Qwerty, при вызове этой функции используются флаги совместного (DISCL_NONEXCLUSIVE) и активного (DISCL_FOREGROUND) режимов доступа. Первый означает, что во время работы нашей программы другое приложение может получить монопольный доступ к мыши, а второй — что, находясь в фоновом режиме, наше приложение не получает ввод от мыши.

Остается лишь задать размер буфера данных функцией SetProperty() интерфейса DirectInputDevice. Размер буфера определяет количество событий, сохраняемых в очереди DirectInput. Если буфер слишком мал, возникает риск потери данных из-за его переполнения. Я снова привожу соответствующий фрагмент листинга 6.5:

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. Среди прочего эта структура содержит значение свойства.

В нашем случае константа DIPROP_BUFFERSIZE говорит о том, что SetProperty() задает размер буфера. Поле dwSize структуры property равно 64; это значит, что мы заказываем буфер данных из 64 элементов. Размер буфера выбирается достаточно произвольно. Он должен быть достаточно большим, чтобы избежать переполнения, и достаточно малым, чтобы не тратить память напрасно.

Подготовка мыши закончена; осталось лишь захватить ее перед получением данных. Перед тем как захватывать мышь, мы кратко рассмотрим процесс инициализации клавиатуры.

Инициализация клавиатуры

Инициализация клавиатуры выполняется функцией 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() failedn");
  return FALSE;
 }
 r=keyboard->SetCooperativeLevel(GetSafeHwnd(), DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
 if (r!=DI_OK) {
  TRACE("keyboard->SetCooperativeLevel() failedn");
  return FALSE;
 }
 return TRUE;
}

Инициализация клавиатуры происходит так же, как и в программе Qwerty.

Захват мыши и клавиатуры

Перед тем как получать данные от клавиатуры или мыши, необходимо предварительно захватить их. Кроме того, устройство приходится захватывать заново, если приложение некоторое время было неактивным. И исходный, и все последующие захваты устройства выполняются в обработчике OnActivate():

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().

Получение данных от мыши

Хлопоты с инициализацией мыши и клавиатуры закончены, теперь можно получать от них данные. Функция DrawScene() (см. листинг 6.6) через указатели mouse и keyboard обращается к обоим устройствам и получает от них данные.

Листинг 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.

Каждый элемент буфера представлен структурой DIDEVICEOBJECTDATA. Эта структура используется независимо от типа устройства, поэтому ее поля были сделаны универсальными. DirectInput определяет структуру DIDEVICEOBJECTDATA следующим образом:

typedef struct {
 DWORD dwOfs;
 DWORD dwData;
 DWORD dwTimeStamp;
 DWORD dwSequence;
} DIDEVICEOBJECTDATA, *LPDIDEVICEOBJECTDATA;

Для мыши поле dwOfs определяет тип события. В DirectInput определены следующие константы, описывающие ввод от мыши:

• DIMOFS_X

• DIMOFS_Y

• DIMOFS_Z

• DIMOFS_BUTTON0

• DIMOFS_BUTTON1

• DIMOFS_BUTTON2

• DIMOFS_BUTTON3

Программа Smear реагирует только на перемещение мыши по осям x и y, поэтому после вызова функции GetDeviceData() поле dwOfs сравнивается с константами DIMOFS_X и DIMOFS_Y.

Поле dwData определяет новые значения осевых координат и кнопок. Поскольку мы используем относительные значения, содержимое этого поля равно смещению по данной оси с момента получения последних данных. Следовательно, оно может быть и отрицательным. Поле dwData используется для обновления переменных x и y.

Поля dwTimeStamp и dwSequence содержат информацию о том, когда произошло данное событие. Поле dwTimeStamp определяет время в миллисекундах (о том, как интерпретируется эта величина, можно подробно узнать в описании функции Win32 GetTickCount()). Поле dwSequence определяет порядок наступления событий. События с меньшими номерами наступили раньше, однако несколько событий могут иметь одинаковые порядковые номера. Например, если мышь или рукоять джойстика смещается по диагонали, события для координат x и y будут иметь одинаковые номера.

Вернемся к функции DrawScene(). Цикл ввода извлекает элементы буфера до тех пор, пока буфер не опустеет. Этот цикл выглядит так:

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. При возврате из функции это значение показывает количество полученных элементов.

Если вызов функции прошел успешно и значение elements осталось равным 1, значит, элемент буфера был прочитан, а поля dwOfs и dwData определяют тип события. Нулевое значение elements говорит о том, что буфер пуст и цикл завершается.

После извлечения всех элементов буфера остается лишь вывести поверхность в позиции, определяемой переменными x и y. Для этого используется функция BltSurface():

BltSurface(primsurf, sphere, x, y, TRUE);

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

Завершение приложения

Перед завершением приложения MFC вызывает функцию OnDestroy(); мы воспользуемся ею для освобождения объектов DirectInput. Функция OnDestroy() выглядит так:

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 (и вызывает одноименную функцию базового класса).

Заключение

В этой главе мы узнали, как организовать в своих приложениях поддержку DirectInput и обойти традиционные механизмы ввода Windows. Такое решение повышает быстродействие и дает больше возможностей для обработки пользовательского ввода. По этим причинам DirectInput будет использоваться во всех оставшихся программах этой книги.

В следующей главе мы займемся одной неприятной проблемой, связанной с курсором мыши. Как вы вскоре убедитесь, DirectInput станет неотъемлемой частью ее решения.

Оглавление книги


Генерация: 1.914. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз