Search code examples
vb.nettoolstripmenu

How to disable or (un)check a ToolStripMenuItem when it is being displayed?


There are several examples online of how to disable a menu item's child (e.g. using the DropDownOpening event of the parent), but I'd like to create a class that inherits ToolStripMenuItem and can decide on its own whether it should be enabled or not.

Something like this:

Public Class SmartMenuItem
    Inherits ToolStripMenuItem

    Public Sub New(text As String)
        MyBase.New(text)
        AddHandler MyBase.VisibleChanged, AddressOf enableSelf
    End Sub

    Private Sub enableSelf(sender As Object, e As System.EventArgs)
        Me.Enabled = MagicFunctionBooleanResult()
    End Sub

End Class

But the VisibleChanged event doesn't work as I hoped it would nor can I find any other event.

I also tried the DropDownOpening event for the item itself, but that only gets fired with quite a delay so, if the users are fast enough, they could still click the item once it gets displayed.

It seems to be such an obvious feature that I'm afraid I'm missing something ...obvious.

Any ideas?

Edit: Changing the Checked property is the same deal of course...


Solution

  • I was anxious, but tired. Waking up I found the solution, which indeed appeared obvious once I translated the phrase "is displayed" in my question to "on paint":

    Public Class SmartMenuItem
        Inherits ToolStripMenuItem
    
        Public Sub New(text As String)
            MyBase.New(text)
        End Sub
    
        Protected Overrides Sub OnPaint(e As PaintEventArgs)
            Me.Enabled = MagicEnabledFunction()
            Me.Checked = MagicCheckedFunction()
            Me.Text = MagicTextFunction()
    
            MyBase.OnPaint(e)
        End Sub
    
    End Class
    

    By overriding the event handling function (instead of using AddHandler Me.Paint) you can be sure the custom code is executed before the base class deals with the paint event, which is the optimal opportuntiny for changing display relevant properties, e.g. Enabled, Checked, Text.

    Update: After utilizing the above technique in my project I ended up with a base class that removes a big drawback in the initial solution: the shortcut keys would still trigger the item's click event, even though it was disabled.

    First, the base class:

    Public Class MenuItem
        Inherits ToolStripMenuItem
    
        Public Delegate Sub ClickDelegate()
    
        Public Sub New(text As String, shortcut As Windows.Forms.Keys, clickCallback As ClickDelegate)
            MyBase.New(text)
    
            AddHandler Me.Click, Sub(sender As Object, e As System.EventArgs)
                                     If Me.enabledCallback Then
                                         'NOTE: shortcut keys trigger the event even, if the item is not enabled; so check here to prevent their execution
                                         clickCallback.Invoke()
                                     End If
                                 End Sub
    
    
            If shortcut <> Keys.None Then
                Me.ShortcutKeys = shortcut
                Me.ShowShortcutKeys = True
            End If
        End Sub
    
        Protected Overrides Sub OnPaint(e As PaintEventArgs)
            'Store the current Enabled state before painting
            Dim _enabled As Boolean = Me.Enabled
    
            'Set Enabled/Checked according to the callbacks
            Me.Enabled = enabledCallback()
            Me.Checked = checkedCallback()
    
            'Paint the item
            MyBase.OnPaint(e)
    
            'Restore Enabled
            Me.Enabled = _enabled
    
    
            'NOTES:
            '- If the native Enabled-property is not disabled, then the mechanism above allows the item to always respond to shortcut keys.
            '- In the lamda click handler (which is also called when a shortcut is used) the enabledCallback will be checked to verify
            '  that the clickCallback should really be executed.
            '- This way, if the criteria for enabling/disabling the item (coded in enabledCallback) change, the shortcut keys will work as expected.
            '- Otherwise the enabled state would only be refreshed on paint and, if enabledCallback() = false, then the shortcut keys could not
            '  be used (until the item is painted again).
            '- Query Me.Enabled (or MyBase.enabledCallback) within the enabledCallback override to allow for enabling/disabling regardless of
            '  the criteria coded in the callback.
            '- A similar mechanism for Checked is not implemented, assuming the property is only relevant for painting and is not queried anywhere else.
    
    
        End Sub
        Protected Overridable Function enabledCallback() As Boolean
            Return Me.Enabled
        End Function
    
        Protected Overridable Function checkedCallback() As Boolean
            Return Me.Checked
        End Function
    End Class
    

    Second, a derived class:

    Public Class SelectionMenuItem
        Inherits Wd.Menu.MenuItem
    
        Public Sub New(text As String, shortCut As Windows.Forms.Keys, callback As MenuItem.ClickDelegate, minCount As Integer, Optional maxCount As Integer = 1000)
            MyBase.New(text, shortCut, callback)
    
            _minCount = minCount
            _maxCount = maxCount
        End Sub
    
        Private _minCount As Integer
        Private _maxCount As Integer
    
        Protected Overrides Function enabledCallback() As Boolean
            Return (Magic.Selection.Count >= _minCount) AndAlso (Magic.Selection.Count <= _maxCount)
        End Function
    End Class
    

    I hope the comments included in the code above help explain how i got around that; haven't got the time right now to elaborate ;o)