Часть 2. Использование API в VB.
На главную самоучителя
01.10.2005
Клавиатура и мышь.
Управление состоянием виртуальных клавиш.
Скачать исходник примера "Управление виртуальными клавишами"

Новые API-функции, используемые в этом параграфе.
Функция GetKeyboardState
Назначение Получает текущее состояние всех виртуальных клавиш из буфера клавиатуры.
Объявление Declare Function GetKeyboardState Lib "user32" (lpKeyState As Byte) As Long
Аргументы lpKeyState (Byte) - первый элемент байтового массива, состоящего из 256-ти элементов (0-255)
Функция GetKeyboardState возвращает не ноль при успешном выполнении операции и ноль при неудаче.
Функция SetKeyboardState
Назначение Задает текущее состояние всех виртуальных клавиш в буфере клавиатуры.
Объявление Declare Function SetKeyboardState Lib "user32" (lpKeyState As Byte) As Long
Аргументы lpKeyState (Byte) - первый элемент байтового массива, состоящего из 256-ти элементов (0-255)
Функция SetKeyboardState возвращает не ноль при успешном выполнении операции и ноль при неудаче. Только для Win98 и ниже.
Процедура keybd_event
Назначение Симулирует нажатие и отжатие кнопок клавиатуры (если они не нажаты с клавиатуры).
Объявление Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Аргументы bVk (Byte) - код виртуальной клавиши (от 1 до 254)
bScan (Byte) - аппаратный скан-код
dwFlags (Long) - флаги параметров функции:
Const KEYEVENTF_EXTENDEDKEY= &H1- если флаг установлен, скан-коду предшествует префиксный байт &HE0 (224)
Const KEYEVENTF_KEYUP=&H2 -если флаг установлен - клавиша была отпущена, если нет - была нажата.
dwExtraInfo (Long) - дополнительное 32-разрядное значение, связанное с нажатием клавиши
Процедура keybd_event ничего не возвращает.
Функция GetKeyState
Назначение Получает значение состояния виртуальной кнопки.
Объявление Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Аргументы nVirtKey (Long) - код виртуальных клавиш
Функция GetKeyState возвращает 0 или 1 при отжатой кнопке клавиатуры и -128 или -127 при нажатой.

Па-а-а-а-а-прошу Вашего драгоценного внимания и предлагаю рассмотреть задачу, а именно: управление из программы состоянием виртуальных клавиш. Для чего это нужно, я сразу не понял, но в моей почте был вопрос, как программно нажать кнопку NumLock. И вот я, пыхтя и сопя, просканировал свою клаву и вскоре нашел эту замечательную кнопку, да не просто кнопку, а аж три кнопки (еще и CapsLock со Scroll), а к каждой еще и по зеленой лампочке. При этом обнаружилась такая любопытная закономерность: эти лампочки отображали состояние каждой кнопки (нажал - лампа загорелась, NumLock включен, еще нажал - лампа погасла - NumLock выключен). Полдня я долбал по кнопкам, зажигая и гася лампы. И все-таки понял суть вопроса. Вместо того, чтобы амортизировать клавиатуру и портить кнопки, мы можем прямо из программы вчинять эту цветомузыку и получать безумное эстетическое наслаждение. Сказано - сделано. Сперва я хотел ограничиться инструкцией по программному нажатию кнопки NumLock, но по мере написания статьи понял, что надо рассмотреть этот вопрос расширенно, с тем чтобы было ясно, какие функции можно использовать, какие нет и как они работают или не работают.

Какой-то умник, который задолго до меня также мигал лампочками, и, видимо, чтобы отмазаться от начальства, и придать этому невинному занятию вид научных изысканий, назвал ряд кнопок на клавиатуре виртуальными клавишами. Нет, ты не понял. Не клавиши с лампочками он так назвал, а вообще клавиши, типа F1...F12, Home, End, Shift, Ctrl, Alt, стрелочки и т.д. Для тех, кто интересуется, я привел коды виртуальных клавиш здесь. Вот о них и пойдет речь.

Ну так вот. Исходя из своей безмерной вредности, а также, чтоб реально достичь понимания API, мы, конечно, сперва пойдем неправильным путем, ибо так поступают все нормальные люди. Итак, каверзы API.

Попытка первая: использование функций GetKeyboardState и SetKeyboardState - все вроде правильно, но решение задачи не достигается.

