Diskuze: WPF - Global key bind

C# .NET .NET (C# a Visual Basic) WPF - Global key bind American English version English version

Aktivity (1)
Avatar
Elisse
Člen
Avatar
Elisse:29.7.2016 19:06

Zdravím, potřeboval bych prosím poradit, jak napsat key bind (aplikace bude reagovat například na zmáčknutí F2 i když budu úplně v jiné aplikace (příklad. Push to talk u TeamSpeaku), hádám, že budu muset zabrousit do WinAPI? Nebo to jde nějak jednodušeji? Díky moc :)

 
Odpovědět 29.7.2016 19:06
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:31.7.2016 1:27

Ahoj. Není to zase tak těžké. :-)

Je to sice postup pro WinForms, ale nevidím důvod, proč by to nemělo fungovat i ve WPF...

Krok 1 - Do těla třídy je třeba přidat:

[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr windowHandle, int idOfShortCut, int keyModifiers, int keyCode);
[DllImport("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr windowHandle, int idOfShortCut);

// Identifikační číslo klávesové zkratky
const int HOTKEY_ID_FOR_F2 = 1;

Krok 2 - Registrace zkratky při inicializaci:
V tomto kódu je pod instancí this ukrytá instance mého hlavního formuláře. Kód může být umístěn v konstruktoru formuláře nebo třeba v události Load.

// Modifikační hodnoty kláves: Alt = 1, Ctrl = 2, Shift = 4, Win = 8
// Kombinace je možná pomocí sčítání (Jde v podstatě o maskování)
// ALT+CTRL = 1 + 2 = 3 , CTRL+SHIFT = 2 + 4 = 6...
RegisterHotKey(this.Handle, HOTKEY_ID_FOR_F2, 0, (int)Keys.F2);

Krok 3 - Odchycení klávesy:

protected override void WndProc(ref Message m)
{
    if (m.Msg == 0x0312 && m.WParam.ToInt32() == HOTKEY_ID_FOR_F2)
    {
        // Udělej, co potřebuješ...
    }

    base.WndProc(ref m);
}

Krok 4 - Uvolnění zkratky:
Je vhodné tento kód zavolat v události FormClosing.

UnregisterHotKey(this.Handle, HOTKEY_ID_FOR_F2);

Snad jsem na nic nezapomněl... Dej pak vědět, jestli Ti to funguje... ;-)

 
Nahoru Odpovědět 31.7.2016 1:27
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:1.8.2016 13:16

Hmmm máš pravdu vypadá to jednoduše :) Vyzkouším hnedka až k tomu zase sednu.

 
Nahoru Odpovědět 1.8.2016 13:16
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:1.8.2016 13:33

Ještě takový dotázek jen, jak todle funguje v případě zavolání něčeho co už něco používá, bez problému to funguje a zkrátka se to zavolá v obou aplikacích? Nebo s tím může být nějaký problém?

 
Nahoru Odpovědět 1.8.2016 13:33
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:1.8.2016 15:00

No, vzhledem k tomuto,

base.WndProc(ref m);

by to problém být neměl.

Tím je vlastně řečeno, že jakmile provedeš tu vlastní implemetaci, zavolá se původní metoda, kterou přepisuješ. Pokud to tedy chápu správně, tak by v případě shody té zkratky (v případě, že by tu metodu přepisovalo více aplikací), bylo nějaké pořadí, ve kterém si ty aplikace splní tu vlastní implemetaci až se to vrátí až na tu původní nepřepsanou metodu.

Takhle to chápu já, ale jestli se pletu nebo má někdo lepší vyvětlení, prosím o doplnění... :)

 
Nahoru Odpovědět 1.8.2016 15:00
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:1.8.2016 15:23

Bezva to zní dobře, dám pak tedy vědět jak jsem dopadl až k tomu sednu :)

 
Nahoru Odpovědět 1.8.2016 15:23
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:2.8.2016 13:03

Zdravím, přidal jsem řádek

