Simuler l’envoi d’une touche dans une app XAML !

Lorsque votre application XAML ne bénéficie pas d’un clavier physique comme périphérique de saisie, que vous voulez simuler la pression de touches, de raccourcis clavier voir de commander complètement une application à distance, il est indispensable de pouvoir simuler l’envoi de touche du clavier. On parle alors d’injection de touches. On parlera dans cet article les différentes manières d’implémenter cette injection dans des applications WPF et UWP .

Injection vs TextBox méthodes

Lorsque l’on pense injection de touche, on est souvent tenté de ne penser qu’au contrôle TextBox. On pourrait effectivement se servir des méthode de manipulation de texte/curseur de la TextBox pour insérer du texte. Malheureusement le résultat est souvent peu satisfaisant et ne fonctionnerait pas sur tous les contrôles. Alors comment faire pour injecter la touche TAB (tabulation) ou envoyer une chaîne de caractères complète ?

A la fin de l’envoi, je touche (en WPF)

Heureusement il existe une API spécifique dans WPF qui permet d’envoyer des clés de touche ou une chaîne de caractères à un contrôle. On est donc capable d’envoyer du texte dans une TextBox/PasswordBox mais également de naviguer de contrôle en contrôle (en envoyant la touche Tabulation).

Commençons par l’envoi de touche :

// Envoyons la clé Entrée
Key keyValue = Key.Enter;

// on crée l'eventArgs qui va accueilir la touche à envoyer
var eventArgs = new KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, keyValue);
eventArgs.RoutedEvent = Keyboard.KeyDownEvent;

// C'est parti !
InputManager.Current.ProcessInput(eventArgs);

Puis l’envoi d’une chaîne :

// la chaine à envoyer
string keyString = "Une chaine";

// argument contenant les informations sur le texte envoyé
var eventArgs = new TextCompositionEventArgs(
Keyboard.PrimaryDevice,
new TextComposition(InputManager.Current, Keyboard.FocusedElement, keyString)
);

// static RoutedEvent appartenant à UIElement et donnant le type d’événement routé
eventArgs.RoutedEvent = TextInputEvent;

// envoie de la chaine au contrôle en cours
InputManager.Current.ProcessInput(eventArgs);

On choisira l’envoi de touche pour injecter des touches tabulation, entrée, backspace, flêches… On préférera l’envoie de chaîne pour le reste (caractères alphanumériques) car cela permet de ne pas être dépendant de la langue du système.

Et en UWP ?

UWP permet également d’injecter des touches au système mais de part sa nature sand-boxée il doit déclarer cette capacité dans son Manifest. C’est une capacité réservée au SDK Anniversary Update (Windows 1607) et qui est encore en preview.

ATTENTION : N’espérez pas trop voir la capacité d’injection passer la certification du store. Cette capacité doit être justifiée auprès de Microsoft (qui dira NON! basiquement).

Dans le manifest en mode XML on ajoute un namespace rescap
puis ajouter la capacité inputInjectionBrokered

Une fois cette capacité activée dans notre application, il est possible d’utiliser la classe InputInjector capable de simuler l’envoi de touche à l’élément disposant du focus.

var inputInjector = InputInjector.TryCreate();
// liste des clés
var keyInfos = new List<InjectedInputKeyboardInfo>();

// On veut envoyer la clé "TAB" (tabulation)
VirtualKey keyCode = VirtualKey.Tab;

var info = new InjectedInputKeyboardInfo();
info.VirtualKey = (ushort)keyCode;
// la clé sera envoyé en tant qu'evenement Down
info.KeyOptions = InjectedInputKeyOptions.None;

keyInfos.Add(info);

// envoi
inputInjector.InjectKeyboardInput(keyInfos);

// le caractère sera envoyé en tant qu'evenement Up
info.KeyOptions = InjectedInputKeyOptions.KeyUp;

// envoi
inputInjector.InjectKeyboardInput(keyInfos);

Vous pouvez injecter simultanément plusieurs clés en rajoutant des nouveaux InjectedInputKeyboardInfo dans keyInfos. Il est possible alors d’injecter des raccourcis clavier (VirtualKey.Control et VirtualKey.V pour CTRL+V par exemple).

Astuce : pour envoyer la touche ALT qui n’existe pas en tant que VirtualKey, vous devez envoyer à la suite VirtualKey.Control et VirtualKey.Control.Menu

Il est également possible d’envoyer une chaine de caractères de cette manière :

var inputInjector = InputInjector.TryCreate();
// liste des clés
var keyInfos = new List<InjectedInputKeyboardInfo>();
            
// On veut envoyer la chaine "Bonjour"
string keyString = "bonjour";

foreach (char c in keyString)
{
    var info = new InjectedInputKeyboardInfo();
    info.ScanCode = c;
    // le caractère sera envoyé en tant qu'evenement Down
    info.KeyOptions = InjectedInputKeyOptions.Unicode;
    keyInfos.Add(info);
}

// envoi
inputInjector.InjectKeyboardInput(keyInfos);

foreach (var info in keyInfos)
{
    // le caractère sera envoyé en tant qu'evenement Up
    info.KeyOptions = InjectedInputKeyOptions.KeyUp | InjectedInputKeyOptions.Unicode;
}

// envoi
inputInjector.InjectKeyboardInput(keyInfos);

Il est à noter que l’injection ne concerne pas que le clavier mais également des shortcuts spécifiques (Back, Home, Search), le Touch, le Pen, la souris ainsi que les manettes de jeu.

Le saviez-vous ? Dans le cadre d’une application en mode kiosque (mode dans lequel une application UWP est démarrée à la place du shell Windows), le clavier virtuel n’est pas disponible. C’est un vrai handicap (bug?) si vous devez gérer un formulaire de saisie. L’injection de touche de clavier est alors la seul solution pour remplacer ce clavier qui ne s’affiche pas..

Problème de focus ?

Lorsque vous utilisez ces méthodes d’injection, il est important de comprendre qu’elles envoient leurs touches sur l’élément disposant du Focus. Si par exemple vous utilisez un bouton pour lancer ces méthodes, le bouton prendra le focus et les touches ne seront pas envoyés à la textbox que vous visiez mais au bouton. Pas vraiment l’objectif recherché.

En UWP on privilégiera l’attribut AllowFocusOnInteraction=”False” sur le bouton pour empêcher le focus de se déclencher tandis qu’en WPF on ajoutera simplement l’attribut Focusable=”False”