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ıkta 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 dinlememi olacak? Örnekte verilen CTRL+A tuşu aynı zamanda Word'de tüm yazının seçilmesini sağlar bu durumda uygulama arka tarafta açıkken Word kullandığıma yan etikler 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" den 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");
    }
}

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"i aktifleştirmeniz gerekecektir.
file

Şayet, sürükle-bırak yöntemleri 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çinde 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 uşu Key propertysi üzerinden değil de SystemKey üzerinden alıyor olmamız gerekmesi.

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 gerecek 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'a verdiğimiz 13 ("WH_KEYBOARD_LL") parametresi. 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. lParam.Flags'ı kontrol etme sebebimiz ise key_down ve key_up'ı ayırmak. Fonksiyon çalıştıktan sonra bağlama işlemini yenilemek için de CallNextHook'u kullanıyoruz.

Bir cevap yazın

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