Цель попытки - познавательная. Исключительно проводим опыт. Если тебе некогда изучать Visual Basic, а надо "программить", то сразу переходи ко второй попытке.
Не мудрствуя лукаво, берем самые подходящие (на первый взгляд) функции для работы с клавиатурой. Как известно, информация о состоянии клавиатуры хранится в ее буфере. По общечеловеческой логике нам надо узнать текущее состояние виртуальных клавиш, взяв ее из буфера, и изменить нужную, не меняя состояния других, после чего снова запихнуть в буфер. Элементарно просто.

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

Создаем новый Exe-проект (а ты как думал?). Пока мы не будем делать программу для лампомигания, а посмотрим, как работает функция GetKeyboardState. Не считая нужным выделять объявления API (их у нас будет две) в отдельный модуль, лепим все на форму.
В секции (General) объявляем :


Option Explicit
Private Declare Function GetKeyboardState Lib "user32" (lpKeyState As Byte) As Long


Посмотрев на ее объявление, мы увидим один-единственный жалкий аргумен т- lpKeyState, однако аргумент этот представляет собой первый (т.е. нулевой) элемент аж целого массива из 256 (0-255) элементов типа Byte. Поэтому для использования функции нам нужно этот массив объявить. Дадим ему имя, например, MassiveKey:

Dim MassiveKey(0 To 255) As Byte

В этот массив функция GetKeyboardState и записывает состояние всех виртуальных клавиш в виде циферек. Ну мы, хотя конечно, ребята простые, но провести себя на мякине не дадим. Давай пощупаем это дело руками и убедимся во всем так сказать, визуально. Положим на форму кнопку Command1 и Text1 со свойствами Multiline=True и ScrollBars=2-Вертикаль, да возьмем и выведем в наш текстбокс содержимое массива. Да не просто выведем, а для каждого элемента сначала покажем номер в десятичнойсистеме счисления, потом его же в шестнадцатеричной, а потом уж и само содержимое этого элемента. Мы, это самое, тоже цикл от задержки отличаем. Итак, пишем в процедуре кнопки Command1:

Private Sub Command1_Click()
'переменная для цикла
Dim X As Integer
Text1 = ""
'вызываем нашу функцию, которая заполняет массив
GetKeyboardState MassiveKey(0)
For X = 0 To 255
'выводим в текстбокс содержимое массива
Text1 = Text1 & "Элемент # " & X & " (" & Hex(X) & ") = " & MassiveKey(X) & vbCrLf
Next X
End Sub


Запускаем проект и нажимаем кнопку Command1.
Ну вот, теперь мы видим, что действительно нас здесь не обманули, есть циферки в массиве. А кроме того, то что у нас получилось в скобочках, т.е. номер элемента массива в шестнадцатеричной форме - это не что иное, как коды виртуальных клавиш. Т.е код виртуальных клавиш - это просто напросто номер элемента массива, представленный в шестнадцатеричной форме. Вот оно как вышло.
Надо сказать, что функция GetKeyboardState делает мгновенный снимок клавиатуры и дальнейшие изменения состояния клавиатуры не отражает до следующего ее (функции, а не клавиатуры) вызова.
Далее проводим над этим всем опыты. Жмакаем по клаве или даже удерживаем некоторые кнопки, после чего щелкаем по кнопке Command1, чтобы вывести новое состояние виртуальных клавиш и пытаемся разобраться, как изменяются циферки в массиве. А изменяются они очень любопытно. Оказывается у них целых четыре состояния, а не два, как нам казалось, когда мы, вдавливая потным пальцем кнопку SHIFT (ALT, CTRL), мочили монстров в очередной стрелялке.
Получается, да ты и сам можешь это наблюдать, что, та же кнопка SHIFT (Код VK_SHIFT=&H10, а это элемент нашего массива номер 16) меняет свое состояние следующим образом:
0 - не нажата
129 - нажата и удерживается
1 - отпущена
128 - опять нажата и удерживается

и далее вся песня повторяется.
Это касается всех виртуальных кнопок, даже наш NumLock (который с лампочкой) имеет два нажатых состояния и два отпущенных, хотя вроде бы зачем, но так есть.


Ну вот, полдела сделано. Остается вторая половина - изменить нужный элемент массива и запихнуть наш массив обратно в клавиатуру. Пробуем. Для этого в поле нашего зрения попала функция SetKeyboardState.
Пишем ее в секции (General):

Private Declare Function SetKeyboardState Lib "user32.dll" (lpKeyState As Byte) As Long

Нам нужна константа, которая, собственно и определит номер элемента нашего байтового массива, содержащего состояние этой кнопки:

Const VK_NUMLOCK = &H90

