Search code examples
c#winformseventsclickeventhandler

WinForms - PerfromClick() doesn't work when the button is not visible


I created a form menu that is created dynamically and shows/hides the child items after clicking on the parent items. I have a code that checks the keyboard keys on the BaseMenuForm. When a shortcut is detected, then I need to perform the click event for the relevant button. My problem is that the PerformClick() event is not working when the children items are not visible.

I found a workaround by assigning the event to the ShortcutAction property. I don't think that it is necessary to assign the same event twice but currently I don't know how to overcome this issue. I don't want to show all the menu items for the shortcuts.

You can see the "TODO" remarks and the second constructor that performs a workaround if the shortcut is configured. When the button is clicked by the user, the Click event work as expected, so it doesn't work programatically.

This is part of my code (only the relevant parts):

The following property is set on the designer. The BaseMenuForm_KeyDown event is called as expected.

BaseMenuForm.KeyPreview = true;

My Item class:

    /// <summary>
    /// The menu item settings.
    /// </summary>
    public class MyItem : Button
    {
        #region Properties

        /// <summary>
        /// The keyboard keys shortcut.
        /// </summary>
        public Shortcut Shortcut { get; }

        /// <summary>
        /// The action that will be called by the shortcut.
        /// TODO: why not working when I call PerformClick()
        /// </summary>
        public Action ShortcutAction { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the item instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="text">The text.</param>
        /// <param name="onClickEvent">The event that will be triggered on cick.</param>
        public MyItem(string name, string text, EventHandler onClickEvent) : this(name, text)
        {
            if (onClickEvent == null)
            {
                throw new ArgumentNullException(nameof(onClickEvent));
            }
            else
            {
                Click += onClickEvent;
            }
        }

        /// <summary>
        /// Initializes the item instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="text">The text.</param>
        /// <param name="onClickEvent">The event that will be triggered on cick.</param>
        /// <param name="shortcut">The keyboard shortcut.</param>
        public MyItem(string name, string text, EventHandler onClickEvent, Shortcut shortcut) : this(name, text, onClickEvent)
        {
            ShortcutAction = () => onClickEvent(this, EventArgs.Empty); //TODO: Why do I need this?
            Shortcut = shortcut;
        }

        #endregion

        #region Methods

       
    }

BaseMenuForm: This class contains the form, every form that will inherit this form will have a menu.

        #region Shortcut

        private void BaseMenuForm_KeyDown(object sender, KeyEventArgs e)
        {
            CheckShortcut(e.KeyData);
        }

        /// <summary>
        /// Checks if the pressed keys is a shortcut combination.
        /// </summary>
        /// <param name="keyData">The keys.</param>
        private void CheckShortcut(Keys keyData)
        {
            var shortut = (Shortcut)keyData;
            Logger.LogDebug($"Got shortcut {shortut}");
            if(Shortcuts.TryGetValue(shortut, out var button) && button.ShortcutAction != null)
            {
                button.ShortcutAction();
                //button.PerformClick(); //TODO: why not working !
            }
            else
            {
                Logger.LogDebug("No click");
            }
        }

        #endregion

Solution

  • I'll answer the following questions:

    1. Why PerfromClick doesn't work for an invisible button?
    2. What's a workaround for this issue?

    Answer to 1 - If you look at the source code of the PerfromClick method, you see the first statement is a check: if (CanSelect) and if follow the code further, you see the CanSelect method returns false when the control is not enabled or is not visible. So that's the reason.

    Answer to 2 - As a workaround, you can use the button's accessible object and invoke the default action which basically performs a click for a button:

    button1.AccessibilityObject.DoDefaultAction();
    

    Or as another workaround, use reflection to invoke the OnClick method:

    button1.GetType().GetMethod("OnClick", 
        System.Reflection.BindingFlags.NonPublic |
        System.Reflection.BindingFlags.Instance)
        .Invoke(button, new object[] { EventArgs.Empty });