3

I've read similar questions around this issue, including Best way to tackle global hotkey processing in C#? and Set global hotkeys using C#. I've also investigated a NuGet package Global Hotkeys which appears to be in its infancy.

The problem here is, most of them seem to be designed for Winforms, or could probably run in WPF. The P/Invoke they're using seems to require a window handle. I'm thinking of having a windoless application here, i.e running without a main form or window, except when a certain key combo is pressed, so there might not actually be a handle.

So, would passing a cheeky 0 as the Window handle for the P/Invoke cause it to not look for a window to process the keypress on? Or is my best bet here to use an invisible unfocusable Window?

To add a little context, I'm making a windowless app to be used with TTS providing the feedback for control, my target audience here are blind and visually impaired users. Occasionally, things will have to be entered, so I want to be able to launch forms when necessary, but for the most part I'd like there to be no window cluttering things up.

Some sample code (I can't verify if this would work properly yet).

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    ScreenReader.sapiEnable(true);
    ScreenReader.SayString("Launching application...");
    // bind hotkeys here
    Application.Run();
}

// called when the right keyboard shortcut is pressed
static void ExitApp()
{
    ScreenReader.SayString("Exiting program");
    Application.Exit();
}

Thanks for any help you may be able to provide.

Community
  • 1
  • 1
Craig Brett
  • 2,295
  • 1
  • 22
  • 27

3 Answers3

7

Using RegisterHotkey() is boilerplate to detect a hotkey press. I won't repeat it here, you can easily google sample code from the function name.

It does however require a window, no workaround for that. It just doesn't have to be a visible window. By far the simplest way to create an invisible window is to use a Form class, like you do in any Winforms application, and set ShowInTaskbar = False, WindowState = Minimized, FormBorderStyle = FixedToolWindow. Call RegisterHotkey() in your OnLoad() method override, you'll need the Handle property. Easy peasy.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I've given that form idea a go, it still appears in the alt-tab list of applications and people can still bring up the blank form, since it's merely minimized. I'll see if I can build from this to get it truly out of the way, though. And since this method uses a form, I can just borrow pre-existing code, so it's got that advantage. – Craig Brett Jun 16 '13 at 13:45
  • Post updated. I completely stopped using Alt+Tab since Win7 :) – Hans Passant Jun 16 '13 at 13:48
  • Well, I don't know how it's done it, but it's done it :) I'll keep checking to make sure it's really out of the way but I think it's a winner – Craig Brett Jun 16 '13 at 13:59
  • I'm still finding it possible to get to this window if I'm pretty unlucky. If I close all other windows, this seems to get focus out of nowhere. I've tried setting the window state to minimised when the form's activated. Any other suggestions? – Craig Brett Jul 04 '13 at 23:17
  • You were pretty adamant about a "windowless app". Now you've changed your mind and you actually do want a window? Click the Ask Question button to explain what you really want. – Hans Passant Jul 05 '13 at 00:08
  • No no, I don't. That's what I mean. I don't want there to be a window, but if I'm unlucky, this window becomes visible and gets focus. – Craig Brett Jul 05 '13 at 06:06
  • Well, I solved this myself by kind of cheating. When the user does trigger the activate event on the form, I focus them on the desktop, since I can't see how they could get focus to the form if anything else is open. Does feel a little dirty but I'm open to better suggestions. [This question](http://stackoverflow.com/questions/12535165/how-do-i-set-the-focus-to-the-desktop-from-within-my-c-sharp-application) is how I achieved moving to the desktop, in case you're curious. – Craig Brett Jul 05 '13 at 06:56
  • @CraigBrett In case you haven't found anything better and still looking for a way to disable focus on a WPF Window: http://stackoverflow.com/questions/12591896/disable-wpf-window-focus – Lunyx Nov 13 '15 at 15:31
5

You could also use the NativeWindow class. In the example below, I'm also using an ApplicationContext which is a great way to start a "windowless" application:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MyContext());
    }
}

public class MyContext : ApplicationContext
{

    private MyHotKey hotkeys = null;

    public MyContext()
    {
        hotkeys = new MyHotKey();
        hotkeys.HotkeyPressed += new MyHotKey.HotkeyDelegate(hotkeys_HotkeyPressed);
    }

    private void hotkeys_HotkeyPressed(int ID)
    {
        switch (ID)
        {
            case 1001:
                MessageBox.Show("Alt-1");
                break;

            case 1002:
                MessageBox.Show("Alt-2");
                break;

            case 1003: // Alt-Q
                Application.Exit();
                break;
            default:
                break;
        }
    }

}

public class MyHotKey : NativeWindow
{

    private const int WM_HOTKEY = 0x0312;
    private const int WM_DESTROY = 0x0002;

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    private List<Int32> IDs = new List<int>();

    public delegate void HotkeyDelegate(int ID);
    public event HotkeyDelegate HotkeyPressed;

    public MyHotKey()
    {
        this.CreateHandle(new CreateParams());
        Application.ApplicationExit += new EventHandler(Application_ApplicationExit);

        RegisterCombo(1001, 1, (int)Keys.D1);
        RegisterCombo(1002, 1, (int)Keys.D2);
        RegisterCombo(1003, 1, (int)Keys.Q);
    }

    private void RegisterCombo(Int32 ID, int fsModifiers, int vlc)
    {
        if (RegisterHotKey(this.Handle, ID, fsModifiers, vlc))
        {
            IDs.Add(ID);
        }
    }

    private void Application_ApplicationExit(object sender, EventArgs e)
    {
        this.DestroyHandle();
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_HOTKEY:
                if (HotkeyPressed != null)
                {
                    HotkeyPressed(m.WParam.ToInt32());
                }
                break;

            case WM_DESTROY: // fires when "Application.Exit();" is called
                foreach (int ID in IDs)
                {
                    UnregisterHotKey(this.Handle, ID);
                }
                break;
        }
        base.WndProc(ref m);
    }

}
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • This looks similar to other code I've seen, except that it uses an ApplicationContext. I'll consider this as an alternative. Though it's not as easy to understand as the code for Winforms, it doesn't depend on a form, which feels less like a sidestep. – Craig Brett Jun 16 '13 at 18:11
-1

Just create global kbhook like this

Peuczynski
  • 4,591
  • 1
  • 19
  • 33