Vol verwachting keek ze naar mijn handen toen ik vertelde dat het programma wist welke invoer het verwacht en zelf van toetsenbord wisselt. Alsof er onder mijn handen echt een ander toetsenbord tevoorschijn getoverd zou komen … maar dat gebeurde natuurlijk niet.
In dit artikel wordt een aantal procedures besproken om het toetsenbord te manipuleren.
De voorbeelden zijn in Delphi, maar aangezien het eigenlijk vooral aanroepen van de Windows API zijn zullen ze betrekkelijk eenvoudig naar andere talen omgezet kunnen worden.
Het schermtoetsenbord
Wat niet iedereen blijkt te weten is dat Windows de mogelijkheid biedt om een visuele weergave van het toetsenbord op het scherm te tonen. Als je onder Start - Programma's - Bureau-Accessoires - Toegankelijkheid kiest voor “Schermtoetsenbord” dan krijg je een venster met de weergave van het toetsenbord. Het aardige is dat we meteen zien wat het effect is van een toetsaanslag. Zo krijgen we als we de shift indrukken meteen alle hoofdletters te zien. En zo kom je er eenvoudig achter dat je een “®” eenvoudig kan maken met CRTL+ALT+R (in ieder geval op mijn computer en met mijn toetsenbord instellingen).

Fig. 1: Schermtoetsenbord met CTRL+ALT (of alleen RECHTER ALT) ingedrukt
Achter de schermen wordt hiervoor het programma OSK.EXE gestart, dat we kunnen vinden in de SYSTEM32-directory. OSK staat overigens voor On-Screen Keyboard.
Er zitten wel een paar beperkingen aan OSK.EXE. Zo kan het maar één keer opgestart worden. Dat maakt het lastig om het vanuit je eigen programma op te starten: je weet immers nooit of de gebruiker het al niet opgestart heeft. En het staat het altijd op de voorgrond, maar dat is wel logisch, want het is immers bedoeld als hulp bij het invoeren. Daarnaast kijkt het programma niet echt hoe het fysieke toetsenbord eruit ziet: het kent een aantal basistoetsenborden waarmee je kunt proberen iets te vinden wat zoveel mogelijk op je eigen toetsenbord lijkt.
Maar voor het moment is het een prima hulpmiddel om te zien wat er allemaal gebeurt.
Een ander toetsenbord gebruiken
Laten we het simpel houden en doen alsof we een medewerker hebben die uit België komt en niet goed overweg kan met het “qwerty” toetsenbord, omdat in België nu eenmaal het “azerty” toetsenbord standaard is. Het is heel eenvoudig om voor die persoon het toetenbord van “qwerty” om te zetten in “azerty”. In het configuratiescherm kunnen we onder “Landinstellingen” aangeven dat we Frans als invoertaal willen gebruiken: klik in de tab “Talen” op “Details” en gebruik daar de knop toevoegen en kies voor “Frans” met de toetsenbordindeling “Frans”. Even op “Toepassen” klikken en klaar zijn we! Controleer ook even bij de “Voorkeursinstellingen” onder “Taalbalk” zodat deze wordt weergegeven op het bureaublad.
En nu kunnen we eenvoudig van invoertaal wisselen: door de gewenste invoertaal te kiezen in de taakbalk of door de sneltoets te gebruiken
En nu kunnen we eenvoudig van invoertaal wisselen; door de gewenste invoertaal te kiezen in de taakbalk of door de sneltoets te gebruiken (standaard wordt Shift+Alt gebruikt om naar de volgende invoertaal over te schakelen). Dit omschakelen is de reden dat ik hierboven koos voor Frans als invoertaal en niet “Nederlands (België)”. Nu kun je veel eenvoudiger de invoertaal wijzingen.
Ieder programma wordt gestart met het toetsenbord zoals dat gespecificeerd is bij “Standaardinvoertaal”. Daarna kunnen we de taal (dat wil zeggen de te gebruiken lay-out van het toetsenbord) voor ieder programma afzonderlijk instellen. Zo kunnen eenvoudig omschakelen naar “Frans” en dit artikel verder op zijn Frans intikken.
Hier merken we overigens nog een beperking van OSK.EXE: als we van invoertaal wisselen heeft het schermtoetsenbord dat niet meteen in de gaten. We moeten er even met de muis overheen om het actuele toetsenbord in te stellen.
Overigens kun je ook gewoon bij de taal “Nederlands” kiezen voor een “Frans” of een willekeurig ander toetsenbord. Dvorak schijnt in sommige kringen populair te zijn. Het is een goede manier om te zorgen dat anderen niet meer soepel op jouw computer kunnen werken. Dat kan zowel een voordeel als een nadeel zijn!
Windows en toetsenborden
Windows identificeert toetsenborden met HKL’s (Handle Keyboard Layouts). Een HKL is een longword en bestaat uit twee delen: een specificatie van de invoertaal en een specificatie van de gekozen toetsenbord lay-out. De specificatie van de invoertaal bestaat ook weer uit twee delen, de taal zelf en het dialect. Zo staat “$0413” voor Nederlands, “$0813” staat voor “Belgisch” (Vlaams dus). “$0409” is “US”, “0809” is Engels.
De specificatie van de gekozen lay-out is feitelijk een volgnummer. “0000” is het standaard toetsenbord en zo wordt er verder geteld.
De lijst met geïnstalleerde talen kunnen we in de Registry vinden onder “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts”. Daaronder vinden we een lijst met beschikbare HKL’s. Onder die HKL’s kunnen we de specificatie, waaronder een “Layout Text”, vinden.

