2

Has anyone figured out an event that fires whenever a menu item is highlighted?

I would like to display a description of each menu command in the status bar as they are highlighted. I'd like this to happen whether they are highlighted using the mouse or the keyboard.

But after considerable effort, I don't see any event like this. I even tried overriding WndProc to detect raw menu messages but found none are sent. Apparently, WinForms doesn't use the standard Windows menus.

It seems like knowing when a menu item is clicks and when it is selected (highlighted without being clicked) should be the two most important menu events. I don't know why the latter wouldn't be supported.

Anyone been able to figure this out?

UPDATE

With the help of answers given here, I was able to come up with a complete solution. I have posted that solution as open source on NuGet and GitHub.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • What about the hover event? – Daniel A. White Jan 16 '22 at 15:15
  • how about MenuActivate , and you need to consider mouse/keyboard trigger, you can try out with reading https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.menustrip.menuactivate?view=windowsdesktop-6.0 – Turbot Jan 16 '22 at 15:28
  • See: [Showing a tooltip for a MenuItem](https://stackoverflow.com/questions/58245/showing-a-tooltip-for-a-menuitem). – Olivier Jacot-Descombes Jan 16 '22 at 15:32
  • @DanielA.White: I don't see how a hover event would help when items are selected from the keyboard. – Jonathan Wood Jan 16 '22 at 15:41
  • @OlivierJacot-Descombes: I want to show the description in the status bar. I don't want to show a tooltip. – Jonathan Wood Jan 16 '22 at 15:42
  • @Turbot: The `MenuActivate` event seems to fire once when the users starts engaging the menu. It does not fire as individual menu commands are highlighted. – Jonathan Wood Jan 16 '22 at 15:45
  • Tweak [this](https://stackoverflow.com/a/66145542/14171304) a little to display the description in a status bar label rather than a tooltip window. – dr.null Jan 16 '22 at 15:52
  • @dr.null: That code seems to rely on `MouseHover` and `MouseLeave`. But I want to also handle when a command is highlighted using the keyboard. In addition, nothing I do seems to result in `MouseHover` or `MouseLeave` events firing. – Jonathan Wood Jan 16 '22 at 17:10
  • 1
    See [How can I detect when a menu item is selected (not clicked)?](https://social.msdn.microsoft.com/Forums/en-US/76eedce2-375e-4799-9954-d008ab398ecc/how-can-i-detect-when-a-menu-item-is-selected-not-clicked?forum=vbgeneral) – LarsTech Jan 16 '22 at 17:33
  • @LarsTech: That's a good find. I have that working with the mouse. Unfortunately, I'm not any closer to getting it to work with the keyboard. Someone posted about on that thread about making it work with the keyboard. But that post seemed confused and I couldn't get anything they were saying to work. – Jonathan Wood Jan 16 '22 at 18:08

2 Answers2

1

With @dr.null's help, I got this working. Here's my version of the code.

private void InitializeMenuStatus(ToolStrip toolStrip)
{
    toolStrip.ShowItemToolTips = false;
    toolStrip.KeyUp += ToolStrip_KeyUp;
    foreach (ToolStripItem toolStripItem in toolStrip.Items)
    {
        toolStripItem.AutoToolTip = false;
        toolStripItem.MouseEnter += ToolStripItem_MouseEnter;
        toolStripItem.MouseLeave += ToolStripItem_MouseLeave;
        if (toolStripItem is ToolStripDropDownItem dropDownItem)
            InitializeMenuStatus(dropDownItem.DropDown);
    }
}

private ToolStripItem? SelectedMenuItem = null;

private void SetSelectedMenuItem(ToolStripItem? item)
{
    if (!ReferenceEquals(item, SelectedMenuItem))
    {
        SelectedMenuItem = item;
        lblStatus.Text = item?.ToolTipText ?? string.Empty;
    }
}

private void ToolStripItem_MouseEnter(object? sender, EventArgs e)
{
    if (sender is ToolStripMenuItem menuItem && menuItem.Selected)
        SetSelectedMenuItem(menuItem);
}

private void ToolStripItem_MouseLeave(object? sender, EventArgs e)
{
    SetSelectedMenuItem(null);
}

private void ToolStrip_KeyUp(object? sender, KeyEventArgs e)
{
    if (sender is ToolStripDropDownMenu dropDownMenu)
    {
        ToolStripMenuItem? menuItem = dropDownMenu.Items.OfType<ToolStripMenuItem>()
            .Where(m => m.Selected)
            .FirstOrDefault();
        SetSelectedMenuItem(menuItem);
    }
}

Why a Selected event was never added to menu items escapes me. I have suggested that it be added. If you agree, please go and show your support for that request.

If anyone's interested, I spent some time fine tuning this code and ended up making a free component, now published as a NuGet package. You can view the code on GitHub

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • Each time a menu is selected, mouse or keyboard, the Paint event is raised. You can test `[ToolStripMenuItem].Selected` then. – Jimi Jan 16 '22 at 18:48
  • @Jimi: That's interesting. And it does work! Let me see if I can optimize it so that spurious paint events don't cause it to repeatedly update the status text. – Jonathan Wood Jan 16 '22 at 18:50
  • You only *risk* to use the Text of the MenuItem previously selected, then the one currently selected. Unless you also test the previous text. It doesn't cause flickering anyway. -- A `ToolStripProfessionalRenderer` can be used to handle these events (override the methods, actually, as `OnRenderItemText`), in case you already have one. – Jimi Jan 16 '22 at 19:00
1

In addition to the mouse events, you can add the keyboard keys part by handling the KeyUp event of the owner menu to get the selected item and display a description in a status-bar label.

public YourForm()
{
    InitializeComponent();

    menuStrip1.ShowItemToolTips = false;
    menuStrip1.KeyUp += OnToolStripKeyUp;

    foreach (var item in GetAllToolStripItems(menuStrip1.Items))
    {
        item.AutoToolTip = false;
        item.MouseEnter += OnToolStripItemMouseEnter;
        item.MouseLeave += OnToolStripItemMouseLeave;

        if (item.GetCurrentParent() is ToolStrip dm)
        {
            dm.ShowItemToolTips = false;
            dm.KeyUp -= OnToolStripKeyUp;
            dm.KeyUp += OnToolStripKeyUp;
        }
    }
}

private void OnToolStripItemMouseEnter(object sender, EventArgs e)
{
    sbrLabel.Text = (sender as ToolStripItem).ToolTipText;
}

private void OnToolStripItemMouseLeave(object sender, EventArgs e)
{
    sbrLabel.Text = "Ready";
}

private void OnToolStripKeyUp(object sender, KeyEventArgs e)
{
    var s = sender as ToolStrip;
    var selItem = s.Items.OfType<ToolStripMenuItem>().FirstOrDefault(x => x.Selected);

    sbrLabel.Text = selItem?.ToolTipText;
}

private IEnumerable<ToolStripItem> GetAllToolStripItems(ToolStripItemCollection tsic)
{
    foreach (var tsi in tsic.Cast<ToolStripItem>())
    {
        yield return tsi;

        if (tsi is ToolStripDropDownItem tsddi && tsddi.HasDropDown)
            foreach (var ddi in GetAllToolStripItems(tsddi.DropDownItems))
                yield return ddi;
    }
}

SO70731362

dr.null
  • 4,032
  • 3
  • 9
  • 12
  • I can't seem to get the `KeyUp` handler to be called for anything. Did that work for you? – Jonathan Wood Jan 16 '22 at 19:25
  • Yes it does work here. Just check `-=` and `+=` part. Also, check what you set, maybe you didn't assign values to the `ToolTipText` properties. Use your descriptions source. – dr.null Jan 16 '22 at 19:28
  • Thanks. I'm getting closer. Sometimes I get a little confused between all the different menu strips, tool strips, toolstrip dropdowns, menu items, etc. Also, I don't quite understand why you're first removing your event handlers before adding them? Are you trying to avoid adding them more than once? – Jonathan Wood Jan 16 '22 at 20:04
  • Also, I don't think it is serving any purpose to set `ShowItemToolTips` and the `KeyUp` handler for `menuStrip1`. Doing it for the toolstrips seems to be all you need. – Jonathan Wood Jan 16 '22 at 20:11
  • @JonathanWood `-=` is necessary because two or more items can belong to the same parent. As for the confusion, the `ToolStrip` is their base class. I set `ShowItemToolTips = false;` here because I use the `ToolTipText` as the source of the description. Use yours and ignore this part. – dr.null Jan 16 '22 at 20:12
  • 1
    Thanks again. It seems to be working pretty well. I was able to simplify the initialization code a little bit. You can see what I came up with in my answer. Cheers! – Jonathan Wood Jan 16 '22 at 20:39