От уже рассмотренной нами GetKeyboardState новая функция SetKeyboardState абсолютно ничем не отличается. Просто запихивает наш байтовый массив в буфер клавиатуры. Вроде это то, что нам надо.
Давай, клади на форму кнопку Command2, в процедуре каковой мы и будем использовать функцию SetKeyboardState.


Private Sub Command2_Click()
'делаем мгновенный снимок виртуальных клавиш непосредственно перед изменением
GetKeyboardState MassiveKey(0)
'меняем состояние элемента 144, т.е. &H90 или VK_NUMLOCK на противоположный,
'игнорируя промежуточные состояния

If MassiveKey(VK_NUMLOCK) = 1 Then
MassiveKey(VK_NUMLOCK) = 0
ElseIf MassiveKey(VK_NUMLOCK) = 0 Then
MassiveKey(VK_NUMLOCK) = 1
End If
'и наконец отправляем массив в буфер клавиатуры
SetKeyboardState MassiveKey(0)
End Sub


Пора пожинать плоды просвещения. Запускаем проект и жмакамем на пипки. Как мы можем видеть, содержимое буфера клавиатуры действительно изменилось. Но пользы нам от этого никакой. Потому что кнопка не сработала и лампочка на погасла (не зажглась). Если ты работаешь по Windows98, то этой трагедии возможно и не увидишь, потому что там эти функции
вроде как работают. Но в операционных системах Windows2000, NT, XP нам придется плохо. Мы можем сделать из этого следующий вывод: функция SetKeyboardState устарела и для использования совершенно непригодна. Функция GetKeyboardState интересна для получения информации о состоянии буфера клавиатуры, но не более того. А посему, утерев горькие слезы разочарования, плюнем на указанные функции и окинем окрестности затуманенным взором в поисках новых. (Для тех, кому небезразлична судьба этих функций, я положил исходник наших изысканий здесь).

Попытка вторая: использование процедуры keybd_event и функции GetKeyState - получаем результат.

Как говорится "свято место пусто не бывает". Попробуем использовать функцию keybd_event. Сразу бросается в глаза, что с ней что-то не то. А именно, что по сути это не функция, а процедура, поэтому в объявлении вместо ключевого слова Function используется слово Sub. Кроме того, она не возвращает никакого значения. Я уж не говорю, что название функции (процедуры) такое уродское. Но это ни в коем случае не помешает нам использовать ее достоинства. Ведь именно она позволит нам нажимать и, заметь, даже и отпускать любые кнопочки:

Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

Она имеет следующие аргументы:
bVK - просто код виртуальной кнопки, с этим мы уже познакомились выше
vbScan - аппаратный скан код - зарезервированное значение (надо устанавливать ноль)
dwFlags - флаги параметров функции. Имеется два флага:
- KEYEVENTF_EXTENDEDKEY, равный &H1, который устанавливает, что скан-коду предшествует префиксный байт &HE0 (224) и обычно не используется, и
- KEYEVENTF_KEYUP, равный &H2, наличие которого говорит, что кнопка отпускается, а отсутствие (т.е. ноль) - что кнопка нажимается
dwExtraInfo - дополнительное 32-разрядное значение, ассоциируемое с событием клавиатуры и обычно неиспользуемое.
Из этого мы видим очень простое использование этой функции: нам надо вставить в функцию скан-код виртуальной клавиши и один флаг KEYEVENTF_KEYUP (если кнопка опускается) или ноль (если кнопка нажимается). Остальные аргументы - нули.
Иными словами, технология использования функции keybd_event сводится к следующему: определяем в (General) константу кнопки NumLock
Const VK_NUMLOCK = &H90,
а затем
keybd_event VK_NUMLOCK, 0, 0, 0 - нажимаем кнопочку NumLock
keybd_event VK_NUMLOCK, 0, KEYEVENTF_KEYUP, 0 - отпускаем кнопочку NumLock

Ну нам также желательно получить информацию о нужной нам кнопочке, нажата она или отжата. Для этого нам пригодится функция GetKeyState:

Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer

Как мы видим, здесь один-единственный аргумент nVirtKey, который представляет из себя код нужной нам виртуальной клавиши. Но возвращает она четыре разных состояния клавиш, о чем мы уже говорили при анализе функции GetKeyboardState. Только возвращаемые значения чуть-чуть другие:
0 - не нажата
-127 - (минус 127) нажата и удерживается
1 - отпущена
-128 - (минус 128) опять нажата и удерживается

