【テクニカル・上級編】カスタムペイント(OnPaint)による完全独自ボタンの作成:グラデーションと角丸を持つモダンUIの自作手法 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

魂のピクセル:VB.NETにおけるCustom PaintとGDI+による極限のUI設計

WinFormsは死んだ、という声が聞こえて久しい。しかし、現場の最前線に立つ我々アーキテクトにとって、それは浅薄な見解に過ぎない。Windows OSがその根底でGDI/GDI+を動かし続けている限り、WinFormsは最も低レイヤーに近い場所で「描画」を制御できる、堅牢なキャンバスであり続ける。

標準の `System.Windows.Forms.Button` を配置し、プロパティウィンドウで色を変えて満足しているようでは、シニアエンジニアとは呼べない。真のプロフェッショナルは、コントロールのライフサイクルを掌握し、`OnPaint` メソッドを戦場として、独自の美学とパフォーマンスを共存させる。

今回は、グラデーションと角丸を纏った「モダンUIボタン」を、メモリ効率と描画パフォーマンスを極限まで高めた形で実装する手法を解説する。

—

1. 描画の鉄則:GDIオブジェクトのライフサイクル管理

カスタム描画において、初心者が最も犯しやすい罪は「リソースの垂れ流し」だ。`OnPaint` は、ウィンドウの再描画が必要な際(リサイズ、他ウィンドウによる遮蔽からの復帰など)に、秒間数十回呼ばれることもある。

その中で `New Pen` や `New SolidBrush` を発行し、`Dispose` を怠れば、GDIハンドルは瞬く間に枯渇し、システム全体を不安定に陥れる。

「生成したものは、そのスコープ内で必ず破壊せよ」

これが鉄則だ。`Using` ブロックの使用は、もはやマナーではなく義務である。

—

2. 極限のモダンボタン:実装コード

以下に、再利用性と保守性を担保した `ModernButton` クラスの実装を示す。このコードは、単に見た目を変えるだけでなく、状態遷移(マウスホバー、クリック)を正確に捕捉し、二重バッファリングによってチラつきを完全に排除している。

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Windows.Forms
Imports System.ComponentModel

”’

”’ 究極の描画制御を具現化したモダンUIカスタムボタン。
”’ 標準コントロールの制約を離れ、GDI+の全能力を解放する。
”’

Public Class ModernButton
Inherits Button

‘ 状態管理用の列挙体
Private Enum ControlState
Normal
Hover
Pressed
End Enum

Private _currentState As ControlState = ControlState.Normal
Private _cornerRadius As Integer = 15
Private _gradientColorStart As Color = Color.FromArgb(52, 152, 219)
Private _gradientColorEnd As Color = Color.FromArgb(41, 128, 185)

Public Sub New()
‘ 二重バッファリングの有効化:描画のチラつきを根絶する
Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
ControlStyles.UserPaint Or
ControlStyles.OptimizedDoubleBuffer Or
ControlStyles.ResizeRedraw Or
ControlStyles.SupportsTransparentBackColor, True)

Me.DoubleBuffered = True
Me.Size = New Size(150, 45)
Me.BackColor = Color.Transparent ‘ 親コンテナとの親和性
End Sub

#Region “プロパティ定義”

Public Property CornerRadius As Integer
Get
Return _cornerRadius
End Get
Set(value As Integer)
_cornerRadius = value
Me.Invalidate() ‘ 再描画を要求
End Set
End Property
#End Region

#Region “イベントオーバーライド:状態遷移の制御”
Protected Overrides Sub OnMouseEnter(e As EventArgs)
_currentState = ControlState.Hover
Me.Invalidate()
MyBase.OnMouseEnter(e)
End Sub

Protected Overrides Sub OnMouseLeave(e As EventArgs)
_currentState = ControlState.Normal
Me.Invalidate()
MyBase.OnMouseLeave(e)
End Sub

Protected Overrides Sub OnMouseDown(mevent As MouseEventArgs)
_currentState = ControlState.Pressed
Me.Invalidate()
MyBase.OnMouseDown(mevent)
End Sub

Protected Overrides Sub OnMouseUp(mevent As MouseEventArgs)
_currentState = ControlState.Hover
Me.Invalidate()
MyBase.OnMouseUp(mevent)
End Sub
#End Region

”’

”’ 描画ロジックの心臓部。
”’ GraphicsPathを用いて角丸を定義し、LinearGradientBrushで彩色する。
”’