Fig. 2: Registry entries voor invoertaal Nederlands
Een voorbeeldapplicatie
Om te laten zien hoe het werkt met verschillende toetsenborden heb ik een voorbeeldprogramma gemaakt waarmee je invoer in 4 verschillende talen kunt doen. Het programma heet “MultiKbd” en is te vinden in de download. In het scherm zijn 4 TMemo’s opgenomen waarvoor de taal kan worden ingesteld. Hieronder volgt een voorbeeld van 4x “querty” intypen met vier verschillende toetsenborden:

Fig. 3: Voorbeeldapplicatie
Bij het opstarten zet het programma 4x een combinatie van TCombo en TMemo op het scherm. Daar zou ik natuurlijk een mooie component van hebben kunnen maken, maar eerlijk gezegd heb ik er zelf een hekel aan om voor een voorbeeld eerst allerlei componenten te moeten installeren. Dat vermijd ik door de componenten pas bij het opstarten op het form te zetten.
In de TCombo’s wordt de lijst met beschikbare toetsenborden opgenomen (die worden dus uit de Registry gehaald, zie hierboven), en er wordt een “willekeurig” toetsenbord gekozen. En de TMemo’s weten nu welk toetsenbord ze moeten gebruiken! En het mooiste van allemaal: we hoeven die toetsenborden niet eens vooraf via de settings beschikbaar te stellen!
Wat het programma doet is alle mogelijkheden aflopen totdat er eentje met de gezochte taal gevonden wordt
Bij het “willekeurig” kiezen van een toetsenbord heeft het programma wel degelijk een voorkeur. Er wordt geprobeerd een Nederlands, een Frans, een Russisch en een Grieks toetsenbord te kiezen. Daarbij wordt gebruik gemaakt van het feit dat de basistaal is opgenomen in de HKL. Wat het programma doet is alle mogelijkheden aflopen totdat er eentje met de gezochte taal gevonden wordt. Maar vaak wil je dat nog wat specifieker opgeven. Zo krijg ik op mijn computer een “Belgian French” toetsenbord als ik “Frans”, met code $080c, als taal opgeef. Geen idee wat het verschil is, maar meestal zitten de verschillen in speciale tekens en hoe je letters met accenten kunt krijgen. Om die te selecteren kan naast de taal ook de rest van de HKL getest worden.
Het kan nog iets specifieker gemaakt worden. Als de gebruiker al een toetsenbord met taalcode “Frans” heeft is het natuurlijk wel zo aardig die dan ook maar te gebruiken.
Om er achter te komen of, en zo ja met welk toetsenbord, een taal is geïnstalleerd, kunnen we de functie “GetKeyboardLayoutList” gebruiken. Die geeft ons een lijst met de geïnstalleerde toetsenborden. Of beter: de functie geeft ons terug hoeveel toetsenborden er zijn geïnstalleerd en geeft de specificatie daarvan in een de “array of HKL” die we als parameter meegegeven hebben. Die lijst kunnen we gebruiken om te testen of er een geschikt toetsenbord is aanwezig is.
Een ander toetsenbord gebruiken/toevoegen
De (Windows) procedure die nodig is om een ander toetsenbord in te stellen heet “LoadKeyboardLayout”. Deze procedure kent twee parameters: de HKL en een optie parameter.
Om het ons niet al te eenvoudig te maken moeten we de HKL nu niet als getal maar als string (of beter gezegd, een PChar) doorgeven. De belangrijkste opties zijn KLF_ACTIVATE en KLF_SETFORPROCESS. De eerste activeert het toetsenbord, en als we KLF_SETFORPROCESS opgeven, dan wordt het toetsenbord alleen voor het betreffende programma geïnstalleerd. Altijd handig voor een demoprogramma: het zorgt ervoor dat je na afloop niet blijft zitten met een heleboel geactiveerde toetsenborden!
Overigens is de constante KLF_SETFORPROCESS om een of andere reden niet opgenomen in de bij Delphi meegeleverde Windows Unit, dus moeten we die zelf even declareren, met de decimale waarde van 256.
lHKLstr :=’00000413’;
FHKL :=LoadKeyboardLayout(PWideChar(lHKLstr),
KLF_ACTIVATE OR KLF_SETFORPROCESS);
Listing 1: Een invoertaal instellen
Een andere optie die handig kan zijn is KLF_REPLACELANG. Deze vervangt een eventueel eerder geïnstalleerd toetsenbord met dezelfde taal. Zo kun je er dus voor zorgen dat je per taal maar één toetsenbord installeert. Andere opties zijn eenvoudig in de bij Delphi (2009) geleverde helpbestanden te vinden, en anders via MSDN.
De procedure “LoadKeyboardLayout” blijkt redelijk “hufter-proof” te zijn. Als je een ongeldig toetsenbord opgeeft wordt automatisch gekozen voor het standaardtoetsenbord “04090409”. Dat kan in het voorbeeldprogramma eenvoudig worden getest: als we de tekst van de TCombo wijzigen dan wordt direct het betreffende toetsenbord geactiveerd. Om te laten zien dat er iets gebeurt maak ik gebruik van de “poor mans debugger”: in de caption verschijnt het geïnstalleerde toetsenbord.
En wellicht is het ook handig om te weten dat je met de procedure “UnloadKeyboardLayout” een HKL weer kan deactiveren. Dat voorkomt dat je met een hele lijst toetsenborden komt te zitten. Een nadeel is wel dat ook de vooraf geïnstalleerde toetsenborden moeiteloos worden verwijderd, en dat is natuurlijk weer iets wat je niet wilt. Met de nodige zorgvuldigheid toepassen dus.
Je eigen toetsenbord maken
Niet tevreden met de lay-out van het toetsenbord? Maak dan gewoon je eigen toetsenbord! Dat gaat verrassend eenvoudig. Het gaat als volgt … Als eerste moet je bij Microsoft de “Microsoft Keyboard Layout Creator” (MSKLC.EXE) downloaden en installeren. Daarna start je het programma en kun je beginnen met een leeg toetsenbord. Het handigst is een bestaand toetsenbord als uitgangspunt te nemen en dat aan te passen. Als je klaar bent met aanpassen, genereer je een Setup. Die even runnen en je kunt je eigen toetsenbord het gebruiken.
Natuurlijk een geweldige manier om je collega dwars te zitten, maar er zijn ook meer praktische toepassingen te bedenken. Zo geeft mijn toetsenbord met CTRL+ALT+5 een euro-teken. Volgens het schermtoetsenbord krijg ik met CTRL+ALT+”-“ een yen-teken (wat overigens niet werkt binnen Word). Het pond-teken is helemaal niet terug te vinden. Maar als je die regelmatig nodig hebt kun je dus zelf een toetsenbord maken waar het pondteken wel op staat. Een andere toepassing is om je toetsenbord echt tweetalig te maken. Dat kan nuttig zijn als je “af en toe” iets moet intypen in een andere taal en geen zin hebt dan steeds van toetsenbord te wisselen. Laten we gewoon even aannemen dat je in een wiskundig gebied werkt en regelmatig α, β, γ etc moet gebruiken. Je kunt nu eenvoudig een toetsenbord maken met ALT+a=α, ALT+b=β, ALT+g=γ, enz. Om het te demonstreren doe ik bij de download een setup voor een compleet Nederlands-Grieks toetsenbord.
Bij het toekennen van de karakters kun je meer dan één karakter gebruiken. Hoeveel is afhankelijk van welk karakter, maar het komt erop neer dat de gehele karakterreeks niet langer mag zijn dan 4 bytes. Dus als we vaak “SDN” moeten typen dan kunnen we dat gewoon achter een toets plaatsen. Probeer maar eens ALT+3 in het meegeleverde toetsenbord. Daarbij geldt dat hier de RECHTER ALT gebruikt moet worden. De LINKER ALT activeert het (hoofd)menu van de applicatie. Dus LINKS-ALT+H activeert de help in het hoofdmenu (indien aanwezig), RECHTER-ALT+H genereert het karakter “η” (feitelijk is de RECHTER ALT gelijk aan CTRL+ALT)
Iets aan CTRL+C toekennen lijkt dus een minder goed idee …
Een kleine waarschuwing is hier wellicht op zijn plaats. Je moet wel een beetje oppassen met het toekennen van toetsen die binnen Windows een speciale betekenis hebben. Iets aan CTRL+C toekennen lijkt dus een minder goed idee. Zie ook het hiervoor genoemde voorbeeld met de yen: ALT+CLTR+”-“ heeft kennelijk een, mij onbekende, betekenis binnen Word en levert dus binnen Word geen yen-teken op.
En dan nog een opmerking: je kunt de geïnstalleerde toetsenborden de-installeren via het configuratiescherm. Het blijkt echter dat ze dan nog wel in de Windows-registry blijven hangen. Die entries moet je dus zelf even weghalen (onder “Keyboard Layouts”). Je kunt ze eenvoudig herkennen: het zijn de entries die beginnen met een ‘a’.
Een beperking van MSKLC is dat je de SHIFT, ALT, enz. niet kunt herdefiniëren. Op zich natuurlijk geen probleem, waarom zou je ook? Er is echter een veelgenoemd voorbeeld waarbij het wel zinvol kan zijn: de CAPS-LOCK. Ik heb het zelf ook wel op mijn laptop: je wil een SHIFT doen en raakt per ongeluk met je vingertop ook de CAPS-LOCK aan. Heel irritant. Er is een oplossing voor: je kunt in de registry een ingang maken die van de CAPS-LOCK een gewone SHIFT maakt. De code is (met dank aan vele bronnen op internet):
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\
Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,03,00,00,00,\
2a,00,3a,00,3a,00,46,00,00,00,00,00
Listing 2: Code om van de CAPS-LOCK een SHIFT te maken
Wel eerst even opnieuw opstarten (het is tenslotte Windows).
En tja, als je die collega nu echt eens flink dwars wilt zitten, dan is wel een geschikte manier. Als je wilt maak je een toetsenbord met alleen maar SHIFT-toetsen.
Zorg er wel voor dat je het nog ongedaan kunt maken! De manier om het ongedaan te maken is om betreffende entry in de registry geheel te verwijderen.
Het wijzigen van het toetsenbord detecteren
Als we zelf, dat wil zeggen binnen het programma, een ander toetsenbord activeren, dan zijn we daar natuurlijk van op de hoogte. In bepaalde situaties echter zou het handig kunnen zijn om te kunnen detecteren wanneer het toetsenbord van buitenaf wordt gewijzigd. Er blijken twee messages van belang:
- WM_INPUTLANGCHANGE om aan te geven dat de invoertaal is gewijzigd; WM_INPUTLANGCHANGEREQUEST voordat de invoertaal gewijzigd gaat worden.
Als we dat testen op een leeg TForm werkt het prima. Maar op het moment dat we een button, een edit of een ander control die de focus kan krijgen op het form zetten dan krijgen we deze messages niet meer. De reden is dat deze messages terecht komen bij de component met de focus. Nu kunnen we natuurlijk in alle componenten die messages afvangen en die dan zelf doorsturen naar het Form, maar dat is niet erg handig. Een hoop werk en voor je het weet ben je er weer een vergeten. En andere manier is om te proberen de messages af te vangen via TApplication.OnMessage. Als we dat doen dan krijgen we wel het REQUEST te zien, maar de echte CHANGE niet. Maar eigenlijk is de REQUEST wel zo goed. Deze message komt vlak voordat de echte CHANGE wordt uitgevoerd. En het aardige is dat we op dat moment kunnen besluiten de CHANGE te verbieden. En als we hem wel toestaan, dan weten we ook dat er binnenkort een CHANGE plaats zal vinden!
Het op deze manier niet toestaan van een wijziging blijkt nog met de nodige zorgvuldigheid te moeten gebeuren. In eerste instantie had ik een TCheckbox op het form gezet om te bepalen of er wel of niet gewijzigd mag worden. Het blijkt een zeer effectieve manier om te voorkomen dat je programma goed opstart! Maar op zich werkt het wel. Om het te laten zien zit in het programma de volgende verborgen optie: als je in de TMemo links boven staat en daarin “toevallig” de tekst “xxx” staat, dan kun je de taal niet veranderen met CTRL+ALT.
procedure TForm1.OnApplicationMessage(var aMsg:TMsg;
var Handled: Boolean);
begin
case aMsg.Message of
WM_INPUTLANGCHANGEREQUEST:
begin
Handled:=FMemo1.Focused AND (FMemo1.Text='xxx');
end;
end;
end;
Listing 3: Wijzigen van de invoertaal verbieden
Het afvangen van de TApplication.OnMessage moet wel met enige zorgvuldigheid gebeuren. Zolang we een applicatie hebben met maar één form die één keer de OnMessage gebruikt. is er niets aan de hand. Maar als we meerdere forms hebben die allemaal de OnMessage willen gebruiken, moeten we op een of andere manier een keten maken en ervoor zorgen dat het allemaal goed blijft gaan als er een form ergens in de keten wordt opgeruimd.
Er schijnt in Delphi 2010 een CM_ INPUTLANGCHANGE opgenomen te zijn die mogelijk een betere oplossing voor het afvangen van de taalverandering is. Maar ik heb, in ieder geval op het moment van het schrijven van dit artikel, Delphi 2010 nog niet in bezit.
Toetsenborden en Events
Binnen Delphi hebben we drie events die te maken hebben met een toetsaanslag: OnKeyDown, OnKeyPress en OnKeyUp. De volgende tabellen laten zien wat er gebeurt als ik een “w”, “z” en “2” aantik (de parameter die we doorkrijgen wordt getoond als karakter en als decimale waarde).
| |
00000413-Dutch |
0000080c-Belgian French |
00000419-Russian |
00000408-Greek |
| FormKeyDown |
W(87) |
Z(90) |
W(87) |
W(87) |
| FormKeyPress |
w(119) |
z(122) |
ц(1094) |
ς(962) |
| FormKeyUp |
W(87) |
Z(90) |
W(87) |
W(87) |
Tabel 1: Waardes voor de toets “w”
| |
00000413-Dutch |
0000080c-Belgian French |
00000419-Russian |
00000408-Greek |
| FormKeyDown |
Z(90) |
W(87) |
Z(90)/td> |
Z(90) |
| FormKeyPress |
z(122) |
W(119) |
я(1103) |
ζ(950) |
| FormKeyUp |
Z(90) |
W(87) |
Z(90) |
Z(90) |
Tabel 2: Waardes voor de toets “z”
| |
00000413-Dutch |
0000080c-Belgian French |
00000419-Russian |
00000408-Greek |
| FormKeyDown |
2(50) |
2(50) |
2(50) |
2(50) |
| FormKeyPress |
2(50) |
é(233) |
2(50) |
2(50) |
| FormKeyUp |
2(50) |
2(50) |
2(50) |
2(50) |
Tabel 3: Waardes voor de toets “2”
Om bij de laatste tabel te beginnen: het azerty toetsenbord heeft standaard accenttekens waar wij de cijfers hebben, cijfers krijg je met SHIFT+cijfer.
En nee, in de tabellen voor “w” en “z” is géén fout gemaakt. Toevallig zit de “z” op een “azerty” toetsenbord op de plaats van een “w” op het “querty” toetsenbord, en omgekeerd. Wat duidelijk wordt, is dat als we voor een “Frans” toetsenbord kiezen Windows doet alsof er echt, via een soort Hans Klok act, een ander toetsenbord wordt gebruikt, en dat voor de meer exotische toetsenborden “alleen maar” een vertaling van de toetsaanslagen wordt gemaakt.
In eerste instantie lijkt het wellicht merkwaardig, of misschien wel fout, dat er in de OnKeyDown bij de “exoten” geen vertaling naar het echte invoerkarakter wordt gemaakt. Maar dat valt wel mee: dit is precies de reden dat ook CTRL+C en CTRL+V gewoon blijven werken! Bovendien, ook voor ons normale toetsenbord is de code feitelijk onjuist: we krijgen immers een “W” als we op de “w” drukken!
Dit maakt dus duidelijk dat je geacht wordt de KeyDown/KeyUp events voor andere doeleinden te gebruiken dan het KeyPress event. Maar wat nu als je oudere code hebt, waarin het verschil minder helder was, en als je in het KeyDown event al wilt weten om welk karakter het gaat, kun je daar dan achter komen? Ja, dat kan met de Windows procedure “ToUnicode”. Daarbij speelt een kleine complicatie: deze procedure verwacht als parameter de “KeyboardState”. Dat is een array van 256 bytes waarin de status van het toetsenbord wordt weergegeven. Die is nodig om te bepalen of er een SHIFT is ingedrukt of dat wellicht de CAPS-LOCK aanstaat, dat soort dingen. Gelukkig kunnen we de huidige status eenvoudig opvragen met de procedure “GetKeyboardState”. En zo kunnen we bij het KeyDown event al achterhalen welke toets we te pakken hebben. In het voorbeeldprogramma wordt de “Poor Mans Debugger” gebruikt om het werkelijke karakter in de de KeyDown te tonen (in de caption van het form dus).
ToUnicode werkt met het huidige toetsenbord. Maar wat nu als we het effect willen weten van een toets op een ander toetsenbord. Dat kun je achterhalen met de procedure “ToUnicodeEx”. Er is wel een beperking: het werkt alleen met een toetsenbord dat geactiveerd is. Liefhebbers kunnen dat in het voorbeeldprogramma doen door de betreffende regel in de code te activeren.
Een schermtoetsenbord-component
Met de bovenstaande kennis zijn we in staat onze eigen schermtoetsenbord-component te bouwen. Als basis gebruik ik een TPaintbox waarop de toetsen worden getekend. Het voordeel is dat hier het toetsenbord eenvoudig wat groter, of kleiner, geschaald kan worden. Om een toetsenbord te definiëren maak ik een lijst van toetsen met hun positie in een grid en die lijst wordt dan gebruikt om het toetsenbord netjes op het scherm te tekenen binnen de beschikbare ruimte.
Om niet voor iedere toets de plaats in het grid op te hoeven geven zitten er in de component allerlei procedures om het wat makkerijker te maken. Zo maak ik de regel toetsen met “querty” als volgt:
AddNextKey(VK_Tab,clBtnFace,6);
AddNextKey(VK_Q);
AddNextKey(VK_W);
AddNextKey(VK_E);
AddNextKey(VK_R);
AddNextKey(VK_T);
AddNextKey(VK_Y);
AddNextKey(VK_U);
AddNextKey(VK_I);
AddNextKey(VK_O);
AddNextKey(VK_P);
AddNextKey(VK_OEM_4);
AddNextKey(VK_OEM_6);
AddNextKey(VK_OEM_5,clBtnFace,6);
Listing 4: Toevoegen van de QWERTY regel op het toetsenbord
En omdat dat nog best veel code is, mag ik dat ook schrijven als
AddNextKey(VK_Tab,clBtnFace,6);
AddNextKeys([VK_Q,VK_W,VK_E,VK_R,VK_T,VK_Y,
VK_U,VK_I,VK_O,VK_P,VK_OEM_4,VK_OEM_6]);
AddNextKey(VK_OEM_5,clBtnFace,6);
Listing 5: Alternatief voor de QWERTY regel
Zoals de code laat zien gebruik ik voor de toetsen de standaard “Virtual Keys” zoals die ook terugkomen in de KeyDown en KeyUp. De VK_A t/m VK_Z zijn niet opgenomen in Delphi en die moeten we even zelf definiëren voor we ze kunnen gebruiken. Voor de TAB-toets geef ik een afwijkende achtergrondkleur en breedte mee (standaard is de achtergrondkleur “clWhite” en de breedte “4”). Het uiteindelijke resultaat is een form, met het keyboard als onderdeel van het form:

Fig. 4: Voorbeeldapplicatie met schermtoetsenbord
Om te achterhalen welke tekst we op een knop moeten zetten om de knop te krijgen, maak ik gebruik van de functie “ToUnicode”. Deze functie geeft voor een Virtual Key, gegeven de keyboardstate, de caption voor die Key. Het gaat echter mis bij de dode toetsen, zoals de accenttekens. De truc is de functie twee keer achter elkaar aan te roepen. De toetsen waarvoor dit geen resultaat geeft geef ik vervolgens zelf een caption.
Als we dat willen kunnen we achterhalen wat het geïnstalleerde toetsenbord is. Daarvoor kunnen we de functie GetKeyboardType gebruiken. Daarmee is op het type, subtype en aantal functietoetsen te achterhalen. De vraag is alleen of je op het scherm wilt laten zien wat er onder de handen zit of juist een, al dan niet te kiezen, standaardtoetsenbord. Om te laten zien dat je echt elk willekeurig toetsenbord kan maken heb ik een toetsenbord gedefinieerd met alle Virtuele codes (0..255). Het ziet er als volgt uit. Dit keer als een zelfstandig form.

Fig. 5: Virtueel toetsenbord met 256 keys
Werken met het virtuele toetsenbord
Om het toetsenbord zover te krijgen dat het laat zien wat de huidige toetsen zijn, moeten op een of andere manier een melding krijgen als er wijzigingen zijn. Zoals eerder aangegeven is het afvangen van de benodigde events problematisch. Om alle problemen voor te zijn gebruik ik een TTimer die iedere 0,1 seconden afvuurt. Natuurlijk zou ik iedere keer dat de timer een event geneert meteen opnieuw kunnen tekenen, maar om al dat tekenen wat te beperken vraag ik eerst de status en de invoertaal op. Als één van die twee is gewijzigd ten opzichte van de vorige keer dan is er (mogelijk) een wijziging in de betekenis van de toetsen en teken ik het toetsenbord opnieuw.
Natuurlijk is het de bedoeling dat als we op ons virtuele toetsenbord ergens op een toets klikken, dat er dan gedaan wordt alsof er ook op die toets wordt gedrukt. Dat is een kwestie van een paar keer de procedure “keybd_event” (of beter: “SendInput”) aan te roepen. In het voorbeeld is de code gebruikt die ook gebruikt is in het NumPad voorbeeld zoals dat op de site van CodeGear te vinden is.
Het blijkt te werken. Toch is er wel iets over op te merken. Zoals eerder aangegeven is er een mogelijkheid om van de CAPS-LOCK een gewone SHIFTt te maken. Als je dat hebt gedaan, kun je met dit virtuele toetsenbord alsnog de CAPS-LOCK aan en uit zetten.
Een andere opmerking betreft het gebruik van de SHIFT-, CTRL- en ALT-toetsen. De demo leest vanaf het toetsenbord of deze zijn ingedrukt of niet. Natuurlijk is het mogelijk de demo zodanig uit te breiden dat je op een of andere manier de SHIFT op het virtuele toetsenbord ingedrukt kunt houden (alsof het een CAPS-LOCK was!) en kun je die status vervolgens gebruiken. Dat gaat echter voorbij aan het doel van dit artikel.
Bij de events is aangegeven dat Windows bij het instellen van een AZERTY toetsenbord doet alsof er echt een ander toetsenbord is aangesloten
De oplossing zoals hier beschreven lijkt te werken. Op één uitzondering na: bij de events is aangegeven dat Windows bij het instellen van een AZERTY toetsenbord doet alsof er echt een ander toetsenbord is aangesloten. En het gevolg is dat ons toetsenbord dat niet in de gaten heeft. Gelukkig is er een relatief eenvoudig oplossing. In plaats van de virtuele codes kan je ook gebruik maken van de Scan Codes. Dat zijn hardware codes die betrekking hebben op de locatie van de toets op het toetsenbord. Zo heeft de “q” code 16, “w” code 17, “e” code 18, enzovoorts. Dat is natuurlijk ook precies wat Windows achter de schermen gebruikt als je een AZERTY toetsenbord instelt. Gelukkig is er een functie die Scan Codes omzet naar Virtual codes. Op die manier is het relatief eenvoudig het toetsenbord aan te passen. Ik heb het zo gemaakt dat je de beide codes door elkaar heen kunt gebruiken. Om ze uit elkaar te houden gebruik ik voor de Scan Code de negatieve waarde. Bij het gebruik zet ik die negatieve waarden om naar een Windows Virtuele Key. De procedure die we daar voor nodig hebben heet MapVirtualKey. De bijbehorende parameters krijgen we niet van Borland, Codegear, Embarcadero of hoe het ook heet als u dit artikel leest, maar die zijn wel te achterhalen. Wat wij nodig hebben is de parameter “MAPVK_VSC_TO_VK” en dat is de numerieke waarde “1”.
De aanleiding voor dit artikel was om de mogelijkheid om van invoertaal te wisselen te laten zien. Alle methoden die hier beschreven zijn maken, al dan niet indirect, gebruik van het toetsenbord. Een voor de hand liggende vraag is of het ook buiten het toetsenbord om kan. Met andere woorden: kan ik een Russisch schermtoetsenbord hebben en gebruiken, zonder dat Russisch als invoertaal is gedefinieerd. Het beste antwoord is dat het niet kan. Wat zou er bijvoorbeeld in de message van het KeyDown event als key moeten komen? Maar in sommige gevallen kan het handig zijn om zo’n invoermogelijkheid te hebben. De component in het voorbeeld is er op voorbereid. De afhandeling van deze toetsen gebeurt door middel van een “OnInput”-event waar de applicatie een handler aan kan koppelen. En om het helemaal algemeen toepasbaar te maken heb ik de definitie van de toets niet beperkt tot een enkel karakter. Het toevoegen van de volgende toetsenbalk behoort dus tot de mogelijkheden.

Fig. 6: Geheel alternatief toetsenbord
Conclusie
In dit artikel is een aantal aspecten van het toetsenbord beschreven. En hoewel het toetsenbord het meest standaard onderdeel is van de computer, blijkt dat we er veel meer uit kunnen halen dan we meestal doen. De aanleiding van dit artikel was het beschrijven hoe om te gaan met meertalige invoer, maar de technieken kunnen ook toegepast worden voor ééntalig gebruik, met name als er regelmatig met speciale tekens (zoals valuta tekens of wiskundige symbolen) gewerkt moet worden. Alle aanleiding dus om er toch nog maar eens naar te kijken.
De sources die bij dit artikel horen kun je downloaden via Sman_HMT.zip.