Toujours dans le cadre du développement de mon Media Center EasyMedia Manager en WPF, j'ai entrepris l'interception des messages Windows lancés lorsque les boutons Play/Pause/Stop présents sur certains PC sont pressés :
Première chose que je me suis dis : hum facile, j'ai deux solutions, soit je surcharge la méthode WndProc, soit je crée une classe dérivant de IMessageFilter, que j'instancie dans ma fenêtre et que j'ajoute à l'application. Et bien non, pas du tout. En effet, la notion de Handle ayant disparu en WPF cette technique ne fonctionne plus. Mais, pas d'inquiétudes, une autre solution a été pensée par les développeurs du Framework pour faire de l'interopérabilité WPF / Win32 !
Le ComponentDispatcher (System.Windows.Interop)
La MSDN nous apprend que cette classe fournit au développeur les outils nécessaires au contrôle partagé entre Win32 et WPF de la pompe à message (file d'attente des messages) d'une application. Cette classe fournit des évènements intéressant, un tout particulièrement : ComponentDispatcher.ThreadPreprocessMessage. En effet, cet évènement est levé lorsque la pompe à messages de l'application reçoit un message du clavier (les boutons, même sur le pc, sont considérés comme évènements clavier). Intéressons nous au prototype d'une méthode appelée lorsque cet évènement est levé :
private void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled)
{
}
Cette méthode prend en paramètre un objet de type MSG (en référence) ainsi qu'un booléen (par référence aussi) indiquant si le message est considéré comme attrapé ou non, afin de bloquer la progression de sa propagation. MSG est une structure représentant un message de la pompe à messages de l'application.
Pour attraper les messages associés aux touches de contrôles, nous allons tout simplement définir des constantes les représentant, et tester que notre message passé à la méthode ci-dessus soit l'un des messages que nous souhaitons intercepter :
public const int WM_COMMAND = 256;
public const int WM_PLAYPAUSE = 179;
public const int WM_STOP = 178;
public const int WM_NEXT = 176;
public const int WM_PREV = 177;
public const int WM_VOLUP = 175;
public const int WM_VOLDOWN = 174;
public const int WM_MUTE = 173;
WM_COMMAND est le message qui est envoyé lorsqu'un utilisateur sélectionne une commande depuis un Menu Item, ou lorsque qu'un raccourci clavier est lancé (i.e. nos boutons de contrôle, par exemple). Une fois que l'on connait ce message, il devient aisé de récupérer la valeur de chaque bouton, chose que j'ai fait, après quoi j'ai pu créer les constantes que vous voyez ci-dessus, et qui ne sont rien d'autre que la valeur de la propriété wParam du message msg.
Il ne vous reste plus qu'à faire un switch / case sur cette propriété afin de définir quelle action effectuer lors du clic sur Play, Pause ect…
if (msg.message == Win32.WM_COMMAND)
{
switch ((int)msg.wParam)
{
case Win32.WM_PLAYPAUSE:
Play();
break;
case Win32.WM_MUTE:
Mute();
break;
case Win32.WM_NEXT:
Next();
break;
case Win32.WM_PREV:
Previous();
break;
case Win32.WM_STOP:
Stop();
break;
case Win32.WM_VOLDOWN:
VolumeDown(0.1);
break;
case Win32.WM_VOLUP:
VolumeUp(0.1);
break;
}
}
Et magie, cela fonctionne. Ayant récupérée les valeurs de la propriété wParam moi-même (n'ayant trouvé aucune information précise et « officielle ») je ne garantie pas que cela fonctionne sur tous les PC. Néanmoins, j'ai eu l'occasion de le tester sur 3 portables différents sans soucis. Je pense tout de même que c'est un standard ! Le contraire serait plus qu'abérant !
A bientôt
!