Protected Overrides Sub OnPaint(pevent As PaintEventArgs)
Dim g = pevent.Graphics
‘ アンチエイリアス設定:エッジのジャギーを抹殺し、滑らかな曲線を保証する
g.SmoothingMode = SmoothingMode.AntiAlias
g.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit

Dim rect As New Rectangle(0, 0, Me.Width – 1, Me.Height – 1)

‘ 角丸パスの生成
Using path As GraphicsPath = GetRoundPath(rect, _cornerRadius)
‘ 1. 背景描画
Dim fillStart = _gradientColorStart
Dim fillEnd = _gradientColorEnd

‘ 状態に応じた色のフィードバック(シニアレベルのUX設計)
Select Case _currentState
Case ControlState.Hover
fillStart = Color.FromArgb(255, fillStart) ‘ 彩度調整などのロジックをここへ
Case ControlState.Pressed
fillStart = fillEnd ‘ 色を反転させる等の演出
End Select

Using brush As New LinearGradientBrush(rect, fillStart, fillEnd, LinearGradientMode.Vertical)
g.FillPath(brush, path)
End Using

‘ 2. 外枠描画(極細の境界線がモダンさを生む)
Using pen As New Pen(Color.FromArgb(100, Color.White), 1)
g.DrawPath(pen, path)
End Using

‘ 3. テキスト描画(TextRendererを使用し、OS標準のレンダリングを模倣)
Dim flags As TextFormatFlags = TextFormatFlags.HorizontalCenter Or
TextFormatFlags.VerticalCenter Or
TextFormatFlags.EndEllipsis
TextRenderer.DrawText(g, Me.Text, Me.Font, rect, Me.ForeColor, flags)
End Using
End Sub

”’

”’ 指定された矩形から角丸のGraphicsPathを生成する。
”’ 数学的整合性を保ち、リークのないパスを構築する。
”’

Private Function GetRoundPath(rect As Rectangle, radius As Integer) As GraphicsPath
Dim path As New GraphicsPath()
Dim d As Integer = radius 2

‘ 四隅の円弧を接続し、閉じたパスを作成
path.AddArc(rect.X, rect.Y, d, d, 180, 90)
path.AddArc(rect.Right – d, rect.Y, d, d, 270, 90)
path.AddArc(rect.Right – d, rect.Bottom – d, d, d, 0, 90)
path.AddArc(rect.X, rect.Bottom – d, d, d, 90, 90)
path.CloseFigure()

Return path
End Function
End Class

—

3. アーキテクトの視点:なぜ `Region` を使わないのか?

よくある「角丸ボタン」の解説記事では、`Control.Region` プロパティに `GraphicsPath` を割り当てる手法が紹介される。しかし、それは「二流」の解法だ。

`Region` プロパティによる切り抜きは、Windowsの古いリージョンAPIに依存しており、アンチエイリアスが効かない。結果として、ボタンの角がギザギザ(ジャギー)になる。
本稿で示した手法は、背景を透過させつつ、`OnPaint` 内で `SmoothingMode.AntiAlias` を適用して直接描画している。これにより、Retinaディスプレイや高DPI環境でも耐えうる、極めて滑らかなエッジを実現している。

—

4. パフォーマンスの最適化:`Invalidate` の最小化

コード内で `Me.Invalidate()` を呼び出しているが、これは「画面が汚れたので、OSのタイミングで描き直してくれ」というリクエストだ。
大規模な業務システムにおいて、何百ものカスタムコントロールが同時に `Invalidate` を乱発すれば、CPU負荷は跳ね上がる。

実務レベルでは、以下の最適化を検討すべきだ:

  • 矩形指定更新: `Me.Invalidate(rect)` のように、更新が必要な範囲(例えばマウスが乗った境界部分だけ)に限定して再描画をかける。
  • 状態の変更チェック: 現在の状態が `Hover` で、次の状態も `Hover` ならば `Invalidate` を呼ばない。

—

5. 結論:レガシーをモダンへと昇華させる

VB.NETとWinFormsは、決して過去の遺物ではない。
WindowsというOSがビジネスの基盤である以上、APIを叩き、GDI+を飼い慣らす技術は、エンジニアにとっての「聖剣」である。

標準のコントロールを疑え。
描画パイプラインを理解せよ。
そして、ユーザーが触れるその1ピクセルに、エンジニアとしての魂を込めろ。

このモダンボタンの実装は、その第一歩に過ぎない。これをベースに、アニメーション(Timerによる不透明度操作)や、Windows API (`SendMessage`) を利用したより深いOS連携へと歩を進めてほしい。

タイトルとURLをコピーしました