Imports System.Drawing Imports System.Drawing.Drawing2D Imports System.Drawing.Text Imports System.ComponentModel Imports System.Windows.Forms Imports System.Windows.Forms.Design _ Public Class MenuButton Inherits System.Windows.Forms.Button #Region " コンポーネント デザイナで生成されたコード " Public Sub New() MyBase.New() ' この呼び出しは、コンポーネント デザイナで必要です。 InitializeComponent() ' ボタンを通常の状態で初期化 Me.btnState = ButtonState.Normal ' ボタンの領域を初期化 Me.Region = New Region(GetButtonRegionPath(0, 0)) End Sub 'Control は、コンポーネント一覧に後処理を実行するために、dispose をオーバーライドします。 Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'コントロール デザイナで必要です。 Private components As System.ComponentModel.IContainer ' メモ : 以下のプロシージャはコンポーネント デザイナで必要です。 ' コンポーネント デザイナを使って変更できます。 ' コード エディタを使って変更しないでください。 Private Sub InitializeComponent() components = New System.ComponentModel.Container End Sub #End Region #Region "ボタンの状態管理" Public Enum ButtonState Normal ' 通常の状態 Hover ' マウスがボタンの上にある状態 Pressed ' マウスで押し下げられている状態 End Enum Private btnState As ButtonState ' ボタンの状態を保持 ' ボタンの上のマウスが離れたときに呼び出される。 Protected Overrides Sub OnMouseLeave(ByVal e As EventArgs) MyBase.OnMouseLeave(e) Me.btnState = ButtonState.Normal Me.Invalidate() End Sub ' ボタンの上にマウスが乗ったときに呼び出される。 Protected Overrides Sub OnMouseEnter(ByVal e As EventArgs) MyBase.OnMouseEnter(e) Me.btnState = ButtonState.Hover Me.Invalidate() End Sub ' ボタンがマウスによって押し下げられたときに呼び出される。 Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs) MyBase.OnMouseDown(e) ' 左クリックされたら再描画 If e.Button = MouseButtons.Left Then Me.btnState = ButtonState.Pressed Me.Invalidate() End If End Sub ' ボタンの上をマウスが移動したときに呼び出される。 Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) MyBase.OnMouseMove(e) If Me.ClientRectangle.Contains(e.X, e.Y) Then If (Me.Capture = True) And (Me.btnState = ButtonState.Hover) Then ' キャプチャ状態(マウス押されたまま)でボタン状態がHoverなら ' 押し下げたままボタンの外から内に戻ってきたことを意味する Me.btnState = ButtonState.Pressed Me.Invalidate() End If Else If Me.btnState = ButtonState.Pressed Then ' ボタン状態がPressedrなら ' 押し下げたままボタンの内から外に出ていったことを意味する Me.btnState = ButtonState.Hover Me.Invalidate() End If End If End Sub ' マウスによるクリックが終了したときに呼び出される。 Protected Overrides Sub OnClick(ByVal e As EventArgs) If Me.ClientRectangle.Contains(Me.PointToClient(Control.MousePosition)) Then ' 通常マウスがボタン上にあるはず Me.btnState = ButtonState.Hover Else ' マウスがボタン上にない場合には通常状態にする Me.btnState = ButtonState.Normal End If Me.Invalidate() MyBase.OnClick(e) End Sub ' ボタンの有効/無効の状態が切り替わったときに呼び出される。 Protected Overrides Sub OnEnabledChanged(ByVal e As EventArgs) MyBase.OnEnabledChanged(e) ' ボタンの状態を戻して描画 Me.btnState = ButtonState.Normal Me.Invalidate() End Sub #End Region #Region "ボタンの領域" ' ボタン領域のパスを取得する Private Function GetButtonRegionPath(ByVal inflate As Integer, ByVal lineWidth As Integer) As GraphicsPath Dim rc As Rectangle = Me.ClientRectangle If inflate <> 0 Then rc.Inflate(inflate, inflate) End If If lineWidth <> 0 Then rc.Width = rc.Width - lineWidth rc.Height = rc.Height - lineWidth End If Dim sz As New Size(10, 10) Dim path As New GraphicsPath path.AddLine(rc.Left + CInt(sz.Width / 2), rc.Top, rc.Right - CInt(sz.Width / 2), rc.Top) path.AddArc(rc.Right - sz.Width, rc.Top, sz.Width, sz.Height, 270, 90) path.AddLine(rc.Right, rc.Top + CInt(sz.Height / 2), rc.Right, rc.Bottom - CInt(sz.Height / 2)) path.AddArc(rc.Right - sz.Width, rc.Bottom - sz.Height, sz.Width, sz.Height, 0, 90) path.AddLine(rc.Right - CInt(sz.Width / 2), rc.Bottom, rc.Left + CInt(sz.Width / 2), rc.Bottom) path.AddArc(rc.Left, rc.Bottom - sz.Height, sz.Width, sz.Height, 90, 90) path.AddLine(rc.Left, rc.Bottom - CInt(sz.Height / 2), rc.Left, rc.Top + CInt(sz.Height / 2)) path.AddArc(rc.Left, rc.Top, sz.Width, sz.Height, 180, 90) Return path End Function #End Region #Region "描画処理" Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs) ' パネルを描画する DrawButton(pe.Graphics) ' 基本クラスOnPaintは実行しない 'MyBase.OnPaint(pe) End Sub ' ボタンを描画する。 Private Sub DrawButton(ByVal g As Graphics) Select Case Me.btnState Case ButtonState.Normal ' 通常の状態 If Me.Enabled = False Then ' ボタンが無効な状態 DrawDisabledButton(g) ElseIf (Me.Focused) Then ' ボタンにフォーカスがある状態 DrawFoucusedButton(g) Else ' 通常の状態 DrawNormalButton(g) End If Case ButtonState.Hover ' ホーバー状態 DrawHoverButton(g) Case ButtonState.Pressed ' 押し下げ状態 DrawPressedButton(g) End Select ' テキストを描画する Me.DrawButtonText(g) End Sub ' 通常状態のボタンを描画します。 Private Sub DrawNormalButton(ByVal g As Graphics) DrawButton(g, Me.BackColor, Color.White) End Sub ' 無効状態のボタンを描画します。 Private Sub DrawDisabledButton(ByVal g As Graphics) DrawButton(g, SystemColors.ControlDark, Color.White) End Sub ' フォーカス状態のボタンを描画します。 Private Sub DrawFoucusedButton(ByVal g As Graphics) DrawButton(g, Color.Navy, Color.White) End Sub ' マウスが上に乗っている状態のボタンを描画します。 Private Sub DrawHoverButton(ByVal g As Graphics) DrawButton(g, Color.Navy, Color.Yellow) End Sub ' 押し下げ状態のボタンを描画します。 Private Sub DrawPressedButton(ByVal g As Graphics) DrawButton(g, SystemColors.Highlight, Color.Black) End Sub ' ボタンを描画する Private Sub DrawButton(ByVal g As Graphics, ByVal colorBack As Color, ByVal colorGradient As Color) Dim rectPaint As Rectangle = Me.ClientRectangle If (rectPaint.Width <= 0) Or (rectPaint.Height <= 0) Then Return ' 描画しない End If Dim regionPaint As Region = Me.Region If regionPaint Is Nothing Then Return ' 描画しない End If ' 背景色をボタン全体に描画 Dim brushBack As SolidBrush = New SolidBrush(colorBack) g.FillRegion(brushBack, regionPaint) brushBack.Dispose() ' ボタン表面のグラデュエーションを描画 Dim brushSurface As New LinearGradientBrush( _ rectPaint, _ Color.FromArgb(128, colorGradient), _ Color.FromArgb(0, colorGradient), _ LinearGradientMode.Vertical) Dim reginSurface As Region = New Region(GetButtonRegionPath(-1, 0)) g.FillRegion(brushSurface, reginSurface) brushSurface.Dispose() End Sub #End Region #Region "ボタンテキストの描画" ' ボタンのテキストを描画する Private Sub DrawButtonText(ByVal g As Graphics) Dim rectPaint As RectangleF = RectangleF.op_Implicit(Me.ClientRectangle) If (rectPaint.Width <= 0) Or (rectPaint.Height <= 0) Then Return ' 描画しない End If ' ボタン用の文字列フォーマットを取得する Dim strFormat As StringFormat = GetButtonStringFormat() If Me.btnState = ButtonState.Pressed Then rectPaint.X += 1 rectPaint.Y += 1 Dim brushText As SolidBrush = New SolidBrush(Me.ForeColor) g.DrawString(Me.Text, Me.Font, CType(brushText, Brush), rectPaint, strFormat) brushText.Dispose() ElseIf Me.Enabled = True Then Dim brushText As SolidBrush = New SolidBrush(Me.ForeColor) g.DrawString(Me.Text, Me.Font, brushText, rectPaint, strFormat) brushText.Dispose() Else ' コントロールの無効文字を描画するには ' Win32 APIのDrawStateString関数を使うしかない。 ' ControlPaint.DrawStringDisabledメソッドはあるが ' 筆者が試した限り、うまく動作しないようだ。 ' なお、ControlPaint.DrawImageDisabledは使えるようだ。 ' ここでは、1ピクセル右下にずらすことで、疑似的に3D表示っぽく見せる Dim brushLightText As SolidBrush = New SolidBrush(SystemColors.ControlLightLight) g.DrawString(Me.Text, Me.Font, brushLightText, rectPaint, strFormat) brushLightText.Dispose() rectPaint.X = rectPaint.X - 1 rectPaint.Y = rectPaint.Y - 1 Dim darkColor As Color If Me.Enabled Then darkColor = Me.ForeColor Else darkColor = SystemColors.ControlDark End If Dim brushDarkText As New SolidBrush(darkColor) g.DrawString(Me.Text, Me.Font, brushDarkText, rectPaint, strFormat) brushDarkText.Dispose() End If strFormat.Dispose() End Sub ' ボタン用の文字列フォーマットを取得する。 Private Function GetButtonStringFormat() As StringFormat Dim strFormat As StringFormat = New StringFormat() strFormat.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show Select Case Me.TextAlign Case ContentAlignment.MiddleCenter strFormat.LineAlignment = StringAlignment.Center strFormat.Alignment = StringAlignment.Center Case ContentAlignment.MiddleLeft strFormat.LineAlignment = StringAlignment.Center strFormat.Alignment = StringAlignment.Near Case ContentAlignment.MiddleRight strFormat.LineAlignment = StringAlignment.Center strFormat.Alignment = StringAlignment.Far Case ContentAlignment.TopCenter strFormat.LineAlignment = StringAlignment.Near strFormat.Alignment = StringAlignment.Center Case ContentAlignment.TopLeft strFormat.LineAlignment = StringAlignment.Near strFormat.Alignment = StringAlignment.Near Case ContentAlignment.TopRight strFormat.LineAlignment = StringAlignment.Near strFormat.Alignment = StringAlignment.Far Case ContentAlignment.BottomCenter strFormat.LineAlignment = StringAlignment.Far strFormat.Alignment = StringAlignment.Center Case ContentAlignment.BottomLeft strFormat.LineAlignment = StringAlignment.Far strFormat.Alignment = StringAlignment.Near Case ContentAlignment.BottomRight strFormat.LineAlignment = StringAlignment.Far strFormat.Alignment = StringAlignment.Far End Select Return strFormat End Function #End Region #Region "リサイズ時の描画処理の改善" ' OnResizeイベントのEventArgsパラメータを保持 Private resizeEeventArgs As EventArgs = Nothing 'フォームのサイズが変更した時 Protected Overrides Sub OnResize(ByVal e As System.EventArgs) ' リサイズ時にボタン領域も変更 Me.Region = New Region(GetButtonRegionPath(0, 0)) MyBase.OnResize(e) ' 描画処理はアイドル時間に行う If resizeEeventArgs Is Nothing Then resizeEeventArgs = e ' デリゲートを追加 AddHandler Application.Idle, AddressOf OnIdleInvalidate End If End Sub ' アプリケーションがアイドル状態のときに呼び出されるデリゲート Private Sub OnIdleInvalidate(ByVal s As Object, ByVal e As EventArgs) If Not resizeEeventArgs Is Nothing Then ' 描画処理を行う Invalidate() ' デリゲートを削除 resizeEeventArgs = Nothing RemoveHandler Application.Idle, AddressOf OnIdleInvalidate End If End Sub #End Region #Region "デザイナ表示用の入れ子クラス" ' MenuButtonDesigner の概要の説明です。(入れ子クラス) Friend Class MenuButtonDesigner Inherits ControlDesigner Protected Overrides Sub PostFilterProperties(ByVal Properties As IDictionary) ' 次のプロパティはデザイナでは非表示にする Properties.Remove("FlatStyle") Properties.Remove("BackgroundImage") Properties.Remove("Image") Properties.Remove("ImageAlign") Properties.Remove("ImageIndex") Properties.Remove("ImageList") End Sub End Class #End Region End Class