106 votes

Capture globale du clavier dans une application C#

Je veux capturer un raccourci clavier dans mon application et déclencher l'apparition d'une boîte de dialogue si l'utilisateur appuie sur une combinaison de touches même en dehors de l'application. Similaire au raccourci Ctrl, Ctrl de Google Desktop Search pour faire apparaître la boîte de recherche.

J'ai essayé d'utiliser certains modules de crochet de clavier disponibles qui utilisent essentiellement l'interopérabilité Win32 pour obtenir cet effet, mais chaque implémentation que j'ai essayée limite dans une certaine mesure l'utilisation du clavier à tel point que des comportements étranges apparaissent lorsque l'application effectue des tâches intensives. Par exemple, charger une grande quantité de données pourrait entraîner le blocage du clavier et de la souris.

Je recherche une solution légère qui permettrait de réaliser cela sans bloquer le clavier et la souris.

113voto

John T Points 14067

Stephen Toub a écrit un excellent article sur la mise en œuvre des hooks clavier globaux en C# :

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

    public static void Main()
    {
        _hookID = SetHook(_proc);
        Application.Run();
        UnhookWindowsHookEx(_hookID);
    }

    private static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    private delegate IntPtr LowLevelKeyboardProc(
        int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(
        int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);
            Console.WriteLine((Keys)vkCode);
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
        LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
        IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);
}

12voto

Joe Points 2507

Si un raccourci clavier global suffirait, alors RegisterHotKey ferait l'affaire

0voto

sterlingberger Points 43

Mon rep est trop faible pour commenter, mais concernant l'exception CallbackOnCollectedDelegate, j'ai modifié la méthode public void SetupKeyboardHooks() dans la réponse de C4d pour ressembler à ceci :

public void SetupKeyboardHooks(out object hookProc)
{
  _globalKeyboardHook = new GlobalKeyboardHook();
  _globalKeyboardHook.KeyboardPressed += OnKeyPressed;

  hookProc = _globalKeyboardHook.GcSafeHookProc;
}

GcSafeHookProc est simplement un getter public pour _hookProc dans le code OPs

_hookProc = LowLevelKeyboardProc; // nous devons maintenir en vie _hookProc, car le GC n'est pas conscient du comportement de SetWindowsHookEx.

et a stocké le hookProc en tant que champ privé dans la classe appelant la méthode SetupKeyboardHooks(...), donc en gardant la référence en vie, à l'abri de la collecte des ordures, plus d'exception CallbackOnCollectedDelegate. Il semble que d'avoir cette référence supplémentaire dans la classe GlobalKeyboardHook ne soit pas suffisant. Assurez-vous peut-être que cette référence est également supprimée lors de la fermeture de votre application.

-3voto

Nemo Points 1
private void buttonHook_Click(object sender, EventArgs e)
{
    // Se connecte uniquement aux touches spécifiées (ici "A" et "B").
    // (***) Utilisez ce constructeur

    _globalKeyboardHook = new GlobalKeyboardHook(new Keys[] { Keys.A, Keys.B });

    // Se connecte à toutes les touches.
    // (***) Ou cela - pas les deux

    _globalKeyboardHook = new GlobalKeyboardHook();
    _globalKeyboardHook.KeyboardPressed += OnKeyPressed;
}

Et puis ça marche bien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X