-1

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...

mike
  • 1,627
  • 1
  • 14
  • 37

2 Answers2

0

I did manage to see that delay you referenced when you used the DropDownOpening event of the custom ToolStripMenuItem when you mouse over the item. When you mouse over the item, it is then trying to open any submenus, even if it doesn't have any. That's the delay you are seeing.

Try using the OwnerChanged event instead to know when the parent item is doing the DropDownOpening event instead:

Public Class SmartMenuItem
  Inherits ToolStripMenuItem

  Public Sub New(text As String)
    MyBase.New(text)
  End Sub

  Private Sub SmartMenuItem_OwnerChanged(sender As Object, e As EventArgs) _
                                         Handles Me.OwnerChanged
    If Me.OwnerItem IsNot Nothing Then
      Dim dropMenu As ToolStripMenuItem = TryCast(Me.OwnerItem, ToolStripMenuItem)
      If dropMenu IsNot Nothing Then
        AddHandler dropMenu.DropDownOpening, Sub() Me.Enabled = MagicBooleanResult()
      End If
    End If
  End Sub
End Class
LarsTech
  • 80,625
  • 14
  • 153
  • 225
0

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)

mike
  • 1,627
  • 1
  • 14
  • 37