2008-09-08 9 views
44

Estoy construyendo una aplicación en C# usando WPF. ¿Cómo puedo enlazar algunas teclas?¿Cómo puedo registrar una tecla rápida global para decir CTRL + MAYÚS + (LETRA) usando WPF y .NET 3.5?

Además, ¿cómo puedo vincularme al Windows key?

+0

De la función Win32 ['RegisterHotKey' function] (http://msdn.microsoft.com/en-us/library/ms646309.aspx):" Los atajos de teclado que involucran la tecla WINDOWS están reservados para que los use el operador sistema." –

+0

@IanKemp: Hasta que no sean utilizados por el SO, puede usarlos en sus aplicaciones. Sí, es por eso que MS dio Win + N y Win + Shift + N a OneNote ;-) –

Respuesta

27

No estoy seguro de lo que quiere decir con "global" aquí, pero aquí va (supongo que quiere decir un comando en el nivel de aplicación, por ejemplo, Guardar todo que se puede activar desde cualquier lugar por Ctrl +Shift +S.)

usted encuentra el mundial UIElement de su elección, por ejemplo, la ventana de nivel superior, que es el padre de todos los controles donde se necesita esta unión. Debido al "burbujeo" de los eventos WPF, los eventos en los elementos secundarios burbujearán hasta la raíz del árbol de control.

Ahora, primero que hay

  1. para unir el Key-Combo con un comando usando un InputBinding como esto
  2. a continuación, puede conexión el comando para el controlador (por ejemplo, código que se llama por SaveAll) a través de CommandBinding.

Para el clave de Windows, se utiliza el miembro enumerado derecho Key, Key.LWin o Key.RWin

public WindowMain() 
    { 
     InitializeComponent(); 
     // Bind Key 
     InputBinding ib = new InputBinding(
      MyAppCommands.SaveAll, 
      new KeyGesture(Key.S, ModifierKeys.Shift | ModifierKeys.Control)); 
     this.InputBindings.Add(ib); 
     // Bind handler 
     CommandBinding cb = new CommandBinding(MyAppCommands.SaveAll); 
     cb.Executed += new ExecutedRoutedEventHandler(HandlerThatSavesEverthing); 
     this.CommandBindings.Add (cb); 
    } 

    private void HandlerThatSavesEverthing (object obSender, ExecutedRoutedEventArgs e) 
    { 
     // Do the Save All thing here. 
    } 
+0

Consulte aquí para obtener un estilo más MVVM http://stackoverflow.com/questions/19697106/create-key-binding-in-wpf – anhoppe

+4

no es realmente global (nivel de aplicación). Si abres una ventana nueva desde MainWindow y presionas la tecla de acceso directo allí, no funcionaría. – Dork

+5

También esto no es "global" desde una perspectiva de Windows. Si su aplicación no está enfocada, esto no funciona. – user99999991

1

No estoy seguro acerca de WPF, pero esto puede ayudar. Utilicé la solución descrita en (modificada para mis necesidades, por supuesto) para una aplicación C# Windows Forms para asignar una combinación CTRL-KEY dentro de Windows para mostrar un formulario C#, y funcionó maravillosamente (incluso en Windows Vista). ¡Espero que ayude y buena suerte!

+2

Esto no funciona con WPF. –

+0

Parece que no funciona con la tecla de Windows. Bueno, es compatible con la clave de Windows, pero las teclas de acceso rápido están reservadas para Windows. – erodewald

0

RegisterHotKey() sugerido por John podría funcionar - la única pega es que requiere un HWND (usando PresentationSource.FromVisual(), y emitiendo el resultado a un HwndSource).

Sin embargo, también deberá responder al mensaje WM_HOTKEY - No estoy seguro de si hay una manera de obtener acceso al WndProc de una ventana WPF o no (lo que se puede hacer para las ventanas de Windows Forms) .

0

Un compañero de trabajo escribió una muestra sobre cómo crear un bajo nivel gancho de teclado para ser utilizado con WPF.