RegisterHotKey(this.Handle, HOTKEY_ID_FOR_F2, 0, (int)Keys.F2);

k eventu Form_Loaded

Nicméně mi prej chybí reference pro Handle . Knihovny jsem samozřejmě importoval.

 
Nahoru Odpovědět 2.8.2016 13:03
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:2.8.2016 13:11

Zkus toto...

private void Window_Loaded(object sender, RoutedEventArgs e)
{
        IntPtr myHandle = new WindowInteropHelper(this).Handle;
}
 
Nahoru Odpovědět 2.8.2016 13:11
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na David Oczka
David Oczka:2.8.2016 13:19

Ještě jsem zapomněl uvést, že v případě, že by šlo o jiné než hlavní okno, tak místo this uvedeš instanci toho daného okna... Třeba:

IntPtr myHandle = new WindowInteropHelper(myWindow).Handle;
 
Nahoru Odpovědět 2.8.2016 13:19
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:2.8.2016 13:35

Jasné, mám to v main formu :) teď ovšem vůbec nemám tušení jak upravit tu metodu na zachycení klávesy 8-|

Editováno 2.8.2016 13:36
 
Nahoru Odpovědět 2.8.2016 13:35
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:2.8.2016 16:12

No, ono to řešení pro WPF úplně fungovat nebude, tak se omlouvám za špatný návod... Ale je to alespoň nová zkušenost pro mě... ;)

Tady máš řešení:

Do referencí je ale třeba z Assemblies přidat System.Window­s.Forms a pak přidat stejnojmenný using.

// Importy
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);

[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

// Identifikační číslo klávesové zkratky
const int HOTKEY_ID_FOR_F2 = 1;

// Handle pro naše okno
IntPtr myHandle;

// Událost Loaded (Handle pro okno zde již 100% existuje)
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    myHandle = new WindowInteropHelper(this).Handle;
    var source = PresentationSource.FromVisual(this) as HwndSource;
    source?.AddHook(WndProc);

    // Pozor! Toto nebude fungovat, protože enum Key z WPF má jiné hodnoty kláves
    // než enum Keys z WinFormů. Proto máme ten using System.Windows.Forms
    // RegisterHotKey(myHandle, HOTKEY_ID_FOR_F2, 0, (int)Key.F2);

    RegisterHotKey(myHandle, HOTKEY_ID_FOR_F2, 0, (int)Keys.F2);
}

// Odchycení klávesové zkratky
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == 0x0312 && wParam.ToInt32() == HOTKEY_ID_FOR_F2)
    {
        this.Title = "Funguje to!";
    }
    return IntPtr.Zero;
}

// Událost Closing
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    UnregisterHotKey(myHandle, HOTKEY_ID_FOR_F2);
}

Docela dlouho jsem se zasekal na tom, než mě napadlo zkontrolovat hodnoty těch kláves ve WPF a WF... %P

