|
|
|
|
|
|
Vielleicht werden sie schon darüber gestolpert sein, dass die Sendkeys-Anweisung unter Windows 2000 manchmal nicht so funktioniert wie erwartet. Dahinter steckt ein Bug, der Microsoft bereits bekannt ist - der Artikel Q276346 der Knowledge-Base weist darauf hin. Darin werden auch Wege zur Umgehung des Problems angedeutet - sehr hilfreich und ein wirklicher Ersatz für die SendKeys-Anweisung sind sie jedoch nicht.
Der Ansatz, ein einzelnes Tastatur-Ereignis über die API-Funktion keybd_event zu versenden, ist allerdings goldrichtig. Dabei ist "Tastatur-Ereignis" durchaus wörtlich zu verstehen: Das Niederdrücken und das Loslassen einer Taste sind zwei verschiedene Ereignisse. Wie bei SendKeys auch werden die gesendeten Tastendrücke an das Fenster bzw. Steuerelement übergeben, das gerade den Fokus inne hat. Zum Simulieren des Niederdrückens einer Taste übergeben Sie der Funktion keybd_event im ersten Parameter den zu sendenden KeyCode der Taste - in den übrigen Parametern übergeben Sie 0. Zum Simulieren des Loslassens einer Taste übergeben Sie ebenfalls den KeyCode im ersten Parameter, und dazu im dritten Parameter die Konstante KEYEVENTF_KEYUP. Die Tabulator-Taste senden Sie beispielsweise so:
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
ByVal bScan As Byte, ByVal dwFlags As Long, _
ByVal dwExtraInfo As Long)
keybd_event vbKeyTab, 0, 0, 0
keybd_event vbKeyTab, 0, KEYEVENTF_KEYUP, 0
Genau so, wie Sie in den KeyDown- und KeyUp-Ereignissen über das Niederdrücken oder Loslassen der Umschalt-Taste, der Strg-Taste und der Alt-Taste informiert werden, müssen Sie auch per keybd_event das Niederdrücken und Loslassen dieser Tastens senden. Den Großbuchstaben "A" senden Sie demzufolge so:
keybd_event vbKeyShift, 0, 0, 0
keybd_event vbKeyA, 0, 0, 0
keybd_event vbKeyA, 0, KEYEVENTF_KEYUP, 0
keybd_event vbKeyShift, 0, KEYEVENTF_KEYUP, 0
Im Vergleich zu SendKeys ist das natürlich eine sehr mühselige Angelegenheit. Die folgende Prozedur SendKey vereinfacht das Senden einer Taste um einiges. Im ersten Parameter übergeben Sie den KeyCode und im optionalen zweiten Parameter können Sie eine Kombination der Shift-Masken (Konstanten ShiftConstants: vbShiftMask, vbCtrlMask, vbAltMask) angeben. Im letzten optionalen Parameter Repeats können Sie noch festlegen, wie oft der Tastendruck wiederholt werden soll.
Public Sub SendKey(ByVal KeyCode As KeyCodeConstants, _
Optional ByVal Shift As ShiftConstants, _
Optional ByVal Repeats As Long = 1)
Dim l As Long
Const KEYEVENTF_KEYUP = &H2
If Repeats >= 1 Then
If CBool(Shift And vbShiftMask) Then
keybd_event vbKeyShift, 0, 0, 0
End If
If CBool(Shift And vbCtrlMask) Then
keybd_event vbKeyControl, 0, 0, 0
End If
If CBool(Shift And vbAltMask) Then
keybd_event vbKeyMenu, 0, 0, 0
End If
For l = 1 To Repeats
keybd_event KeyCode, 0, 0, 0
keybd_event KeyCode, 0, KEYEVENTF_KEYUP, 0
Next 'l
If CBool(Shift And vbAltMask) Then
keybd_event vbKeyMenu, 0, KEYEVENTF_KEYUP, 0
End If
If CBool(Shift And vbCtrlMask) Then
keybd_event vbKeyControl, 0, KEYEVENTF_KEYUP, 0
End If
If CBool(Shift And vbShiftMask) Then
keybd_event vbKeyShift, 0, KEYEVENTF_KEYUP, 0
End If
End If
End Sub
Einen kleinen Haken hat diese Prozedur allerdings. Woher bekommen Sie die KeyCodes für alle Tasten? In den KeyCode-Konstanten in Visual Basic finden sie bereits eine ganze Reihe von KeyCodes vordefiniert - aber bei weitem nicht alle. Neben den KeyCodes für die Tasten A bis Z und die Tasten 0 bis 9 finden Sie auch nahezu alle Sondertasten. Doch es fehlen beispielsweise die KeyCodes für die Umlaute, die Interpunktionszeichen, die Zeichen ab Ascii 128 und mehr. Natürlich können Sie diese ermitteln, indem Sie sich die entsprechenden KeyCodes in einem KeyDown-Ereignis ansehen.
Einfacher und sicherer ist es jedoch, diese Ermittlungs-Arbeit Windows zu überlassen, genauer gesagt, der API-Funktion VkKeyScan, der Sie den Ascii-Wert eines Zeichens übergeben können. Im Rückgabewert der Funktion finden Sie sowohl den zu einem Zeichen passenden KeyCode als auch die Information darüber, welche Shift-Tastenkombination zur Erzeugung des Zeichens benötigt wird. Der Rückgabewert ist allerdings nicht sehr VB-freundlich. Es ist ein Integer-Wert, in dessen niederwertigem Byte der KeyCode und in dessen höherwertigem Byte die Shift-Kombination abgelegt sind.
Ein einfacher Trick zur Zerlegung eines solchen Integer-Wertes nutzt zwei benutzerdefinierte Variablen. Die eine besteht nur aus einem Integer-Element, die andere aus zwei Byte-Elementen. Sie weisen dem Integer-Element den Integer-Wert zu, übertragen den Inhalt der Variablen mit der VB-Anweisung LSet an die andere Variable, und schon können Sie aus dieser die beiden Byte-Bestandteile ohne Schwierigkeit auslesen. Auch das alles packen wir wieder in eine kompakte Funktion:
Private Declare Function VkKeyScan Lib "user32" _
Alias "VkKeyScanA" (ByVal cChar As Byte) As Integer
Private Type IntegerType
i As Integer
End Type
Private Type HiLowByteType
LowByte As Byte
HiByte As Byte
End Type
Public Function GetKeyCodeFromAscii(ByVal KeyAscii As Long, _
ShiftState As Long)
Dim nHL As HiLowByteType
Dim nI As IntegerType
nI.i = VkKeyScan(KeyAscii)
LSet nHL = nI
ShiftState = ShiftState Or nHL.HiByte
GetKeyCodeFromAscii = nHL.LowByte
End Function
Das Senden des Zeichens "Ö" sähe nun unter Verwendung dieser Funktion und der Prozedur SendKey so aus:
Dim nKeyCode As Long
Dim nShift As Long
nKeyCode = GetKeyCodeFromAscii(Asc("Ö"), nShift)
SendKey nKeyCode, nShift
Ist Ihnen das auch immer noch zu umständlich? Keine Frage - auch dafür gibt es eine vereinfachende Prozedur:
Public Sub SendAscii(ByVal Ascii As Long, _
Optional ByVal Shift As ShiftConstants, _
Optional ByVal Repeats As Long = 1)
Dim nKeyCode As Long
nKeyCode = GetKeyCodeFromAscii(Ascii, Shift)
SendKey nKeyCode, Shift, Repeats
End Sub
Oder wenn Sie ein Zeichen nicht als Ascii-Code, sondern gleich als String übergeben möchten, verwenden Sie die leicht modifizierte Alternative SendChar, zusammen mit der ebenfalls leicht modifizierten Variante GetKeyCodeFromChar:
Public Sub SendChar(Char As String, _
Optional ByVal Shift As ShiftConstants, _
Optional ByVal Repeats As Long = 1)
Dim nKeyCode As Long
nKeyCode = GetKeyCodeFromChar(Char, Shift)
SendKey nKeyCode, Shift, Repeats
End Sub
Public Function GetKeyCodeFromChar(Char As String, _
ShiftState As Long)
Dim nHL As HiLowByteType
Dim nI As IntegerType
nI.i = VkKeyScan(Asc(Char))
LSet nHL = nI
ShiftState = ShiftState Or nHL.HiByte
GetKeyCodeFromChar = nHL.LowByte
End Function
Nun ist es wohl immer noch viel zu umständlich, Zeichen für Zeichen einzeln zu senden. Aber auch dafür haben wir schon eine kleine Prozedur für Sie vorbereitet:
Public Sub SendString(Str As String, _
Optional Shift As ShiftConstants)
Dim l As Long
For l = 1 To Len(Str)
SendChar Mid$(Str, l, 1), Shift
Next 'l
End Sub
Sie wundern sich vielleicht ein wenig über die Shift-Parameter der Prozeduren SendAscii, SendChar und SendString. Sie haben ja recht - eigentlich könnten diese durchaus entfallen. Doch so haben Sie die Möglichkeit, ein Zeichen unabhängig von der Groß-/Kleinschreibung anzugeben und diese statt dessen im Shift-Parameter festzulegen. Und natürlich brauchen Sie die Shift-Konstanten, wenn Sie eine Kombination aus Zeichen und Strg- und/oder Alt-Taste senden möchten.
Den Komfort des VB-SendKeys haben wir allerdings immer noch nicht ganz erreicht. Sie können zwar mittlerweile ganze Zeichenfolgen senden, aber nicht mittendrin Sondertasten etwa zur Cursor-Steuerung unterbringen. Wie wäre es damit, wenn Sie ein Array übergeben könnten, das Zeichen, Zeichenfolgen und KeyCodes von Zeichen und Sondertasten bunt gemischt enthält? Oder wenn Sie diese Elemente in einer Collection sammeln und diese übergeben könnten? Oder wenn Sie alles wirklich bunt mischen könnten, also Arrays, Collections, einzelne Zeichen, Zeichenfolgen und KeyCodes? Selbstverständlich ist auch das kein Problem - die Prozedur SendKeystrokes und ihre Varianten sind ungemein flexibel:
Public Enum ShiftStateConstants
skShiftStateShift = 256
skShiftStateCtrl = 512
skShiftStateAlt = 1024
skShiftStateOn = 2048
skShiftStateShiftOn = 256 Or skShiftStateOn
skShiftStateCtrlOn = 512 Or skShiftStateOn
skShiftStateAltOn = 1024 Or skShiftStateOn
skShiftStateOff = 4096
skShiftStateShiftOff = 256 Or skShiftStateOff
skShiftStateCtrlOff = 512 Or skShiftStateOff
skShiftStateAltOff = 1024 Or skShiftStateOff
skShiftStateAscii = 8192
End Enum
Public Sub SendKeystrokes(Keystroke As Variant, _
Optional ShiftState As Long)
Dim l As Long
Dim nI As IntegerType
Dim nHL As HiLowByteType
Dim nShift As Long
Dim nKeyCode As Long
Dim nAscii As Long
Const kShiftStateOn = 8
Const kShiftStateOff = 16
Const kShiftStateAscii = 32
Const kShiftMask = vbShiftMask Or vbCtrlMask Or vbAltMask
If IsArray(Keystroke) Then
SendKeystrokesArr Keystroke, ShiftState
ElseIf IsObject(Keystroke) Then
If TypeOf Keystroke Is Collection Then
SendKeystrokesColl CVar(Keystroke), ShiftState
End If
Else
Select Case VarType(Keystroke)
Case vbString
SendString CStr(Keystroke), ShiftState
Case vbByte, vbLong, vbInteger
If IsNumeric(Keystroke) Then
nI.i = CInt(Keystroke)
LSet nHL = nI
nShift = nHL.HiByte And kShiftMask
If (nHL.HiByte And kShiftStateOn) = kShiftStateOn Then
ShiftState = ShiftState Or nShift
nShift = ShiftState
ElseIf (nHL.HiByte And kShiftStateOff) = _
kShiftStateOff Then
ShiftState = ShiftState And Not nShift
nShift = ShiftState
End If
If (nHL.HiByte And kShiftStateAscii) Then
SendAscii nHL.LowByte, nShift
Else
SendKey nHL.LowByte, nShift
End If
End If
End Select
End If
End Sub
Public Sub SendKeystrokesArr(Keystrokes As Variant, _
Optional ByVal ShiftState As Long)
Dim nKeyStroke As Variant
For Each nKeyStroke In Keystrokes
SendKeystrokes nKeyStroke, ShiftState
Next
End Sub
Public Sub SendKeystrokesColl(Keystrokes As Collection, _
Optional ByVal ShiftState As Long)
Dim nKeyStroke As Variant
For Each nKeyStroke In Keystrokes
SendKeystrokes nKeyStroke, ShiftState
Next
End Sub
Public Sub SendKeystrokesList _
(ParamArray Keystrokes() As Variant)
Dim nKeyStroke As Variant
Dim nShiftState As Long
For Each nKeyStroke In Keystrokes
SendKeystrokes nKeyStroke, nShiftState
Next
End Sub
Habe ich Sie mit der Enumeration ShiftStateConstants und den diversen Auswertungen in der Prozedur SendKeystrokes verwirrt? Dann will Sie sofort darüber aufklären, was es damit auf sich hat. Das damit gelöste Problem ist nämlich folgendes: Sie brauchen eine Möglichkeit, zu einem einzelnen KeyCode einen ShiftStatus angeben zu können. Denn die SendKey-Prozedur sendet ja jedes Zeichen einzeln und erwartet dazu auch den gewünschten ShiftStatus. Wir machen es dazu ähnlich wie die API-Funktion VkKeyScan, indem wir die benötigten Informationen in einen einzigen Wert hineinstopfen. Da für KeyCodes ja nur die Werte bis 255 verwendet werden, haben wir bei einem Integer- oder Long-Wert noch jede Menge Bits übrig.
Kombinieren Sie nun statt der gewohnten Shift-Konstanten die neuen Enumerations-Konstanten skShiftStateShift, skShiftStateCtrl und skShiftStateAlt mit einem KeyCode - in SendKeystrokes wird für die Auswertung gesorgt. Wollen Sie statt eines KeyCodes einen Ascii-Code übergeben, kombinieren Sie das ganze noch mit der Konstanten skShiftStateAscii. Wollen Sie gleich eine ganze Reihe von Zeichen mit der gleichen Shift-Kombination senden, kombinieren Sie den ersten Wert einer solchen Reihe noch mit der Konstanten skShiftStateOn, und den letzten Wert der Reihe mit skShiftStateOff. Oder fügen Sie die beiden Konstanten nur mit einer ShiftState-Kombination und ohne KeyCode- oder Ascii-Wert paarweise ein, und dazwischen schieben Sie andere Elemente, oder gar wieder ein ganzes Array oder eine ganze Collection - alle sich darunter bzw. darin befindlichen Werte werden automatisch mit der eingeschalteten ShiftState-Kombination gesendet, außer sie haben bei einem Wert ausdrücklich eine eigene Kombination angegeben.
Das klingt fürchterlich kompliziert - ist aber halb so schlimm. Schauen wir uns dazu einmal ein paar Beispiele an.
Am einfachsten ist die Prozedur SendKeystrokesList handzuhaben. Ihr übergeben Sie einfach eine Reihe von Elementen, jeweils durch Kommata getrennt - eben eine einfache Parameter-Liste:
SendKeystrokesList "H", "a", "ll", "o", "Welt", "!"
Oder das gleiche Ergebnis, anders "formuliert":
SendKeystrokesList skShiftStateShift Or vbKeyH, _
"a", "ll", "o ", Array("Welt", "!")
Oder wenn die Welt "groß" sein soll:
SendKeystrokesList skShiftStateShift Or vbKeyH, _
"a", "ll", "o ", skShiftStateShiftOn, Array("Welt", "!"), _
skShiftStateShiftOff
Hierbei können Sie auch das letzte Element weglassen - das abschließende Abschalten des Shift-Zustandes ist nicht notwendig.
Nehmen wir eine Collection hinzu und gehen zur Prozedur SendKeyStrokesArr über:
Dim nColl As Collection
Set nColl = New Collection
With nColl
.Add "l"
.Add "lo"
.Add vbKeySpace
End With
SendKeystrokesArr Array(skShiftStateShift Or vbKeyH, "a", _
nColl, Array("Welt", "!")
Oder sollen die beiden Wörter durch eine Zeilenschaltung getrennt werden?
Dim nColl As Collection
Set nColl = New Collection
With nColl
.Add "Hallo"
.Add vbKeyReturn
.Add Array(skShiftStateShift Or skShiftStateAscii _
Or Asc("w"), "elt")
.Add "!"
End With
SendKeystrokesColl nColl
Sie sehen, es gibt vielfältige Möglichkeiten, die Welt zu begrüßen... Ach, Sie hätten doch lieber eine kompatible SendKeys-Prozedur, damit Sie Ihre mühsam ausgetüftelten SendKeys-Strings weiterhin und unverändert verwenden können? Na gut - hier haben Sie diese auch noch. Aber machen Sie mich bitte nicht dafür verantwortlich, wenn nicht alle Ihre Tricksereien Ihrer SendKeys-Strings einwandfrei umgesetzt werden. Ich habe mich lediglich an die Dokumentation gehalten und keine (!) ausgiebigen Tests mit den verschiedensten Anwendungen durchgeführt.
Public Sub SendKeys(Keys As String)
Dim l As Long
Dim nPos As Long
Dim nKey As String
Dim nShiftState As ShiftConstants
Dim nKeyCode As KeyCodeConstants
Dim nPosSpace As Long
Dim nChar As String
Dim nRepeat As String
Dim nRepeats As Long
Dim ll As Long
Dim nShift As Long
l = 1
Do
DoEvents
nChar = Mid$(Keys, l, 1)
Select Case nChar
Case "+"
nShiftState = nShiftState Or vbShiftMask
l = l + 1
Case "^"
nShiftState = nShiftState Or vbCtrlMask
l = l + 1
Case "%"
nShiftState = nShiftState Or vbAltMask
l = l + 1
Case "~"
SendKey vbKeyReturn, nShiftState
nShiftState = 0
l = l + 1
Case "{"
nRepeats = 1
nPos = InStr(l + 1, Keys, "}")
nKey = Trim$(Mid$(Keys, l + 1, nPos - l - 1))
If Len(nKey) = 0 Then
If Mid$(Keys, nPos + 1, 1) = "}" Then
nKey = "}"
End If
End If
If Len(nKey) Then
Select Case UCase$(nKey)
Case "SHIFT", "UMSCHALT", "UMSCH"
nKeyCode = vbKeyShift
Case "CTRL", "STRG", "CONTROL"
nKeyCode = vbKeyControl
Case "ALT"
nKeyCode = vbKeyMenu
Case "BACKSPACE", "BS", "BKSP", "BACK"
nKeyCode = vbKeyBack
Case "BREAK"
nKeyCode = vbKeyPause
Case "CAPSLOCK"
nKeyCode = vbKeyCapital
Case "DELETE", "DEL"
nKeyCode = vbKeyDelete
Case "DOWN"
nKeyCode = vbKeyDown
Case "END"
nKeyCode = vbKeyEnd
Case "ENTER"
nKeyCode = vbKeyReturn
Case "ESC", "ESCAPE"
nKeyCode = vbKeyEscape
Case "HOME"
nKeyCode = vbKeyHome
Case "INSERT", "INS"
nKeyCode = vbKeyInsert
Case "LEFT"
nKeyCode = vbKeyLeft
Case "NUMLOCK"
nKeyCode = vbKeyNumlock
Case "PGDN"
nKeyCode = vbKeyPageDown
Case "PGUP"
nKeyCode = vbKeyPageUp
Case "PRTSC"
nKeyCode = vbKeySnapshot
Case "RIGHT"
nKeyCode = vbKeyRight
Case "SCROLLLOCK"
nKeyCode = vbKeyScrollLock
Case "TAB"
nKeyCode = vbKeyTab
Case "UP"
nKeyCode = vbKeyUp
Case "F1"
nKeyCode = vbKeyF1
Case "F2"
nKeyCode = vbKeyF2
Case "F3"
nKeyCode = vbKeyF3
Case "F4"
nKeyCode = vbKeyF4
Case "F5"
nKeyCode = vbKeyF5
Case "F6"
nKeyCode = vbKeyF6
Case "F7"
nKeyCode = vbKeyF7
Case "F8"
nKeyCode = vbKeyF8
Case "F9"
nKeyCode = vbKeyF9
Case "F10"
nKeyCode = vbKeyF10
Case "F11"
nKeyCode = vbKeyF11
Case "F12"
nKeyCode = vbKeyF12
Case "F13"
nKeyCode = vbKeyF13
Case "F14"
nKeyCode = vbKeyF14
Case "F15"
nKeyCode = vbKeyF15
Case "F16"
nKeyCode = vbKeyF16
Case "{", "}", "[", "]", "+", "^", "%"
nKeyCode = GetKeyCodeFromAscii(nKey, nShiftState)
Case Else
nPosSpace = InStr(nKey, " ")
If nPosSpace Then
nRepeat = Trim$(Mid$(nKey, nPosSpace + 1))
If IsNumeric(nRepeat) Then
nKey = Left$(nKey, nPosSpace - 1)
Select Case UCase$(nKey)
Case "BACKSPACE", "BS", "BKSP"
nKeyCode = vbKeyBack
Case "BREAK"
'nKeyCode
Case "CAPSLOCK"
'nKeyCode=
Case "DELETE", "DEL"
nKeyCode = vbKeyDelete
Case "DOWN"
nKeyCode = vbKeyDown
Case "END"
nKeyCode = vbKeyEnd
Case "ENTER", "~"
nKeyCode = vbKeyReturn
Case "ESC", "ESCAPE"
nKeyCode = vbKeyEscape
Case "HOME"
nKeyCode = vbKeyHome
Case "INSERT", "INS"
nKeyCode = vbKeyInsert
Case "LEFT"
nKeyCode = vbKeyLeft
Case "NUMLOCK"
nKeyCode = vbKeyNumlock
Case "PGDN"
nKeyCode = vbKeyPageDown
Case "PGUP"
nKeyCode = vbKeyPageUp
Case "PRTSC"
nKeyCode = vbKeyPrint
Case "RIGHT"
nKeyCode = vbKeyRight
Case "SCOLLLOCK"
nKeyCode = vbKeyScrollLock
Case "TAB"
nKeyCode = vbKeyTab
Case "UP"
nKeyCode = vbKeyUp
Case "F1"
nKeyCode = vbKeyF1
Case "F2"
nKeyCode = vbKeyF2
Case "F3"
nKeyCode = vbKeyF3
Case "F4"
nKeyCode = vbKeyF4
Case "F5"
nKeyCode = vbKeyF5
Case "F6"
nKeyCode = vbKeyF6
Case "F7"
nKeyCode = vbKeyF7
Case "F8"
nKeyCode = vbKeyF8
Case "F9"
nKeyCode = vbKeyF9
Case "F10"
nKeyCode = vbKeyF10
Case "F11"
nKeyCode = vbKeyF11
Case "F12"
nKeyCode = vbKeyF12
Case "F13"
nKeyCode = vbKeyF13
Case "F14"
nKeyCode = vbKeyF14
Case "F15"
nKeyCode = vbKeyF15
Case "F16"
nKeyCode = vbKeyF16
Case Else
nKeyCode = _
GetKeyCodeFromAscii(nKey, nShiftState)
End Select
nRepeats = CLng(nRepeat)
End If
Else
nKeyCode = GetKeyCodeFromAscii(nKey, nShiftState)
End If
End Select
If nKeyCode Then
SendKey nKeyCode, nShiftState, nRepeats
End If
End If
nShiftState = 0
l = nPos + 1
Case "("
nPos = InStr(l + 1, Keys, ")")
nKey = Trim$(Mid$(Keys, l + 1, nPos - l - 1))
For ll = 1 To Len(nKey)
nShift = nShiftState
nKeyCode = _
GetKeyCodeFromAscii(Mid$(nKey, ll, 1), nShift)
SendKey nKeyCode, nShift
Next 'll
nShiftState = 0
l = nPos + 1
Case Else
nKeyCode = GetKeyCodeFromAscii(nChar, nShiftState)
SendKey nKeyCode, nShiftState
nShiftState = 0
l = l + 1
End Select
Loop Until l > Len(Keys)
End Sub
|
|
|