Давай сделаем проект, позволяющий нажимать кнопки CAPSLOCK, NUMLOCK, SCROLL, а также SHIFT, ALT и CTRL. Кроме того, хотелось бы еще и отобразить на экране состояние этих кнопок, если они нажаты с клавиатуры.
Нам на форме понадобятся шесть CheckBox'ов для иммитации нажатия клавиш:
Check1 - NumLock
Check2 - CapsLock
Check3 - Scroll
Check4 - Shift
Check5 - Alt
Check6 - Ctrl
(при этом их свойство Style надо установить в 1-Graphical, чтобы они выглядели как кнопки),

а также три TextBox'а: в Text1 будем выводит код нажатой (отжатой) кнопки, в Text2 - значение, возвращаемое функцией GetKeyState, а Text3 - просто для ввода любого текста, чтобы видеть, что мы действительно нажали кнопку (например, CapsLock или Shift). Сперва, конечно, в секции (General) объявим все то, что нужно объявить:

Option Explicit
'две наши API-функции
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
'значения констант, определяющих виртуальные клавиши
Const VK_NUMLOCK = &H90
Const VK_CAPITAL = &H14
Const VK_SCROLL = &H91
Const VK_CONTROL = &H11
Const VK_SHIFT = &H10
Const VK_MENU = &H12
'константу флага функции keybd_event
Const KEYEVENTF_KEYUP = &H2
'и некоторые переменные
Dim v As Integer 'сюда будет возвращать свое значение функция GetKeyState
Dim PrBut As Integer 'определяет состояние CheckBox'а: 0 или 1
Dim Flag As Boolean 'флаг, показывающий, что программой нажимались кнопки Shift, Alt или Ctrl
Dim MouseBut As Boolean 'флаг, меняющий свое значение, в зависимости от состояния (нажато - не нажато)
'кнопок Shift, Alt или Ctrl


Далее поцедура Form_Load. Скажу честно, я особенно с ней не напрягался. Используется она здесь для того, чтобы
- во-первых, установить свойство формы .KeyPreview, которое передает все события клавиатуры для объектов формы непосредственно на форму. При установке ее в значение True события клавиатуры получает сначала форма, а потом уже элемент управления (в нашем случае CheckBox). Это позволит нам перехватывать нажатие кнопок по событиям на форме (процедуры Form_KeyDown, Form_KeyUp), а не описывать их для каждого CheckBox'а.
- во-вторых, показать на экране текущее состояние кнопок NumLock, CapsLock и Scroll. Сразу скажу, что это довольно условно, поскольку я предполагаю, что в момент загрузки нашей программы эти клавиши будут либо нажаты, либо отжаты (значение 0 или 1). Поэтому я сразу присваиваю свойству .Value CheckBox'ов то, что возвращается функцией GetKeyState. Если ты будешь давить в это время на кнопку пальцем, то функция GetKeyState возвратит -127 ли -128, что для CheckBox.Value неприемлемо и вызовет ошибку.

Private Sub Form_Load()
Form1.KeyPreview = True
Check1.Value = GetKeyState(VK_NUMLOCK)
Check2.Value = GetKeyState(VK_CAPITAL)
Check3.Value = GetKeyState(VK_SCROLL)
End Sub

Теперь пишем по две процедуры для каждого из трех CheckBox'ов 1-3 (кнопок NumLock, CapsLock и Scroll). Первая процедура обрабатывает событие _MouseDown и запускает функцию keybd_event для нажатия кнопки, а вторая - событие _MouseUp - и запускает ее же для отжатия.

Private Sub Check1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_NUMLOCK, 0, 0, 0
End Sub
Private Sub Check1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_NUMLOCK, 0, KEYEVENTF_KEYUP, 0
End Sub

Private Sub Check2_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_CAPITAL, 0, 0, 0
End Sub
Private Sub Check2_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_CAPITAL, 0, KEYEVENTF_KEYUP, 0
End Sub

Private Sub Check3_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_SCROLL, 0, 0, 0
End Sub
Private Sub Check3_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
keybd_event VK_SCROLL, 0, KEYEVENTF_KEYUP, 0
End Sub

Эти процедуры позволят нам нажимать кнопки NumLock, CapsLock и Scroll кликами по CheckBox'ам 1-3.
Для нажатия на кнопки Shift, Ctrl или Alt будем использовать процедуры события_MouseDown (хотя можно и _MouseUp) для CheckBox'ов 4-6. Так как их содержание абсолютно идентично, удобнее вынести код в собственную отдельную процедуру PushMouseButton. Мы будем вызывать ее, просто передавая в нее соответствующий код клавиши из процедур CheckBox'ов 4-6.


