TreeControl/SRxCheckCommandToolStripMenuItem.ps1

#Requires -Version 5.1
using namespace System
using namespace System.Collections.Generic
using namespace System.Drawing
using namespace System.Windows.Forms
using namespace System.Windows.Forms.VisualStyles

class SRxCheckCommandToolStripMenuItem : ToolStripMenuItem {
    hidden [int] $_checkBoxSize = 14
    hidden [int] $_leftPadding = 6
    hidden [int] $_gapAfterCheckBox = 7
    hidden [int] $_rightPadding = 10
    hidden [int] $_minimumItemHeight = 24
    hidden [Rectangle] $_checkBoxBounds = [Rectangle]::Empty
    hidden [bool] $_mouseOverCheckBox = $false
    hidden [bool] $_showCheckBox = $true
    hidden [bool] $_keepDropDownOpenOnCheckClick = $true
    hidden [List[EventHandler]] $_checkBoxClickedHandlers
    hidden [List[EventHandler]] $_commandClickedHandlers

    SRxCheckCommandToolStripMenuItem() : base() { $this.initializeItem() }
    SRxCheckCommandToolStripMenuItem([string] $text) : base($text) { $this.initializeItem() }
    SRxCheckCommandToolStripMenuItem([string] $text, [bool] $isChecked) : base($text) {
        $this.initializeItem()
        $this.Checked = $isChecked
    }

    hidden [void] initializeItem() {
        $this.CheckOnClick = $false
        $this.Image = $null
        $this.ImageScaling = [ToolStripItemImageScaling]::None
        $this.AutoSize = $true
        $this.Margin = [Padding]::Empty
        $this.Padding = [Padding]::Empty
        $this._checkBoxClickedHandlers = [List[EventHandler]]::new()
        $this._commandClickedHandlers = [List[EventHandler]]::new()
    }

    hidden [void] setShowCheckBox([bool] $value) {
        if ($this._showCheckBox -ne $value) {
            $this._showCheckBox = $value
            $this._mouseOverCheckBox = $false
            $this.Invalidate()
        }
    }

    hidden [bool] getShowCheckBox() { return $this._showCheckBox }

    hidden [void] setKeepDropDownOpenOnCheckClick([bool] $value) {
        $this._keepDropDownOpenOnCheckClick = $value
    }

    hidden [bool] getKeepDropDownOpenOnCheckClick() {
        return $this._keepDropDownOpenOnCheckClick
    }

    [void] addCheckBoxClicked([EventHandler] $handler) {
        if ($null -ne $handler) { $this._checkBoxClickedHandlers.Add($handler) }
    }

    [void] removeCheckBoxClicked([EventHandler] $handler) {
        if ($null -ne $handler) { [void] $this._checkBoxClickedHandlers.Remove($handler) }
    }

    [void] addCommandClicked([EventHandler] $handler) {
        if ($null -ne $handler) { $this._commandClickedHandlers.Add($handler) }
    }

    [void] removeCommandClicked([EventHandler] $handler) {
        if ($null -ne $handler) { [void] $this._commandClickedHandlers.Remove($handler) }
    }

    hidden [void] raiseCheckBoxClicked() {
        foreach ($handler in $this._checkBoxClickedHandlers.ToArray()) {
            $handler.Invoke($this, [EventArgs]::Empty)
        }
    }

    hidden [void] raiseCommandClicked() {
        foreach ($handler in $this._commandClickedHandlers.ToArray()) {
            $handler.Invoke($this, [EventArgs]::Empty)
        }
    }

    hidden [void] updateCheckBoxBounds() {
        $top = [Math]::Max(0, [int](($this.Height - $this._checkBoxSize) / 2))
        $this._checkBoxBounds = [Rectangle]::new($this._leftPadding, $top, $this._checkBoxSize, $this._checkBoxSize)
    }

    [Rectangle] getCheckBoxBounds() {
        $this.updateCheckBoxBounds()
        return $this._checkBoxBounds
    }

    [bool] isPointInCheckBox([Point] $location) {
        if (-not $this.getShowCheckBox()) { return $false }
        $this.updateCheckBoxBounds()
        $hitBounds = [Rectangle]::new(0, 0, $this._checkBoxBounds.Right + $this._gapAfterCheckBox, $this.Height)
        return $hitBounds.Contains($location)
    }

    [Size] GetPreferredSize([Size] $constrainingSize) {
        $baseSize = ([ToolStripMenuItem] $this).GetPreferredSize($constrainingSize)
        $textValue = if ($null -eq $this.Text) { [string]::Empty } else { $this.Text }
        $textSize = [TextRenderer]::MeasureText($textValue, $this.Font, [Size]::Empty, ([TextFormatFlags]::SingleLine -bor [TextFormatFlags]::NoPadding))
        $checkWidth = if ($this.getShowCheckBox()) { $this._checkBoxSize + $this._gapAfterCheckBox } else { 0 }
        $width = $this._leftPadding + $checkWidth + $textSize.Width + $this._rightPadding
        $height = [Math]::Max($this._minimumItemHeight, [Math]::Max($this._checkBoxSize + 6, $textSize.Height + 6))
        return [Size]::new([Math]::Max($baseSize.Width, $width), [Math]::Max($baseSize.Height, $height))
    }

    hidden [void] OnMouseMove([MouseEventArgs] $e) {
        $overCheckBox = $this.isPointInCheckBox($e.Location)
        if ($this._mouseOverCheckBox -ne $overCheckBox) {
            $this._mouseOverCheckBox = $overCheckBox
            $this.Invalidate()
        }
        ([ToolStripMenuItem] $this).OnMouseMove($e)
    }

    hidden [void] OnMouseLeave([EventArgs] $e) {
        $this._mouseOverCheckBox = $false
        $this.Invalidate()
        ([ToolStripMenuItem] $this).OnMouseLeave($e)
    }

    hidden [void] OnMouseUp([MouseEventArgs] $e) {
        if (($e.Button -eq [MouseButtons]::Left) -and $this.isPointInCheckBox($e.Location)) {
            $this.toggleCheckBox()
            if ($this.getKeepDropDownOpenOnCheckClick()) { return }
        }
        ([ToolStripMenuItem] $this).OnMouseUp($e)
    }

    hidden [void] OnClick([EventArgs] $e) {
        ([ToolStripMenuItem] $this).OnClick($e)
        $this.raiseCommandClicked()
    }

    hidden [void] OnPaint([PaintEventArgs] $e) {
        $this.drawBackground($e.Graphics)
        if ($this.getShowCheckBox()) { $this.drawCheckBox($e.Graphics) }
        $this.drawItemText($e.Graphics)
    }

    hidden [void] toggleCheckBox() {
        if (-not $this.Enabled -or -not $this.getShowCheckBox()) { return }
        $this.Checked = -not $this.Checked
        $this.raiseCheckBoxClicked()
        $this.Invalidate()
    }

    hidden [void] drawBackground([Graphics] $graphics) {
        $bounds = [Rectangle]::new(0, 0, $this.Width, $this.Height)
        $color = if ($this.Selected) { [SystemColors]::Highlight } elseif ($null -ne $this.Owner) { $this.Owner.BackColor } else { [SystemColors]::Menu }
        $brush = [SolidBrush]::new($color)
        try { $graphics.FillRectangle($brush, $bounds) } finally { $brush.Dispose() }
    }

    hidden [void] drawCheckBox([Graphics] $graphics) {
        $this.updateCheckBoxBounds()
        $state = if (-not $this.Enabled) {
            if ($this.Checked) { [CheckBoxState]::CheckedDisabled } else { [CheckBoxState]::UncheckedDisabled }
        } elseif ($this._mouseOverCheckBox) {
            if ($this.Checked) { [CheckBoxState]::CheckedHot } else { [CheckBoxState]::UncheckedHot }
        } else {
            if ($this.Checked) { [CheckBoxState]::CheckedNormal } else { [CheckBoxState]::UncheckedNormal }
        }
        if ([CheckBoxRenderer]::IsSupported) {
            [CheckBoxRenderer]::DrawCheckBox($graphics, $this._checkBoxBounds.Location, $state)
        } else {
            $fallback = [ButtonState]::Normal
            if (-not $this.Enabled) { $fallback = $fallback -bor [ButtonState]::Inactive }
            if ($this.Checked) { $fallback = $fallback -bor [ButtonState]::Checked }
            [ControlPaint]::DrawCheckBox($graphics, $this._checkBoxBounds, $fallback)
        }
    }

    hidden [void] drawItemText([Graphics] $graphics) {
        $textLeft = $this._leftPadding
        if ($this.getShowCheckBox()) { $textLeft += $this._checkBoxSize + $this._gapAfterCheckBox }
        $bounds = [Rectangle]::new($textLeft, 0, [Math]::Max(0, $this.Width - $textLeft - $this._rightPadding), $this.Height)
        $color = if (-not $this.Enabled) { [SystemColors]::GrayText } elseif ($this.Selected) { [SystemColors]::HighlightText } else { $this.ForeColor }
        $textValue = if ($null -eq $this.Text) { [string]::Empty } else { $this.Text }
        $flags = [TextFormatFlags]::Left -bor [TextFormatFlags]::VerticalCenter -bor [TextFormatFlags]::SingleLine -bor [TextFormatFlags]::EndEllipsis -bor [TextFormatFlags]::NoPadding
        [TextRenderer]::DrawText($graphics, $textValue, $this.Font, $bounds, $color, $flags)
    }
}

<#
USAGE
$item = [SRxCheckCommandToolStripMenuItem]::new('Enable Debug', $false)
$item.setShowCheckBox($true)
$item.setKeepDropDownOpenOnCheckClick($true)
$item.addCheckBoxClicked({ param($sender, $eventArgs); Write-Host ('Checked = {0}' -f $sender.Checked) })
$item.addCommandClicked({ param($sender, $eventArgs); Write-Host 'Command text clicked' })
[void] $dropDownButton.DropDownItems.Add($item)

$refreshItem = [SRxCheckCommandToolStripMenuItem]::new('Refresh')
$refreshItem.setShowCheckBox($false)
#>