http://blogs.vertigo.com/personal/ralph/Blog/Lists/Posts/Post.aspx?ID=8

+4

El enlace está roto. ¿Sabes que el artículo ha sido movido? –

+2

Google es su amigo, tiene los términos de búsqueda como parte del enlace, por lo que ahora haga clic aquí ... http://blogs.vertigo.com/personal/ralph/Blog/Lists/Posts/Post.aspx ? ID = 8 – Gorgsenegger

+0

@Gorgsenegger El enlace está roto. –

17

Si usted va a mezclar Win32 y WPF, así es como lo hice:

using System; 
using System.Runtime.InteropServices; 
using System.Windows.Interop; 
using System.Windows.Media; 
using System.Threading; 
using System.Windows; 
using System.Windows.Input; 

namespace GlobalKeyboardHook 
{ 
    public class KeyboardHandler : IDisposable 
    { 

     public const int WM_HOTKEY = 0x0312; 
     public const int VIRTUALKEYCODE_FOR_CAPS_LOCK = 0x14; 

     [DllImport("user32.dll")] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc); 

     [DllImport("user32.dll")] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     public static extern bool UnregisterHotKey(IntPtr hWnd, int id); 

     private readonly Window _mainWindow; 
     WindowInteropHelper _host; 

     public KeyboardHandler(Window mainWindow) 
     { 
      _mainWindow = mainWindow; 
      _host = new WindowInteropHelper(_mainWindow); 

      SetupHotKey(_host.Handle); 
      ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage; 
     } 

     void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled) 
     { 
      if (msg.message == WM_HOTKEY) 
      { 
       //Handle hot key kere 
      } 
     } 

     private void SetupHotKey(IntPtr handle) 
     { 
      RegisterHotKey(handle, GetType().GetHashCode(), 0, VIRTUALKEYCODE_FOR_CAPS_LOCK); 
     } 

     public void Dispose() 
     { 
      UnregisterHotKey(_host.Handle, GetType().GetHashCode()); 
     } 
    } 
} 

Puede obtener el código de tecla virtual de la tecla de acceso directo que desea registrar aquí: http://msdn.microsoft.com/en-us/library/ms927178.aspx

Puede haber una manera mejor, pero esto es lo que tengo hasta ahora.

¡Salud!

+0

Supongo que produce una fuga de memoria allí ... –

+0

¿Cómo se produce una fuga de memoria desde este código? Es IDisposable – user99999991

+0

'ComponentDispatcher.ThreadPreprocessMessage + = ComponentDispatcher_ThreadPreprocessMessage;' Esta línea debe invocarse desde UI Thread; de lo contrario, no recibirá mensajes. –

1

Aunque RegisterHotKey a veces es precisamente lo que desea, en la mayoría de los casos probablemente no quiera utilizar teclas de acceso directo en todo el sistema. Terminé usando código como el siguiente:

 
using System.Windows; 
using System.Windows.Interop; 

namespace WpfApp 
{ 
    public partial class MainWindow : Window 
    { 
     const int WM_KEYUP = 0x0101; 

     const int VK_RETURN = 0x0D; 
     const int VK_LEFT = 0x25; 

     public MainWindow() 
     { 
      this.InitializeComponent(); 

      ComponentDispatcher.ThreadPreprocessMessage += 
       ComponentDispatcher_ThreadPreprocessMessage; 
     } 

     void ComponentDispatcher_ThreadPreprocessMessage(
      ref MSG msg, ref bool handled) 
     { 
      if (msg.message == WM_KEYUP) 
      { 
       if ((int)msg.wParam == VK_RETURN) 
        MessageBox.Show("RETURN was pressed"); 

       if ((int)msg.wParam == VK_LEFT) 
        MessageBox.Show("LEFT was pressed"); 
      } 
     } 
    } 
} 
47

Ésta es una solución completa de trabajo, creo que sirve ...

Uso:

_hotKey = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler); 

...

private void OnHotKeyHandler(HotKey hotKey) 
{ 
    SystemHelper.SetScreenSaverRunning(); 
} 

Clase:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Net.Mime; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Windows; 
using System.Windows.Input; 
using System.Windows.Interop; 

namespace UnManaged 
{ 
    public class HotKey : IDisposable 
    { 
     private static Dictionary<int, HotKey> _dictHotKeyToCalBackProc; 

     [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); 

     public const int WmHotKey = 0x0312; 

     private bool _disposed = false; 

     public Key Key { get; private set; } 
     public KeyModifier KeyModifiers { get; private set; } 
     public Action<HotKey> Action { get; private set; } 
     public int Id { get; set; } 

     // ****************************************************************** 
     public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true) 
     { 
      Key = k; 
      KeyModifiers = keyModifiers; 
      Action = action; 
      if (register) 
      { 
       Register(); 
      } 
     } 

     // ****************************************************************** 
     public bool Register() 
     { 
      int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key); 
      Id = virtualKeyCode + ((int)KeyModifiers * 0x10000); 
      bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode); 

      if (_dictHotKeyToCalBackProc == null) 
      { 
       _dictHotKeyToCalBackProc = new Dictionary<int, HotKey>(); 
       ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage); 
      } 

      _dictHotKeyToCalBackProc.Add(Id, this); 

      Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode); 
      return result; 
     } 

     // ****************************************************************** 
     public void Unregister() 
     { 
      HotKey hotKey; 
      if (_dictHotKeyToCalBackProc.TryGetValue(Id, out hotKey)) 
      { 
       UnregisterHotKey(IntPtr.Zero, Id); 
      } 
     } 

     // ****************************************************************** 
     private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled) 
     { 
      if (!handled) 
      { 
       if (msg.message == WmHotKey) 
       { 
        HotKey hotKey; 

        if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey)) 
        { 
         if (hotKey.Action != null) 
         { 
          hotKey.Action.Invoke(hotKey); 
         } 
         handled = true; 
        } 
       } 
      } 
     } 

     // ****************************************************************** 
     // Implement IDisposable. 
     // Do not make this method virtual. 
     // A derived class should not be able to override this method. 
     public void Dispose() 
     { 
      Dispose(true); 
      // This object will be cleaned up by the Dispose method. 
      // Therefore, you should call GC.SupressFinalize to 
      // take this object off the finalization queue 
      // and prevent finalization code for this object 
      // from executing a second time. 
      GC.SuppressFinalize(this); 
     } 

     // ****************************************************************** 
     // Dispose(bool disposing) executes in two distinct scenarios. 
     // If disposing equals true, the method has been called directly 
     // or indirectly by a user's code. Managed and unmanaged resources 
     // can be _disposed. 
     // If disposing equals false, the method has been called by the 
     // runtime from inside the finalizer and you should not reference 
     // other objects. Only unmanaged resources can be _disposed. 
     protected virtual void Dispose(bool disposing) 
     { 
      // Check to see if Dispose has already been called. 
      if (!this._disposed) 
      { 
       // If disposing equals true, dispose all managed 
       // and unmanaged resources. 
       if (disposing) 
       { 
        // Dispose managed resources. 
        Unregister(); 
       } 

       // Note disposing has been done. 
       _disposed = true; 
      } 
     } 
    } 

    // ****************************************************************** 
    [Flags] 
    public enum KeyModifier 
    { 
     None = 0x0000, 
     Alt = 0x0001, 
     Ctrl = 0x0002, 
     NoRepeat = 0x4000, 
     Shift = 0x0004, 
     Win = 0x0008 
    } 

    // ****************************************************************** 
} 
+0

Sé que ha pasado un tiempo, pero cuando registro mi botón PrintScreen, el método de "PrintScreen" dejó de funcionar. "managed = true" tampoco funcionó. ¿Alguna sugerencia? – ErwinOkken

+0

@ErwinOkken, no estoy seguro de entenderlo correctamente. Una tecla rápida es global para usted sesión (~ global para su máquina). Si registra "PrintScreen", entonces cuando lo presione, esa clave debería manejarse en su programa. Cerrar su programa debería permitir que "PrintScreen" se ejecute como ~ normal (antes de ejecutar su programa). ¿Quiere decir que no puede manejar (registrar) "PrintScreen" en su programa? –

