0

I have a windowless winforms application that uses an ApplicationContext to setup a NotifyIcon (TrayIcon) with which the user can control. But I also want to use hotkeys.

I found some good approaches using RegisterHotkey (eg. Global hotkeys in windowless .NET app), but they all need a form or a native window, which I don't want to use because of side-effects.

But I already use a NotifyIcon (TrayIcon) and I guess that has some kind of message pipe to trigger clicks etc. already. How can I use that one to register global hotkeys?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Fox
  • 566
  • 4
  • 14
  • That's not possible, NotifyNativeWindow is intentionally hidden with no way to override its behavior. The form you need just doesn't have to get visible, takes but 5 lines of code and lets you subtract the ApplicationContext doowop, yay for negative code: https://stackoverflow.com/a/1732294/17034 – Hans Passant Jun 17 '20 at 22:39
  • See my [answer here](https://stackoverflow.com/a/17136050/2330053) for another way to accomplish this via NativeWindow instead of a Form. Just realized you already found that thread. What "side effects" are you seeing with NativeWindow? – Idle_Mind Jun 18 '20 at 00:41
  • @Idle_Mind First performance, loading a window/form takes unnessesary resources. Second, sometimes I get the window flashing briefly on startup. Third, the window gets shown in task view/alt+tab view. Fourth, after closing all other apps, the window get's focus sometimes. – Fox Jun 18 '20 at 06:57
  • I don't think you actually tried my NativeWindow approach [here](https://stackoverflow.com/a/17136050/2330053), as it does none of those things. Start with a standard WinForms project, then add the code in the `Program.cs` file. Be sure to change the `Application.Run()` line so it starts with `new MyContext()` instead of the default Form. Afterwards, you can actually delete Form1 completely from the Project. Run it and try hitting `Alt-1`, `Alt-2`, and `Alt-Q`. I could not find my program in the Alt-Tab list, and it does not show up in the TaskBar. No windows showed up, ever. – Idle_Mind Jun 18 '20 at 13:41
  • @Idle_Mind Didn't try exactly that one, no. I may do later. I solved it now with a hook (user32 > SetWindowsHookEx) and it works well. But I read somewhere that hooks are not good, hmm.. – Fox Jun 18 '20 at 14:35
  • Keyboard hooks aren't good or bad, you just need to be careful with them as ALL keystrokes go through your hook. What you do in the hook procedure affects the entire system. If you suppress or change keystrokes then other apps will see those changes (possibly, depending on order of hooks). If your proc takes too long, then the system will appear sluggish to respond to keystrokes. With great power, comes great responsibility? Registering a hotkey, however, means your app will only get notified if that particular combo gets hit; no chance of messing up the global keystrokes then. – Idle_Mind Jun 18 '20 at 14:42
  • On the flip side, when registering a hotkey combo, if a different app has already taken the combination you wanted...then you're **** out of luck. The only way you'd trap that combo then is to go the lower level keyboard hook route... – Idle_Mind Jun 18 '20 at 14:44
  • Oh, I see. That's why antivirus software could think it's spyware. I should test your option then. – Fox Jun 18 '20 at 15:00
  • @Idle_Mind https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey#parameters I noticed that it supports that the handle is null. Can't I just create my own message loop in the ApplicationContext? – Fox Jun 18 '20 at 17:02
  • Never tried it that way. Let us know how it goes! – Idle_Mind Jun 18 '20 at 17:04
  • @Idle_Mind I tried to check how NativeWindow creates it's message loop, but that's to high for me.. https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/NativeWindow.cs,623 I guess it may be this one? The class is huge btw, that's the overhead I wanted to avoid. – Fox Jun 18 '20 at 19:04

1 Answers1

0

I required the same and finally I found this answer on another question Set global hotkeys using C# It is working perfectly with tray (NotifyIcon) application.

I am aligning the code for tray application, just follow the below steps:

1. Create new class "KeyboardHook"

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MyTrayApp
{
    public sealed class KeyboardHook : IDisposable
    {
        // Registers a hot key with Windows.
        [DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
        // Unregisters the hot key with Windows.
        [DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        /// <summary>
        /// Represents the window that is used internally to get the messages.
        /// </summary>
        private class Window : NativeWindow, IDisposable
        {
            private static int WM_HOTKEY = 0x0312;

            public Window()
            {
                // create the handle for the window.
                this.CreateHandle(new CreateParams());
            }

            /// <summary>
            /// Overridden to get the notifications.
            /// </summary>
            /// <param name="m"></param>
            protected override void WndProc(ref Message m)
            {
                base.WndProc(ref m);

                // check if we got a hot key pressed.
                if (m.Msg == WM_HOTKEY)
                {
                    // get the keys.
                    Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
                    ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);

                    // invoke the event to notify the parent.
                    if (KeyPressed != null)
                        KeyPressed(this, new KeyPressedEventArgs(modifier, key));
                }
            }

            public event EventHandler<KeyPressedEventArgs> KeyPressed;

            #region IDisposable Members

            public void Dispose()
            {
                this.DestroyHandle();
            }

            #endregion
        }

        private Window _window = new Window();
        private int _currentId;

        public KeyboardHook()
        {
            // register the event of the inner native window.
            _window.KeyPressed += delegate (object sender, KeyPressedEventArgs args)
            {
                if (KeyPressed != null)
                    KeyPressed(this, args);
            };
        }

        /// <summary>
        /// Registers a hot key in the system.
        /// </summary>
        /// <param name="modifier">The modifiers that are associated with the hot key.</param>
        /// <param name="key">The key itself that is associated with the hot key.</param>
        public void RegisterHotKey(ModifierKeys modifier, Keys key)
        {
            // increment the counter.
            _currentId = _currentId + 1;

            // register the hot key.
            if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key))
            {
                string message = "The hotkey \"Ctrl+Alt+K\" could not be registered. This problem is probably causedby another tool claiming usage of the same hotkey!\r\n\r\nAll KeepOn features still work directly from the tray icon context menu without hotkey.";
                string title = "KeepOn";
                MessageBoxButtons buttons = MessageBoxButtons.OK;
                MessageBoxIcon icon = MessageBoxIcon.Exclamation;
                DialogResult result = MessageBox.Show(message, title, buttons,icon);
            }
        }

        /// <summary>
        /// A hot key has been pressed.
        /// </summary>
        public event EventHandler<KeyPressedEventArgs> KeyPressed;

        #region IDisposable Members

        public void Dispose()
        {
            // unregister all the registered hot keys.
            for (int i = _currentId; i > 0; i--)
            {
                UnregisterHotKey(_window.Handle, i);
            }

            // dispose the inner native window.
            _window.Dispose();
        }

        #endregion
    }

    /// <summary>
    /// Event Args for the event that is fired after the hot key has been pressed.
    /// </summary>
    public class KeyPressedEventArgs : EventArgs
    {
        private ModifierKeys _modifier;
        private Keys _key;

        internal KeyPressedEventArgs(ModifierKeys modifier, Keys key)
        {
            _modifier = modifier;
            _key = key;
        }

        public ModifierKeys Modifier
        {
            get { return _modifier; }
        }

        public Keys Key
        {
            get { return _key; }
        }
    }

    /// <summary>
    /// The enumeration of possible modifiers.
    /// </summary>
    [Flags]
    public enum ModifierKeys : uint
    {
        Alt = 1,
        Control = 2,
        Shift = 4,
        Win = 8
    }
}

2. Add code to your main class (app startup class)

Check your startup class in Main() method of Program.cs file
Application.Run(new TrayApplicationContext());

public class TrayApplicationContext : ApplicationContext
{
     KeyboardHook hook = new KeyboardHook();

    public TaskTrayApplicationContext()
    {
        // register the event that is fired after the key press.
        hook.KeyPressed += new EventHandler<KeyPressedEventArgs>(Close_App);
    
        // register the control + alt+ F12 combination as hot key.
        hook.RegisterHotKey(ModifierKeys.Control | ModifierKeys.Alt, Keys.F12);
    }

    void Close_App(object sender, KeyPressedEventArgs e)
    {
        Application.Exit();
    }
}

Say thanks to stackoverflow community to make it possible!!

Kuldeep Gill
  • 71
  • 2
  • 11
  • That uses a window, with all it's overhead, and that's what I don't want, as stated. – Fox Oct 24 '20 at 22:15