Imports System.Windows.Forms.VisualStyles Public Class SplitButton Inherits Button Private _state As PushButtonState Private Const PushButtonWidth As Integer = 14 Private Shared BorderSize As Integer = SystemInformation.Border3DSize.Width * 2 Private skipNextOpen As Boolean = False Private dropDownRectangle As New Rectangle Private _showSplit As Boolean = True Public Sub New() Me.AutoSize = True End Sub Public Property ShowSplit() As Boolean Get Return Me._showSplit End Get Set(ByVal value As Boolean) If value <> Me._showSplit Then Me._showSplit = value Me.Invalidate() If Me.Parent IsNot Nothing Then Me.Parent.PerformLayout() End If End If End Set End Property Private Property State() As PushButtonState Get Return Me._state End Get Set(ByVal value As PushButtonState) If value <> Me._state Then Me._state = value Me.Invalidate() End If End Set End Property Public Overrides Function GetPreferredSize(ByVal proposedSize As System.Drawing.Size) As System.Drawing.Size GetPreferredSize = MyBase.GetPreferredSize(proposedSize) If Me._showSplit AndAlso Not String.IsNullOrEmpty(Me.Text) AndAlso TextRenderer.MeasureText(Me.Text, Me.Font).Width + PushButtonWidth > GetPreferredSize.Width Then GetPreferredSize += New Size(PushButtonWidth + BorderSize * 2, 0) End If End Function Protected Overrides Function IsInputKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean If (keyData.Equals(Keys.Down) AndAlso Me._showSplit) Then Return True Return MyBase.IsInputKey(keyData) End Function Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs) If Not Me._showSplit Then MyBase.OnGotFocus(e) ElseIf Not State = PushButtonState.Pressed AndAlso Not State = PushButtonState.Disabled Then State = PushButtonState.Default End If End Sub Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs) If Not Me._showSplit Then MyBase.OnLostFocus(e) ElseIf State <> PushButtonState.Pressed AndAlso State <> PushButtonState.Disabled Then State = PushButtonState.Normal End If End Sub Protected Overrides Sub OnKeyDown(ByVal kevent As System.Windows.Forms.KeyEventArgs) If Me._showSplit Then If kevent.KeyCode = Keys.Down Then ShowContextMenuStrip() ElseIf kevent.KeyCode = Keys.Space AndAlso kevent.Modifiers = Keys.None Then State = PushButtonState.Pressed End If End If MyBase.OnKeyDown(kevent) End Sub Protected Overrides Sub OnKeyUp(ByVal kevent As System.Windows.Forms.KeyEventArgs) If kevent.KeyCode = Keys.Space Then If Control.MouseButtons = MouseButtons.None Then State = PushButtonState.Normal End If End If MyBase.OnKeyUp(kevent) End Sub Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs) If Not Me._showSplit Then MyBase.OnMouseDown(e) ElseIf dropDownRectangle.Contains(e.Location) Then ShowContextMenuStrip() Else State = PushButtonState.Pressed End If End Sub Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs) If Not Me._showSplit Then MyBase.OnMouseUp(e) ElseIf ContextMenuStrip Is Nothing OrElse Not ContextMenuStrip.Visible Then SetButtonDrawState() If Bounds.Contains(Parent.PointToClient(Cursor.Position)) AndAlso Not dropDownRectangle.Contains(e.Location) Then OnClick(New EventArgs()) End If End If End Sub Protected Overrides Sub OnMouseEnter(ByVal e As System.EventArgs) If Not ShowSplit Then MyBase.OnMouseEnter(e) ElseIf State <> PushButtonState.Pressed AndAlso State <> PushButtonState.Disabled Then State = PushButtonState.Hot End If End Sub Protected Overrides Sub OnMouseLeave(ByVal e As System.EventArgs) If Not ShowSplit Then MyBase.OnMouseLeave(e) ElseIf State <> PushButtonState.Pressed AndAlso State <> PushButtonState.Disabled Then If Focused Then State = PushButtonState.Default Else State = PushButtonState.Normal End If End If End Sub Protected Overrides Sub OnPaint(ByVal pevent As System.Windows.Forms.PaintEventArgs) MyBase.OnPaint(pevent) If Me._showSplit Then Dim g As Graphics = pevent.Graphics Dim bounds As Rectangle = Me.ClientRectangle 'draw the button background as according to the current state. If State <> PushButtonState.Pressed AndAlso IsDefault AndAlso Not Application.RenderWithVisualStyles Then Dim backgroundBounds As Rectangle = bounds backgroundBounds.Inflate(-1, -1) ButtonRenderer.DrawButton(g, backgroundBounds, State) 'button renderer doesnt draw the black frame when themes are off =( g.DrawRectangle(SystemPens.WindowFrame, 0, 0, bounds.Width - 1, bounds.Height - 1) Else ButtonRenderer.DrawButton(g, bounds, State) End If 'calculate the current dropdown rectangle. dropDownRectangle = New Rectangle(bounds.Right - PushButtonWidth - 1, BorderSize, PushButtonWidth, bounds.Height - BorderSize * 2) Dim internalBorder As Integer = BorderSize Dim focusRect As New Rectangle(internalBorder, internalBorder, bounds.Width - dropDownRectangle.Width - internalBorder, bounds.Height - (internalBorder * 2)) Dim drawSplitLine As Boolean = State = PushButtonState.Hot OrElse State = PushButtonState.Pressed OrElse Not Application.RenderWithVisualStyles If RightToLeft = Windows.Forms.RightToLeft.Yes Then dropDownRectangle.X = bounds.Left + 1 focusRect.X = dropDownRectangle.Right If drawSplitLine Then 'draw two lines at the edge of the dropdown button g.DrawLine(SystemPens.ButtonShadow, bounds.Left + PushButtonWidth, BorderSize, bounds.Left + PushButtonWidth, bounds.Bottom - BorderSize) g.DrawLine(SystemPens.ButtonFace, bounds.Left + PushButtonWidth + 1, BorderSize, bounds.Left + PushButtonWidth + 1, bounds.Bottom - BorderSize) End If ElseIf drawSplitLine Then 'draw two lines at the edge of the dropdown button g.DrawLine(SystemPens.ButtonShadow, bounds.Right - PushButtonWidth, BorderSize, bounds.Right - PushButtonWidth, bounds.Bottom - BorderSize) g.DrawLine(SystemPens.ButtonFace, bounds.Right - PushButtonWidth - 1, BorderSize, bounds.Right - PushButtonWidth - 1, bounds.Bottom - BorderSize) End If 'Draw an arrow in the correct location Dim middle As New Point(dropDownRectangle.Left + dropDownRectangle.Width / 2, dropDownRectangle.Top + dropDownRectangle.Height / 2) 'if the width is odd - favor pushing it over one pixel right. middle.X += dropDownRectangle.Width Mod 2 Dim arrow As Point() = New Point() {New Point(middle.X - 2, middle.Y - 1), New Point(middle.X + 3, middle.Y - 1), New Point(middle.X, middle.Y + 2)} g.FillPolygon(SystemBrushes.ControlText, arrow) 'Figure out how to draw the text Dim formatFlags As TextFormatFlags = TextFormatFlags.HorizontalCenter Or TextFormatFlags.VerticalCenter 'If we dont' use mnemonic, set formatFlag to NoPrefix as this will show ampersand. If Not UseMnemonic Then formatFlags = formatFlags Or TextFormatFlags.NoPrefix ElseIf Not ShowKeyboardCues Then formatFlags = formatFlags Or TextFormatFlags.HidePrefix End If If Not String.IsNullOrEmpty(Me.Text) Then TextRenderer.DrawText(g, Text, Font, focusRect, SystemColors.ControlText, formatFlags) End If 'draw the focus rectangle. If State <> PushButtonState.Pressed AndAlso Focused Then ControlPaint.DrawFocusRectangle(g, focusRect) End If End If End Sub Private Sub ShowContextMenuStrip() If skipNextOpen Then 'we were called because we're closing the context menu strip 'when clicking the dropdown button. skipNextOpen = False Else State = PushButtonState.Pressed If ContextMenuStrip IsNot Nothing Then AddHandler ContextMenuStrip.Closing, AddressOf ContextMenuStrip_Closing ContextMenuStrip.Show(Me, New Point(0, Height), ToolStripDropDownDirection.BelowRight) End If End If End Sub Private Sub ContextMenuStrip_Closing(ByVal sender As Object, ByVal e As ToolStripDropDownClosingEventArgs) Dim cms As ContextMenuStrip = CType(sender, ContextMenuStrip) If cms IsNot Nothing Then RemoveHandler cms.Closing, AddressOf ContextMenuStrip_Closing End If SetButtonDrawState() If e.CloseReason = ToolStripDropDownCloseReason.AppClicked Then skipNextOpen = dropDownRectangle.Contains(Me.PointToClient(Cursor.Position)) End If End Sub Private Sub SetButtonDrawState() If Bounds.Contains(Parent.PointToClient(Cursor.Position)) Then State = PushButtonState.Hot ElseIf Focused Then State = PushButtonState.Default Else State = PushButtonState.Normal End If End Sub End Class