C# Tuş Algılama

Yeni işimle beraber yeni bir karar aldım. Blog yazılarımda yerli forumlarda gördüğüm soruları derinlemesine makaleler olarak ele alacağım. Bu da mantıken ilk yazım olacak.

Soru:

file

Analiz

Soruyu soran, bir tuş vuruşu gerçekleştiğinde bir metodunun çalıştırılmasını istiyor. Fakat soruda net olmayan bir kısım var. Bu algılama uygulama aktif pencere iken mi çalışacak yoksa global bir dinleme mi olacak? Örnekte verilen CTRL+A tuşu aynı zamanda Word'de tüm yazının seçilmesini sağlar; bu durumda uygulama arka planda açıkken Word kullanırken yan etkiler olacaktır. Varsayımdan kaçınıp her iki cevabı da vereceğim.

Cevap

Uygulama özelinde tuş vuruşu yakalama

Windows Forms Application

Klavye vuruşlarını algılamanın aslında bir düğmeye mouse ile tıklanmasını algılamaktan bir farkı yoktur. Sürükle-bırak sevenler için başlayacağım. Formu seçip events panelinden ihtiyacınıza uygun şekilde "key down", "key up" veya "key press"ten birisini seçin ve çift tıklayın. Ben "key up"ı tercih ettim çünkü basılan tuş hakkında çok daha fazla bilgi geriye döndürmekte.
file

Bunu yaptığımda Visual Studio benim için 2 farklı dosyaya ekleme yaptı. İlki Form1.cs dosyası, diğeri ise Form1.Designer.cs dosyası oldu (pek tabii dosya adının ilk parçası formunuzun adına göre değişecektir).

Form1.cs için eklenen kısım:

private void Form1_KeyUp(object sender, KeyEventArgs e)
{
   // buraya bir şeyler yazmamız bekleniyor
}

Form1.Designer.cs için eklenen kısım:

private void InitializeComponent()
{
    //...
    this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyUp);
    //...
}

İkinci dosya ile işimiz yok, olmamalı da çünkü kendisi Visual Studio tarafından oluşturulan kodları tutuyor, bizim yazdıklarımızı değil. Şimdi basılan tuşu ekrana mesaj kutusu ile gösterelim.

private void Form1_KeyUp(object sender, KeyEventArgs e)
{
    MessageBox.Show(e.KeyCode.ToString());
}

Basılan tuş hakkında tüm bilgilere ikinci parametre üzerinden erişebiliriz. Kendisine ait dokümantasyona erişmek için tıklayın.

Örneğin ALT+A kombinasyonunu dinlemek isteseydik:

private void Form1_KeyUp(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.A && e.Alt)
    {
        MessageBox.Show("ALT + A");
    }
}

Not: İki tuş kombinasyonları için (örneğin A+D gibi), yorumlarda belirtildiği üzere Keyboard.IsKeyDown() metodunu kullanmanız gerekir. Çünkü e.KeyCode yalnızca son basılan tuşu döndürür.

Fakat ara birime metin kutusu gibi herhangi bir focus çekecek bir user control koyarsanız tuş vuruşlarını yakalayamadığınızı fark edeceksiniz. Bunu alt nesnelere de yaymak için form özelliklerinden "Key Preview"ı aktifleştirmeniz gerekecektir.
file

Şayet sürükle-bırak yöntemlerini benim gibi sevmiyorsanız, Form1.cs'de şöyle bir ekleme yapmanız aynı işi yapacaktır:

public Form1()
{
    InitializeComponent();
    KeyUp += (_, e) =>
    {
        if (e.KeyCode == Keys.A && e.Alt)
        {
            MessageBox.Show("ALT + A");
        }
    };
}

WPF Application

WPF programcıları genellikle sürükle-bırak yöntemlerini tercih etmedikleri için o kısmı atlıyorum. Basılan tuşu mesaj kutusunda göstermek için şu kod yeterli olacaktır.

public MainWindow()
{
    InitializeComponent();
    KeyDown += (_, e) =>
    {
        MessageBox.Show(e.Key.ToString());
    };
}

Sadece A'ya basıldığında mesaj göstermek istersek:

if (e.Key == Key.A)
{
    MessageBox.Show("A'ya basıldı");
}

WFA'daki focus problemi WPF için de geçerli olacaktır. WPF'de bunun için KeyDown değil de PreviewKeyDown kullanacağız.

Bunun örneğini de iki tuş ile yapalım, örneğin CTRL + A olsun:

PreviewKeyDown += (_, e) =>
{
    if (e.Key == Key.A && Keyboard.IsKeyDown(Key.LeftCtrl))
    {
        MessageBox.Show("CTRL + A'ya basıldı");
    }
};

