Новости

Фотки
Надписи на партах
Под гармошку
Обои для рабочего стола

Downloads
Компьютерные магазины
Фуфло

ЕЯС
НКС
ООП
СЦОД

Гостевая

Я ВКонтакте

Объектно-ориентированное программирование

Урок №3. Обработка нажатий клавиш.

На этом уроке я расскажу, как определить, какая клавиша на клавиатуре была нажата пользователем.

Как Вы уже знаете, код нажатой клавиши считывается функцией getch(). Упрощённая обработка нажатий может осуществляться, например, вот так:

#include <conio.h>
void main() {

  unsigned char key;

  do {

    switch (key= getch()) {
      case 'A':
        ...
        break;
      case 'B':
        ...
        break;
      case 'T':
        ...
        break;
      case 'H':
        ...
        break;
    }

  } while (key != 27);
}

В принципе, эта форма нормально функционирует и для простеньких программ вполне подойдёт.

Но если мы хотим в программу добавить обработку таких клавиш, как F1, F2, ..., Insert, Delete, ..., Up, Left, ..., а также обработку различных комбинаций с Ctrl, Alt и Shift, то нам надо слегка изменить конструкцию программы.

Сейчас объясню почему:

Дело в том, что при нажатии некоторых клавиш на клавиатуре - в "компьютер" посылается один символ, при нажатии других - два символа.

Давайте напишем программу, которая поможет нам определить, сколько символов посылается, и какие это символы.Наверняка, подобную программу Вы уже писали. Если же нет, то рекомендую скопировать этот код и протестировать.

#include <conio.h> #include <iostream.h>
void main() {
  unsigned char key;
  while ((key= getch()) != 27)
    cout << (int)key << " " << key << "\n";
}

Итак, мы видим, что при нажатии на буквы, цифры, пробел, Enter (и некоторые другие клавиши) нам выводится одна строка с кодом символа и самим символом. А при нажатии на функциональные клавиши или на клавиши управления курсором - выводятся по две строки; причём в первой строке находится символ с кодом 0.

Таким образом, вышеописанная конструкция обработки клавиш не подойдёт. Например, клавиша Up посылает два символа: 0 и 72. Символ с кодом 0 - пропускается, а символ с кодом 72 будет обработан. Но ведь символ 'H' тоже имеет код 72, и он тоже будет обработан в том же месте.

Изменим конструцию так:

#include <conio.h>
void main() {

  unsigned char key;

  while ((key= getch()) != 27) {

    switch (key) {

      case 0: // если код - 0, то значит было послано 2 символа

        switch (key= getch()) { // считываем второй символ

          case 59: // F1
            ...
            break;
          case 44: // Alt+Z
            ...
            break;
          case 148: // Ctrl+Tab
            ...
            break;
          case 90: // Shift+F7
            ...
            break;
          case 72: // Up
            ...
            break;
          case 116: // Ctrl+Right
            ...
            break;
        }
        break;

      case 9: // Tab
        ...
        break;
      case 13: // Enter
        ...
        break;
      case 'Q': // Q
        ...
        break;
      case 'q': // q
        ...
        break;
    }
  }
}

Вот собственно и всё. Вложенный блок switch разрешает проблему двусмысленности.

Теперь перейдём к другой половине нашей задачи. Ведь есть такие клавиши, которые вообще никаких символов не посылают и на kbhit() не реагируют. Это касается клавиш Shift, Ctrl, Alt, NumLock, CapsLock и ScrollLock.

И всё же есть способ определить нажатость этих клавиш. По адресу 0040:0017 располагается слово (слово - двухбайтовый блок), у которого каждый бит отвечает за одну из клавиш. Мы можем объявить дальний указатель на число типа unsigned int и пусть он указывает на эту обасть памяти

unsigned far * KEYBOARD= (unsigned far *) 0x00400017;
// примечание: unsigned int - можно записывать как просто unsigned

И теперь с помощью операции сдвига и выделения правого бита мы можем определить чему равен бит, нулю или единице.

cout << "5-ый бит равен " << (*KEYBOARD >> 5) & 1;
cout << "i-ый бит равен " << (*KEYBOARD >> i) & 1;

Для тестирования можно написать, например, такую программу:

#include <conio.h> #include <stdio.h> #include <dos.h>
unsigned far *KEYBOARD= (unsigned far *) 0x00400017;

void main() {

  clrscr();
  printf (" 0  Нажата правая Shift :\n");
  printf (" 1  Нажата левая Shift :\n");
  printf (" 2  Нажата любая Ctrl :\n");
  printf (" 3  Нажата любая Alt :\n");
  printf (" 4  Включен Scroll Lock :\n");
  printf (" 5  Включен Num Lock :\n");
  printf (" 6  Включен Caps Lock :\n");
  printf (" 7  Включен Insert :\n");
  printf (" 8  Нажата левая Ctrl :\n");
  printf (" 9  Нажата левая Alt :\n");
  printf ("10  ............. :\n");
  printf ("11  ............. :\n");
  printf ("12  Нажата Scroll Lock :\n");
  printf ("13  Нажата Num Lock :\n");
  printf ("14  Нажата Caps Lock :\n");
  printf ("15  Нажата Insert :\n");

  // цикл - пока не нажата ни одна клавиша, или нажата - но не Esc
  while (!kbhit() || getch()!=27) {
    // определяем состояние всех битов
    for (int i=0; i<16; i++) {
      gotoxy (26, i+1);
      printf ( ((*KEYBOARD >> i) & 1) ? "TRUE " : "FALSE" );
    }
    // чтобы слишком часто курсор не рябил
    delay (55);
  }
}

Теперь мы знаем: что бы определить нажата ли, скажем, клавиша Ctrl, надо проверить такое условие:

if ((*KEYBOARD >> 2) & 1) {
  ...
}

Кстати, вот чем интересна обработка клавиш Shift, Ctrl, Alt, NumLock, CapsLock и ScrollLock, так это тем, что они не посылают никаких символов в "компьютер". Они просто - либо нажаты, либо не нажаты.

Чтобы это правильнее понять, давайте посмотрим настройки клавиатуры в Windows. Вкладка "Скорость", раздел "Повтор вводимого символа". Как Вы видите, можно настроить "Задержку перед началом повтора" и "Скорость повтора". Так вот, когда мы нажимаем, например, пробел, то в "компьютер" записывается символ пробела, затем после небольшой паузы записывается ещё один символ пробела, а потом после второго раза пробелы будут записываться уже быстрее, причём через равный промежуток времени.

(Блин, чего это я говорю как с детьми, "компьютер" да "компьютер"... Будет умнее и точнее, если я буду говорить "буфер клавиатуры". Короче запомните: все символы, которые Вы набираете на клавиатуре записываются в буфер клавиатуры. В нём может храниться до 16 символов. Если там уже запиханы 16 символов - значит буфер переполнен, и никакой другой символ туда не добавится. Это значит буфер клавиатуры надо освободить. А освободить его можно используя функцию getch(), которая извлекает один символ из этого буфера. А функция kbhit() - проверяет, есть ли в буфере клавиатуры хотя бы один несчитанный символ.)

Что я этим хотел сказать?

Я хотел сказать, что иногда при создании игр требуется определить именно нажата клавиша, или нет. Например танчики: танк может двигаться, пока клавиша нажата, когда она отпущена - он не перемещается. Или, эмуляция азбуки Морзе: тут уже нужно учитывать длительность нажатия. Вот в этих случаях и удобно применять эти клавиши.

А вообще, для красоты, неплохо бы было завести отдельный класс Keyboard, в нём объявить статические функции которые будут показывать состояние этих клавиш.