Teď už by to snad mělo být v pohodě... Mě to ve WPF aplikaci funguje, tak pak dej vědět, co u Tebe... :-`

Akceptované řešení
+20 Zkušeností
+1 bodů
Řešení problému
 
Nahoru Odpovědět 2.8.2016 16:12
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:2.8.2016 20:18

Paráda, vše funguje jak má! :) Díky todle bych já nikdy nevymyslel, si musím někde uložit ať se mi to neztratí :) Moc děkuji

 
Nahoru Odpovědět 2.8.2016 20:18
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:3.8.2016 23:45

Zdravím, mohl bych poprosit ještě o pomoc s tím když budu chtít nastavit těch bindů více? Zkoušel jsem různé varianty a ve finále se mi dařilo aby fungoval ten bind jen jeden zároveň.

 
Nahoru Odpovědět 3.8.2016 23:45
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:4.8.2016 9:49

Jsem celkem zvědavý, co se Ti povedlo stvořit... Nicméně, tady:

// Importy
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);

[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

// Identifikační číslo klávesové zkratky
const int HOTKEY_ID_FOR_F2 = 1;
const int HOTKEY_ID_FOR_F3 = 2;

// Handle pro okno
IntPtr myHandle;

// Loaded událost
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    myHandle = new WindowInteropHelper(this).Handle;
    var source = PresentationSource.FromVisual(this) as HwndSource;
    source?.AddHook(WndProc);

    RegisterHotKey(myHandle, HOTKEY_ID_FOR_F2, 0, (int)Keys.F2);
    RegisterHotKey(myHandle, HOTKEY_ID_FOR_F3, 0, (int)Keys.F3);
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == 0x0312)
    {
        switch(wParam.ToInt32())
        {
            case HOTKEY_ID_FOR_F2:
                this.Title = "Funguje!";
                break;

            case HOTKEY_ID_FOR_F3:
                this.Title = "Pořád funguje!";
                break;

            default:
                break;
        }

    }

    return IntPtr.Zero;
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    UnregisterHotKey(myHandle, HOTKEY_ID_FOR_F2);
    UnregisterHotKey(myHandle, HOTKEY_ID_FOR_F3);
}
 
Nahoru Odpovědět 4.8.2016 9:49
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:4.8.2016 10:36

Hmm pokud vím udělal jsem prakticky to stejné jen bez switche a měl jsem tam 2x rozdílné if :-O No mrknu na to zase až nebudu v práci, děkuji :)

 
Nahoru Odpovědět 4.8.2016 10:36
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:4.8.2016 10:47

A použil jsi určitě enum Keys?

 
Nahoru Odpovědět 4.8.2016 10:47
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:4.8.2016 12:30

Určitě ano, u té registrace jsem používal ten stejný řádek že jo...

RegisterHotKey(myHandle, HOTKEY_ID_FOR_??, 0, (int)Keys.??);
RegisterHotKey(myHandle, HOTKEY_ID_FOR_??, 0, (int)Keys.??);

Jen jsem měnil otazníčky :)

Až k tomu doma sednu můžu poslat přesně to co mi nešlo :)

Editováno 4.8.2016 12:31
 
Nahoru Odpovědět 4.8.2016 12:30
Avatar
Elisse
Člen
Avatar
Odpovídá na David Oczka
Elisse:4.8.2016 21:59

Ahh najít chybu mi trvalo asi 30 sekund po otevření kódu, return z WndProc na špatném místě :)

Jen menší point vzhledem k absenci původního base.WndProc(ref m); již nelze otevřít program aby fungovaly bindy ve všech nově otevřených instancích programu :)

PS: nepotřebuju to jen tak pro upozornění, které je ti ale asi jasné.

 
Nahoru Odpovědět 4.8.2016 21:59
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na Elisse
David Oczka:6.8.2016 21:11

Nedalo mi to, tak jsem si ověřil testem pravdivost svého tvrzení a musím se bohužel opravit. Jednu klávesou zkratku může v jednu chvíli využívat pouze jedna aplikace a to ta, která si ji zaregistruje jako první. Otestoval jsem to pro WinFormové a WPF aplikace i vzájemně mezi nimi. Nevím, jestli to jde v jiných jazycích, nějakým jiným přístupem, ale v C# to tímto způsobem nelze.

Takže se omlouvám a ruším své tvrzení o tom, že se někde vytváří fronta pro klávesové zkratky, které jsou zavoláním base.WndProc(ref m); provedeny. O:-)

Možná nás mělo trknout, že metoda RegisterHotKey je typu bool a navrací false v případě obsazené zkratky, také v případě, že je volána z konzolové aplikace (To se mimochodem řeší skrytým formulářem), anebo pokud byla zavolána z jiného vlákna, než ve kterém bylo vytvořeno okno, kterému chceme zkratku přiřadit... :-)

Teď je již doufám toto vlákno plně vyřešeno... 8-)

 
Nahoru Odpovědět  +3 6.8.2016 21:11
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 19 zpráv z 19.