Aynı kodu ALT tuşu ile denediğinizde sonuç alamayacaksınız. Bunun sebebi ALT'a basıldığında ikinci tuşu Key property'si üzerinden değil de SystemKey üzerinden alıyor olmamız gerekmesidir.

if (e.SystemKey == Key.A && Keyboard.IsKeyDown(Key.LeftAlt))
{
    MessageBox.Show("ALT + A'ya basıldı");
}

Windows genelinde tuş vuruşu yakalama

Bunu başarmak için Windows API'larına kaçmak gerekecek ve bir hook atacağız. Kodu mümkün olduğunca kısa ve temiz yazmaya çalıştım. Önce koda bakalım.

public partial class Form1 : Form
{
    private delegate int KeyboardHookProc(int code, int wParam, ref KeyboardHookStruct lParam);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardHookProc callback, IntPtr hInstance, uint threadId);

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("user32.dll")]
    private static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref KeyboardHookStruct lParam);

    private readonly IntPtr _hook;

    private struct KeyboardHookStruct
        {
            public int VkCode;
            public int ScanCode;
            public int Flags;
            public int Time;
            public int DwExtraInfo;
        }

    public Form1()
    {
        InitializeComponent();
        var hInstance = LoadLibrary("User32");
        _hook = SetWindowsHookEx(13, HookProc, hInstance, 0);
    }

    private int HookProc(int code, int wParam, ref KeyboardHookStruct lParam)
    {
        if (code >= 0 && lParam.Flags == 0)
        {
            var key = (Keys)lParam.VkCode;
            MessageBox.Show(key.ToString());
        }
        return CallNextHookEx(_hook, code, wParam, ref lParam);
    }
}

Açıklamak gerekirse tüm işi yapan API'mız SetWindowsHookEx'e verdiğimiz 13 ("WH_KEYBOARD_LL") parametresidir. Bu parametre neyi dinleyeceğimizi değiştiriyor ve biz klavyeyi tercih ettik. Bu fonksiyon bir callback fonksiyon talep ediyor. Yani işlem gerçekleştiğinde çağıracağı bir metot bekliyor; bu metodun ne alıp ne döndüreceğini bir delegate ile belirtiyoruz.

Not: Yorumlarda code >= 0 kontrolünün hook mesajının geçerli olup olmadığını, lParam.Flags == 0 kontrolünün ise key down/up ayrımını yaptığı sorulmuştu. Mouse olaylarını yakalamak için farklı hook türleri (örneğin WH_MOUSE_LL = 14) kullanılmalıdır.

CapsLock/NumLock Özel Durumu: Yorumlarda belirtildiği üzere, CapsLock ve NumLock tuşları toggle tuşlardır ve on/off durumlarını yakalamak için GetKeyState() API'sini kullanmanız gerekir. Bu tuşlar normal key event'lerinden farklı davranır.

Fonksiyon çalıştıktan sonra bağlama işlemini yenilemek için de CallNextHookEx'i kullanıyoruz.

8 Replies to “C# Tuş Algılama”

  1. Merhaba,
    Şu yazıya istinaden bir sorum olacak;
    “Açıklamak gerekirse tüm işi yapan API’mız SetWindowsHookEx’a verdiğimiz 13 (“WH_KEYBOARD_LL”) parametresi. Bu parametre neyi dinleyeceğimizi değiştiriyor ve biz klavyeyi tercih ettik.”

    Mouse’un sol tuşunu dinlemek için hangi rakamı kullanmalıyız? Bunun bir listesi var mı?

  2. HOCAM merhaba a+d tuşunu okuyup bir butona aktarmak istiyorum. if (e.KeyCode == Keys.W && Keys.A)kodunu yazdığımda hata veriyor. çözümü nedir

  3. hocam merhaba,
    CAPSLOCK VE NUMLOCK ON/OFF OLDUKLARINDA NASIL YAKALAYABİLİRİM. HER İKİ DURUMDA DA AYNI KODU GÖNDERİYOR.

  4. cihan bey HookProc altında bool NumLock = (((ushort)GetKeyState(0x90)) & 0xffff) != 0; kodum ile durum bilgisini almak istiyorum. capslock sonuç verirken numlock sonuç döndürmüyor. neden olabilir?

  5. hocam capslock ile numlock tuşlarını yakalayamadım bu kod ile bunu nasıl yapabiliriz?

  6. if (code >= 0 && lParam.Flags == 0) SATIRINI TAM ANLAYAMADIM. YARDIMCI OLABİLİRMİSİNİZ.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir