Správný přístup ke zjištění stavu klávesnice v UWP aplikacích

WinUI

6 years ago

UWP aplikace by měly fungovat skvěle s jakýmkoliv vstupním zařízením včetně klávesnice. To zahrnuje podporu klávesových zkratek a kontrolu stavu ve kterém jednotlivé klávesy jsou. Ukazuje se však, že je nutné si dát pozor na některé detaily.

Problém

Předpokládejme, že chceme zpracovat událost KeyDown a zkontrolovat, zda je stisknuta klávesa Ctrl. Jednoduchý přístup by vypadal takto:

if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) == 
    CoreVirtualKeyStates.Down)
{
    //do something
}

Přestože kód vypadá naprosto správně, je v něm skrytý háček. Když aplikaci spustíme a otestujeme, zjistíme, že do bloku v if se dostaneme pouze při každém druhém stisknutí klávesy. Čím je to způsobeno?

Stav Locked

Použijeme-li debugger, uvidíme, že při stistknutích se klávesa Ctrl přepíná mezi stavy Down a Down|Locked. To znamená, že výčtový typ CoreVirtualKeyStates je definován s atributem [Flags]:

[ContractVersion(typeof(UniversalApiContract), 65536)]
[Flags]
[WebHostHidden]
public enum CoreVirtualKeyStates : uint
{
   None = 0,
   Down = 1,
   Locked = 2
}

Klávesa tedy může být Down a Locked zároveň! Co ale stav Locked znamená? Documentace popisuje Locked následovně:

The key is in a toggled or modified state (for example, Caps Lock) for the input event.

V překladu - klávesa je v zapnutém nebo modifikovaném stavu (například Caps Lock) pro danou událost vstupu. To pro Caps Lock či Num Lock rozhodně dává smysl, ale dokumentace pokračuje:

All keys support the Locked state (not just the standard Caps Lock and Num Lock keys).

Tedy - všechny klávesy podporují stav Locked (ne jen standardní klávesy Caps Lock a Num Lock). To je velmi zajímavé! Znamená to, že každá klávesa na klávesnici funguje jako přepínač, tedy i například jednoduchá písmena! Můžeme také vidět, že i když klávesa není stisknuta, zůstane ve stavu Locked do té doby, než je znovu stisknuta. Také jsem zjistil, že stav Locked není specifický pro aplikaci, ale obecný pro celý systém, přestože pro klávesy mimo modifikátory se jejich stav nereflektuje ihned po spuštění aplikace a po krátký časový interval metoda GetKeyState vrací jednoduše None.

Správný přístup

Pro kontrolu, zda je klávesa stisknuta, můžeme použít metodu HasFlag výčtového typu:

if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).
     HasFlag(CoreVirtualKeyStates.Down))
{
    //Ctrl is pressed
}

Alternativní řešení zahrnuje použití bitového operátoru AND:

if ((Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) &
    Windows.UI.Core.CoreVirtualKeyStates.Down)
    == CoreVirtualKeyStates.Down)
{ 
    //Ctrl is pressed
}

Pro zajímavost, pro kontrolu zda klávesa stiskuta není bychom měli použít následující kód (všimněte si operátoru ! před podmínkou):

if (!Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).
     HasFlag(CoreVirtualKeyStates.Down))
{
    //Ctrl is not pressed
}

Nemůžeme použít přímo == CoreVirtualKeyStates.None, protože jsme zjistili výše, klávesa nemusí býti stisknuta, ale může být přesto ve stavu Locked.

Zdrojový kód

Ukázkový projekt, který ukazuje přepínání stavů kláves je k dispozici na mém GitHubu.

Shrnutí

Při kontrole stavu kláves v UWP si musíme dát pozor na to, abychom jej zjišťovali správným a spolehlivým způsobem. Klíčovým zjištěním je fakt, že klávesa může být ve dvou různých stavech současně a namísto jednoduchého porovnání musíme použít metodu HasFlag.