#include <conio.h> #include <stdio.h> #include <dos.h>
class Keyboard {

private:
  static unsigned int far *KEYBOARD;

public:
  static isPressedRightShift() { return *KEYBOARD & 1; }
  static isPressedLeftShift() { return (*KEYBOARD >> 1) & 1; }
  static isPressedAnyShift() { return (*KEYBOARD & 3) > 0; }

  static isPressedRightCtrl() { return isPressedAnyCtrl() && !isPressedLeftCtrl(); }
  static isPressedLeftCtrl() { return (*KEYBOARD >> 8) & 1; }
  static isPressedAnyCtrl() { return (*KEYBOARD >> 2) & 1; }

  static isPressedRightAlt() { return isPressedAnyAlt() && !isPressedLeftAlt(); }
  static isPressedLeftAlt() { return (*KEYBOARD >> 9) & 1; }
  static isPressedAnyAlt() { return (*KEYBOARD >> 3) & 1; }

  static isPressedScrollLock() { return (*KEYBOARD >> 12) & 1; }
  static isPressedNumLock() { return (*KEYBOARD >> 13)  & 1; }
  static isPressedCapsLock() { return (*KEYBOARD >> 14)  & 1; }
  static isPressedInsert() { return (*KEYBOARD >> 15)  & 1; }

  static isOnScrollLock() { return (*KEYBOARD >> 4) & 1; }
  static isOnNumLock() { return (*KEYBOARD >> 5)  & 1; }
  static isOnCapsLock() { return (*KEYBOARD >> 6)  & 1; }
  static isOnInsert() { return (*KEYBOARD >> 7)  & 1; }
};

unsigned int far *Keyboard::KEYBOARD= (unsigned int far *) 0x00000417;

void main() {
  clrscr();

  while (!kbhit() || getch()!=27) {
    gotoxy (1, 1);
    printf ("Нажата правая Shift : %i\n", Keyboard::isPressedRightShift());
    printf ("Нажата левая Shift  : %i\n", Keyboard::isPressedLeftShift());
    printf ("Нажата любая Shift  : %i\n", Keyboard::isPressedAnyShift());
    printf ("\n");
    printf ("Нажата правая Ctrl  : %i\n", Keyboard::isPressedRightCtrl());
    printf ("Нажата левая Ctrl   : %i\n", Keyboard::isPressedLeftCtrl());
    printf ("Нажата любая Ctrl   : %i\n", Keyboard::isPressedAnyCtrl());
    printf ("\n");
    printf ("Нажата правая Alt   : %i\n", Keyboard::isPressedRightAlt());
    printf ("Нажата левая Alt    : %i\n", Keyboard::isPressedLeftAlt());
    printf ("Нажата любая Alt    : %i\n", Keyboard::isPressedAnyAlt());
    printf ("\n");
    printf ("Нажата Scroll Lock  : %i\n", Keyboard::isPressedScrollLock());
    printf ("Нажата Num Lock     : %i\n", Keyboard::isPressedNumLock());
    printf ("Нажата Caps Lock    : %i\n", Keyboard::isPressedCapsLock());
    printf ("Нажата Insert       : %i\n", Keyboard::isPressedInsert());
    printf ("\n");
    printf ("Включен Scroll Lock : %i\n", Keyboard::isOnScrollLock());
    printf ("Включен Num Lock    : %i\n", Keyboard::isOnNumLock());
    printf ("Включен Caps Lock   : %i\n", Keyboard::isOnCapsLock());
    printf ("Включен Insert      : %i\n", Keyboard::isOnInsert());
    delay (55);
  }
}


Урок №1. Перемещение объекта.

Урок №2. Игра "Арканоид".

Урок №3. Обработка нажатий клавиш.

Урок №4. Системный таймер.

Урок №5. Немного о виртуальных функциях.

^^ наверх ^^

Дизайн: Красиков Виктор, kv630@mail.ru, ICQ - 319227
Местонахождение: Россия, респ. Бурятия, г. Улан-Удэ
Время на сервере: 14.12.19 16:52
Время в Улан-Удэ: 14.12.19 21:52
Время генерации страницы 0.003525 сек.