Private Sub Check4_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
PushMouseButton (VK_SHIFT)
End Sub

Private Sub Check5_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
PushMouseButton (VK_MENU)
End Sub

Private Sub Check6_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
PushMouseButton (VK_CONTROL)
End Sub

Теперь создадим процедуру PushMouseButton.

Private Sub PushMouseButton(GettingCode As Integer)
MouseBut = True 'показывает, что мы нажали из программы именно Shift, Ctrl или Alt
'Далее, в зависимости от меняющегося значения флага Flag, мы или нажимаем,
'или отпускаем кнопки Shift, Ctrl или Alt

If Flag = False Then
keybd_event GettingCode, 0, 0, 0
Flag = True
ElseIf Flag = True Then
keybd_event GettingCode, 0, KEYEVENTF_KEYUP, 0
Flag = False
End If
End Sub

Для отслеживания нажатий клавиатуры не из программы, а путем, так сказать, физического давления пальцем непосредственно на кнопки. Используем две процедуры для событий формы (ведь не зря ж мы включили свойство .KeyPreview) - _KeyDown и _KeyUp.

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
DetectButton (KeyCode)
End Sub

Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
DetectButton (KeyCode)
End Sub

Как видишь, в них ничего нет, кроме вызова нашей собственной процедуры DetectButton, куда мы передаем код нажатой клавиши. Некоторая ее сложность вызвана тем, что отображение на экране разных нажатых клавиш различно. Смысл ее в том, чтобы получить корректное значение переменной PrBut, передача которой в свойства CheckBox'ов .Value прведет к правильному отображению кнопки на форме.

Во-первых, кнопки NumLock, CapsLock и Scroll должны отображаться в двух положениях
- включено, т.е. состояние нажато и затем отпущено (функция GetKeyState возвращает значение - минус 127 и 1), при этом CheckBox выглядит нажатым
- и выключено, т.е. состояние снова нажато и отпущено (функция GetKeyState возвращает значение - минус 128 и 0), при этом CheckBox выглядит отжатым.
Это независимо от того, из программы произошло нажатие или с клавиатуры.

Во-вторых, кнопки Shift, Ctrl или Alt должны отображаться по-другому, т.е. таким образом, чтобы при нажатии с клавиатуры CheckBox точно бы иммитировал положение кнопки, а при нажатии из программы кнопка зависала, оставляя возможность ввода в Text3 данных, например, с включенным Shift'ом. Это достигается введением в условие Select Case флага MouseBut, который используется для идентификации того факта, что нажатие произошло именно из программы.
(
Вообще-то условие Select Case я использую чрезвычайно редко, но тут чего-то понесло.)

Private Sub DetectButton(GettingCode As Integer)
v = GetKeyState(GettingCode)
If v < 0 Then
Text1 = "Нажата кнопка с кодом " & GettingCode
Else
Text1 = "Отпущена кнопка с кодом " & GettingCode
End If
If MouseBut = True Then
'нажатие кнопок Shift, Ctrl или Alt из программы
Select Case v
Case -127, -128
PrBut = 0
Case 0, 1
PrBut = 1
End Select
MouseBut = False
Else
Select Case
GettingCode
'нажатие кнопок NumLock, CapsLock и Scroll все равно откуда
Case VK_NUMLOCK, VK_CAPITAL, VK_SCROLL
Select Case v
Case 0, -128
PrBut = 0
Case 1, -127
PrBut = 1
End Select
Case Else

'нажатие кнопок Shift, Ctrl или Alt кнопками клавиатуры
Select Case v
Case 0, 1
PrBut = 0
Case -128, -127
PrBut = 1
End Select
End Select
End If

Text2 = "Значение, возвращенное" & vbCrLf & "GetKeyState = " & v
If GettingCode = VK_NUMLOCK Then Check1.Value = PrBut
If GettingCode = VK_CAPITAL Then Check2.Value = PrBut
If GettingCode = VK_SCROLL Then Check3.Value = PrBut
If GettingCode = VK_SHIFT Then Check4.Value = PrBut
If GettingCode = VK_MENU Then Check5.Value = PrBut
If GettingCode = VK_CONTROL Then Check6.Value = PrBut
End Sub

Реализация этой задачи не претендует на идеальность, зато, как мне кажется, дает ясное представление об использовании API-функций keybd_event и GetKeyState. Исходник примера можно скачать вверху страницы.


Copyright © 2005 4us
Сайт создан в системе uCoz