+0

Lo siento si no estaba claro. La función PrintScreen no debe perder su característica original: almacenar la imagen en el Portapapeles. Cuando o después de eso sucede, necesito conectarlo. – ErwinOkken

1

He encontrado el proyecto Global Hotkeys in WPF en codeproject.com que hace el trabajo por mí. Es relativamente reciente, no necesita una referencia a System.Windows.Forms y funciona "globalmente" en términos de reacción a la tecla presionada incluso si "tu" aplicación no es la ventana activa.

14

El registro de los accesos directos de nivel de sistema operativo casi nunca es algo bueno: los usuarios no quieren que se meta con su sistema operativo.

Dicho esto, hay una manera mucho más simple y fácil de usar para hacer esto en WPF, si estás bien con la tecla de acceso directo a trabajar dentro de la aplicación única (es decir, el tiempo que su aplicación WPF tiene el foco):

En App.xaml.cs:

protected override void OnStartup(StartupEventArgs e) 
{ 
    EventManager.RegisterClassHandler(typeof(Window), Window.PreviewKeyUpEvent, new KeyEventHandler(OnWindowKeyUp)); 
} 

private void OnWindowKeyUp(object source, KeyEventArgs e)) 
{ 
    //Do whatever you like with e.Key and Keyboard.Modifiers 
} 

Es mejor funciona así de simple solución

+0

¿Necesitas anular el registro manualmente? – Joel

+0

Se elimina automáticamente cuando cierras la aplicación, de lo contrario sí, debes anular el registro dentro de la vida útil de la aplicación. –

+3

+1 por simplicidad y por señalar que los accesos directos de nivel del sistema operativo son malos. Convenido. – Joel

0

de babuino, ya que puede tener múltiples ventanas. Lo modifiqué para que use PreviewKeyDownEvent en lugar de PreviewKeyUpEvent para manejar la repetición con las teclas.

Recomendaría en contra del registro a nivel del sistema operativo a menos que esté escribiendo algo así como una herramienta de recorte o una aplicación de grabación de audio, ya que le permitirá acceder a la funcionalidad cuando la ventana no esté enfocada.

1

Esto es similar a las respuestas ya dadas, pero me resulta un poco más limpia:

using System; 
using System.Windows.Forms; 

namespace GlobalHotkeyExampleForm 
{ 
    public partial class ExampleForm : Form 
    { 
     [System.Runtime.InteropServices.DllImport("user32.dll")] 
     private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk); 
     [System.Runtime.InteropServices.DllImport("user32.dll")] 
     private static extern bool UnregisterHotKey(IntPtr hWnd, int id); 

     enum KeyModifier 
     { 
      None = 0, 
      Alt = 1, 
      Control = 2, 
      Shift = 4, 
      WinKey = 8 
     } 

     public ExampleForm() 
     { 
      InitializeComponent(); 

      int id = 0;  // The id of the hotkey. 
      RegisterHotKey(this.Handle, id, (int)KeyModifier.Shift, Keys.A.GetHashCode());  // Register Shift + A as global hotkey. 
     } 

     protected override void WndProc(ref Message m) 
     { 
      base.WndProc(ref m); 

      if (m.Msg == 0x0312) 
      { 
       /* Note that the three lines below are not needed if you only want to register one hotkey. 
       * The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */ 

       Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);     // The key of the hotkey that was pressed. 
       KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF);  // The modifier of the hotkey that was pressed. 
       int id = m.WParam.ToInt32();          // The id of the hotkey that was pressed. 


       MessageBox.Show("Hotkey has been pressed!"); 
       // do something 
      } 
     } 

     private void ExampleForm_FormClosing(object sender, FormClosingEventArgs e) 
     { 
      UnregisterHotKey(this.Handle, 0);  // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey. 
     } 
    } 
} 

lo he encontrado en fluxbytes.com.

Cuestiones relacionadas