HotkeyListener icon indicating copy to clipboard operation
HotkeyListener copied to clipboard

WPF support for HotkeySelector

Open JensPLarsen opened this issue 4 years ago • 1 comments

Hello, this project is awesome.

Do you have any plans to add WPF support for the HotkeySelector?

I have tried to make my own which does work.

It uses System.Windows.Controls.TextBox

Add references to these (WPF) assemblies:

WindowsBase
PresentationCore
PresentationFrameWork

HotkeySelectorWPF.cs

#region Copyright

/*
 * Developer    : Willy Kimura (WK).
 * Library      : HotkeySelector.
 * License      : MIT.
 * 
 */

#endregion

using System;
using System.Collections;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Controls;

namespace WK.Libraries.HotkeyListenerNS
{
    /// <summary>
    /// Provides support for enabling standard Windows controls 
    /// and User controls to select hotkeys at runtime. 
    /// Combined with the <see cref="HotkeyListener"/> class, 
    /// you can easily enable the selection and registering of 
    /// hotkeys for a seamless end-user experience.
    /// </summary>
    //[DebuggerStepThrough]
    [Description("Provides support for enabling standard Windows controls " +
                 "and User controls to select hotkeys at runtime.")]
    public partial class HotkeySelectorWPF : Component
    {
        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="HotkeySelector"/> class.
        /// </summary>
        public HotkeySelectorWPF() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="HotkeySelector"/> class.
        /// </summary>
        public HotkeySelectorWPF(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        #endregion

        #region Fields

        // These variables store the selected hotkey and modifier key(s).
        private Keys _hotkey = Keys.None;
        private Keys _modifiers = Keys.None;

        // ArrayLists used to enforce the use of proper modifiers.
        // Shift+A isn't a valid hotkey, for instance.
        private ArrayList _needNonShiftModifier = null;
        private ArrayList _needNonAltGrModifier = null;

        // Stores the list of enabled hotkey selection controls.
        private List<System.Windows.Controls.Control> _controls = new List<System.Windows.Controls.Control>();

        #endregion

        #region Properties

        #region Public

        /// <summary>
        /// Gets or sets the text to be displayed in a 
        /// control when no hotkey has been set. 
        /// (Preferred default text is "None")
        /// </summary>
        public string EmptyHotkeyText { get; set; } = "None";

        /// <summary>
        /// Gets or sets the text to be displayed in a control 
        /// when an invalid or unsupported hotkey is pressed.
        /// (Preferred default text is "(Unsupported)")
        /// </summary>
        public string InvalidHotkeyText { get; set; } = "Unsupported";

        #endregion

        #endregion

        #region Methods

        #region Public

        /// <summary>
        /// Enables a control for hotkey selection and previewing.
        /// This will make use of the control's Text property to 
        /// preview the current hotkey selected.
        /// </summary>
        /// <param name="control">The control to enable.</param>
        public bool Enable(System.Windows.Controls.TextBox control)
        {
            try
            {
                control.Text = EmptyHotkeyText;

                control.KeyDown += new System.Windows.Input.KeyEventHandler(OnKeyDown);
                control.KeyUp += new System.Windows.Input.KeyEventHandler(OnKeyUp);

                ResetModifiers();

                try
                {
                    _controls.Add(control);
                }
                catch (Exception) { }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Enables a control for hotkey selection and previewing.
        /// This will make use of the control's Text property to 
        /// preview the current hotkey selected.
        /// </summary>
        /// <param name="control">The control to enable.</param>
        /// <param name="hotkey">Assign the default hotkey to be previewed in the control.</param>
        public bool Enable(System.Windows.Controls.TextBox control, Hotkey hotkey)
        {
            try
            {
                Enable(control);

                _hotkey = hotkey.KeyCode;
                _modifiers = hotkey.Modifiers;

                Refresh(control);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Disables a control for hotkey selection and previewing.
        /// </summary>
        /// <param name="control">The control to disable.</param>
        /// <param name="clearKeys">Clear the control's previewed keys?</param>
        public bool Disable(System.Windows.Controls.TextBox control, bool clearKeys = true)
        {
            try
            {
                control.KeyDown -= OnKeyDown;
                control.KeyUp -= OnKeyUp;

                if (clearKeys)
                    control.Text = string.Empty;

                try
                {
                    if (_controls.Contains(control))
                        _controls.Remove(control);
                }
                catch (Exception) { }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a specific 
        /// control is enabled for hotkey selection.
        /// </summary>
        /// <param name="control">The control to determine.</param>
        public bool IsEnabled(System.Windows.Controls.TextBox control)
        {
            if (_controls.Contains(control))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Sets a hotkey selection to be previewed in a control. 
        /// Thsi does not automatically enable the control for 
        /// hotkey selection. For this, please use the <see cref="Enable(Control)"/> method.
        /// </summary>
        /// <param name="control">The control to set.</param>
        /// <param name="hotkey">Provide a standard key or key combination string.</param>
        public bool Set(System.Windows.Controls.TextBox control, Hotkey hotkey)
        {
            try
            {
                Refresh(control);

                control.Text = Convert(hotkey);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Sets a hotkey selection to be previewed in a control. 
        /// Thsi does not automatically enable the control for 
        /// hotkey selection. For this, please use the <see cref="Enable(Control)"/> method.
        /// </summary>
        /// <param name="control">The control to set.</param>
        /// <param name="key">Provide a standard key selection.</param>
        /// <param name="modifiers">Provide a modifier key selection.</param>
        public bool Set(System.Windows.Controls.TextBox control, Keys key, Keys modifiers)
        {
            try
            {
                _hotkey = key;
                _modifiers = modifiers;

                Refresh(control);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Clears the currently previewed hotkey 
        /// selection displayed in a control.
        /// </summary>
        public void Clear(System.Windows.Controls.TextBox control)
        {
            this._hotkey = Keys.None;
            this._modifiers = Keys.None;

            Refresh(control);
        }

        /// <summary>
        /// (Variant of the <see cref="Clear(System.Windows.Controls.TextBox)"/> method) 
        /// Clears the currently previewed hotkey 
        /// selection displayed in a control.
        /// </summary>
        public void Reset(System.Windows.Controls.TextBox control)
        {
            this._hotkey = Keys.None;
            this._modifiers = Keys.None;

            Refresh(control);
        }

        /// <summary>
        /// [Helper] Converts keys or key combinations to their string types.
        /// </summary>
        /// <param name="hotkey">The hotkey to convert.</param>
        public string Convert(Hotkey hotkey)
        {
            try
            {
                _hotkey = hotkey.KeyCode;
                _modifiers = hotkey.Modifiers;

                string parsedHotkey = string.Empty;

                // No modifier or shift only, and a hotkey that needs another modifier.
                if ((_modifiers == Keys.Shift || _modifiers == Keys.None))
                {
                    if (_needNonShiftModifier != null && _needNonShiftModifier.Contains((int)this._hotkey))
                    {
                        if (this._modifiers == Keys.None)
                        {
                            // Set Ctrl+Alt as the modifier unless Ctrl+Alt+<key> won't work.
                            if (_needNonAltGrModifier.Contains((int)this._hotkey) == false)
                            {
                                this._modifiers = Keys.Alt | Keys.Control;
                            }
                            else
                            {
                                // ...In that case, use Shift+Alt instead.
                                this._modifiers = Keys.Alt | Keys.Shift;
                            }
                        }
                        else
                        {
                            // User pressed Shift and an invalid key (e.g. a letter or a number), 
                            // that needs another set of modifier keys.
                            this._hotkey = Keys.None;
                        }
                    }
                }

                // Without this code, pressing only Ctrl 
                // will show up as "Control + ControlKey", etc.
                if (this._hotkey == Keys.Menu || /* Alt */
                    this._hotkey == Keys.ShiftKey ||
                    this._hotkey == Keys.ControlKey)
                {
                    this._hotkey = Keys.None;
                }

                if (this._modifiers == Keys.None)
                {
                    // LWin/RWin don't work as hotkeys...
                    // (neither do they work as modifier keys in .NET 2.0).
                    if (_hotkey == Keys.None || _hotkey == Keys.LWin || _hotkey == Keys.RWin)
                    {
                        parsedHotkey = string.Empty;
                    }
                    else
                    {
                        parsedHotkey = this._hotkey.ToString();
                    }
                }
                else
                {
                    parsedHotkey = this._modifiers.ToString() + " + " + this._hotkey.ToString();
                }

                return parsedHotkey;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        #endregion

        #region Private

        /// <summary>
        /// Resets the hotkey modifiers to their defaults.
        /// </summary>
        private void ResetModifiers()
        {
            // Fill the ArrayLists that contain  
            // all invalid hotkey combinations.
            _needNonShiftModifier = new ArrayList();
            _needNonAltGrModifier = new ArrayList();

            PopulateModifierLists();
        }

        /// <summary>
        /// Populates the ArrayLists specifying disallowed Hotkeys 
        /// such as Shift+A, Ctrl+Alt+4 (produces 'dollar' sign).
        /// </summary>
        private void PopulateModifierLists()
        {
            // Shift + 0 - 9, A - Z.
            for (Keys k = Keys.D0; k <= Keys.Z; k++)
                _needNonShiftModifier.Add((int)k);

            // Shift + Numpad keys.
            for (Keys k = Keys.NumPad0; k <= Keys.NumPad9; k++)
                _needNonShiftModifier.Add((int)k);

            // Shift + Misc (,;<./ etc).
            for (Keys k = Keys.Oem1; k <= Keys.OemBackslash; k++)
                _needNonShiftModifier.Add((int)k);

            // Shift + Space, PgUp, PgDn, End, Home.
            for (Keys k = Keys.Space; k <= Keys.Home; k++)
                _needNonShiftModifier.Add((int)k);

            // Misc keys that we can't loop through.
            _needNonShiftModifier.Add((int)Keys.Insert);
            _needNonShiftModifier.Add((int)Keys.Help);
            _needNonShiftModifier.Add((int)Keys.Multiply);
            _needNonShiftModifier.Add((int)Keys.Add);
            _needNonShiftModifier.Add((int)Keys.Subtract);
            _needNonShiftModifier.Add((int)Keys.Divide);
            _needNonShiftModifier.Add((int)Keys.Decimal);
            _needNonShiftModifier.Add((int)Keys.Return);
            _needNonShiftModifier.Add((int)Keys.Escape);
            _needNonShiftModifier.Add((int)Keys.NumLock);
            _needNonShiftModifier.Add((int)Keys.Scroll);
            _needNonShiftModifier.Add((int)Keys.Pause);

            // Ctrl+Alt + 0 - 9.
            for (Keys k = Keys.D0; k <= Keys.D9; k++)
                _needNonAltGrModifier.Add((int)k);
        }

        /// <summary>
        /// Refreshes the previewed hotkey combination displayed in a control.
        /// </summary>
        /// <param name="control">
        /// The control providing hotkey selection.
        /// </param>
        private void Refresh(System.Windows.Controls.TextBox control)
        {
            Refresh(control, false);
        }

        /// <summary>
        /// Refreshes the previewed hotkey combination displayed in a control.
        /// </summary>
        /// <param name="control">
        /// The control providing hotkey selection.
        /// </param>
        /// <param name="internalCall">
        /// Specifies whether this function is 
        /// called internally or by the user.
        /// </param>
        private void Refresh(System.Windows.Controls.TextBox control, bool internalCall)
        {
            try
            {
                string parsedHotkey = string.Empty;

                // No hotkey set.
                if (this._hotkey == Keys.None && this._modifiers == Keys.None)
                {
                    control.Text = EmptyHotkeyText;
                    return;
                }

                // LWin/RWin don't work as hotkeys...
                // (neither do they work as modifier keys in .NET 2.0).
                if (this._hotkey == Keys.LWin || this._hotkey == Keys.RWin)
                {
                    control.Text = InvalidHotkeyText;

                    return;
                }

                if (this._modifiers == Keys.None)
                {
                    if (this._hotkey == Keys.None)
                    {
                        control.Text = EmptyHotkeyText;

                        return;
                    }
                    else
                    {
                        // We get here if we've got a hotkey that is valid without a modifier,
                        // like F1-F12, Media-keys etc.
                        control.Text = this._hotkey.ToString();

                        return;
                    }
                }
                else
                {
                    // Only validate input if it comes from the user.
                    if (internalCall == false)
                    {
                        // No modifier or shift only, and a hotkey that needs another modifier.
                        if ((this._modifiers == Keys.Shift || this._modifiers == Keys.None) &&
                            this._needNonShiftModifier.Contains((int)this._hotkey))
                        {
                            if (this._modifiers == Keys.None)
                            {
                                // Set Ctrl+Alt as the modifier unless Ctrl+Alt+<key> won't work.
                                if (_needNonAltGrModifier.Contains((int)this._hotkey) == false)
                                {
                                    this._modifiers = Keys.Alt | Keys.Control;
                                }
                                else
                                {
                                    // ...In that case, use Shift+Alt instead.
                                    this._modifiers = Keys.Alt | Keys.Shift;
                                }
                            }
                            else
                            {
                                // User pressed Shift and an invalid key (e.g. a letter or a number), 
                                // that needs another set of modifier keys.
                                this._hotkey = Keys.None;

                                control.Text = this._modifiers.ToString() + $" + {InvalidHotkeyText}";

                                return;
                            }
                        }
                    }
                }

                // Without this code, pressing only Ctrl 
                // will show up as "Control + ControlKey", etc.
                if (this._hotkey == Keys.Menu || /* Alt */
                    this._hotkey == Keys.ShiftKey ||
                    this._hotkey == Keys.Alt ||
                    this._hotkey == Keys.ControlKey ||
                    this._hotkey == Keys.LControlKey ||
                    this._hotkey == Keys.RControlKey ||
                    this._hotkey == Keys.LShiftKey ||
                    this._hotkey == Keys.RShiftKey)
                {
                    this._hotkey = Keys.None;
                }

                // A final compilation of the processed keys in string format.
                if (this._modifiers == Keys.None)
                    parsedHotkey = this._hotkey.ToString();
                else
                    parsedHotkey = this._modifiers.ToString() + " + " + this._hotkey.ToString();

                control.Text = parsedHotkey;

                return;
            }
            catch (Exception) { }
        }

        #endregion

        #endregion

        #region Events

        #region Private

        /// <summary>
        /// Fires when a key is pressed down. Here, we'll want to update the Text  
        /// property to notify the user what key combination is currently pressed.
        /// </summary>
        private void OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Delete || e.Key == (System.Windows.Input.Key.LeftCtrl | System.Windows.Input.Key.RightCtrl | System.Windows.Input.Key.Delete))
                Reset((System.Windows.Controls.TextBox)sender);

            if (e.Key == (System.Windows.Input.Key.LeftShift | System.Windows.Input.Key.RightShift | System.Windows.Input.Key.Insert))
            {
                this._modifiers = Keys.Shift;
                e.Handled = true;
            }

            // Clear the current hotkey.
            if (e.Key == System.Windows.Input.Key.Back || e.Key == System.Windows.Input.Key.Delete)
            {
                Reset((System.Windows.Controls.TextBox)sender);

                return;
            }
            else
            {
                this._modifiers = ToWinforms(e.KeyboardDevice.Modifiers);
                this._hotkey = (Keys)System.Windows.Input.KeyInterop.VirtualKeyFromKey(e.Key);

                Refresh((System.Windows.Controls.TextBox)sender);
            }
        }

        /// <summary>
        /// Source: https://stackoverflow.com/questions/1153009/how-can-i-convert-system-windows-input-key-to-system-windows-forms-keys
        /// </summary>
        /// <param name="modifier"></param>
        /// <returns></returns>
        private System.Windows.Forms.Keys ToWinforms(System.Windows.Input.ModifierKeys modifier)
        {
            var retVal = System.Windows.Forms.Keys.None;
            if (modifier.HasFlag(System.Windows.Input.ModifierKeys.Alt))
            {
                retVal |= System.Windows.Forms.Keys.Alt;
            }
            if (modifier.HasFlag(System.Windows.Input.ModifierKeys.Control))
            {
                retVal |= System.Windows.Forms.Keys.Control;
            }
            if (modifier.HasFlag(System.Windows.Input.ModifierKeys.None))
            {
                // Pointless I know
                retVal |= System.Windows.Forms.Keys.None;
            }
            if (modifier.HasFlag(System.Windows.Input.ModifierKeys.Shift))
            {
                retVal |= System.Windows.Forms.Keys.Shift;
            }
            if (modifier.HasFlag(System.Windows.Input.ModifierKeys.Windows))
            {
                // Not supported lel
            }
            return retVal;
        }

        /// <summary>
        /// Fires when all keys are released. If the current hotkey isn't valid, reset it.
        /// Otherwise, do nothing and keep the Text and hotkey as it was.
        /// </summary>
        private void OnKeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {

            if (this._hotkey == Keys.None && e.KeyboardDevice.Modifiers == System.Windows.Input.ModifierKeys.None)
            {
                Reset((System.Windows.Controls.TextBox)sender);

                return;
            }
        }

        #endregion

        #endregion
    }
}

JensPLarsen avatar Nov 13 '20 15:11 JensPLarsen

Good stuff!

For now there are currently no plans for adding WPF support but will see what I can do. In the meantime, this code will work quite well for WPF apps.

Willy-Kimura avatar Nov 19 '20 12:11 Willy-Kimura