radianの備忘録

プログラミングやPCに関する事をメモ代わりに残していきます

半透明なコントロールを作成する

通常、コントロールは透過色をサポートしていませんが、コントロール作成時にいくつか設定を行うことで透過色を使うことが出来るようになります。

f:id:radian_jp:20141112171825j:plain

https://1drv.ms/u/s!AiiVDEkK6bacgVnsGluZPiwBHy98

デモプログラムを用意してみました。
下のスライダーを動かすと、Label背景の透過率が変わります。α値0で、完全に背景が透明になります。

さて、どうやって半透明を実現しているかを解説していきます。
以下、TransparentLabelクラスのソースです。

''' <summary>
''' α値を設定可能なLabelコントロール。
''' </summary>
''' <remarks></remarks>
Public Class TransparentLabel
    Inherits Label

    Private _BackAlpha As Integer = 0 '背景のα値(0~255、0で透明)

    ''' <summary>
    ''' 背景のα値を取得または設定します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <System.ComponentModel.DefaultValue(GetType(Integer), "0"), _
     System.ComponentModel.Description("背景のα値を取得または設定します。(0~255)")> _
    Public Property BackAlpha As Integer
        Get
            Return _BackAlpha
        End Get
        Set(value As Integer)
            If value < 0 Then
                value = 0
            ElseIf value > 255 Then
                value = 255
            End If
            _BackAlpha = value
            Me.Redraw()
        End Set
    End Property

    ''' <summary>
    ''' 再描画を行う。
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Redraw()
        If _BackAlpha = 255 Then
            Me.Invalidate()
        Else
            '半透明or透明の場合、親が先に描画される必要があるため、親をInvalidate
            If Not IsNothing(Parent) Then
                Parent.Invalidate(New Rectangle(Me.Left, Me.Top, Me.Width, Me.Height), True)
            End If
        End If
    End Sub

    Public Sub New()
        Me.SetStyle(ControlStyles.UserPaint, True)                    'コントロールを独自描画する
        Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True) 'α値を有効にする
        Me.SetStyle(ControlStyles.Opaque, True)                       '背景自動描画OFF
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, False)       'Falseにしないと表示がおかしくなる
    End Sub

    Protected Overrides ReadOnly Property CreateParams As System.Windows.Forms.CreateParams
        Get
            'WS_EX_TRANSPARENT
            'このスタイルで作成されたウィンドウは透明になります。 
            'このスタイルで作成されたウィンドウは、そのウィンドウの下にある
            '兄弟ウィンドウがすべて更新された後にだけ、 WM_PAINT メッセージを受け取ります。 
            Const WS_EX_TRANSPARENT As Integer = &H20

            Dim params As CreateParams = MyBase.CreateParams
            params.ExStyle = params.ExStyle Or WS_EX_TRANSPARENT
            Return params
        End Get
    End Property

    Protected Overrides Sub OnPaint(e As System.Windows.Forms.PaintEventArgs)

        If _BackAlpha > 0 Then
            'α値を設定して背景を描画
            Dim argb As Integer = (Me.BackColor.ToArgb And &HFFFFFF) Or (Me._BackAlpha << 24)
            Using br As New SolidBrush(Color.FromArgb(argb))
                Dim g As Graphics = e.Graphics
                g.FillRectangle(br, g.VisibleClipBounds)
            End Using
        End If

        '基底クラスOnPaint呼び出し(文字を書いてもらう)
        MyBase.OnPaint(e)
    End Sub

End Class

全体の流れは、

①コントロールスタイルの設定(New)
 ↓
②ウィンドウスタイルの設定(CreateParams)
 ↓
③描画イベントで、α値を設定してコントロールを描画(OnPaint)

といった感じです。
半透明を実現するキモの部分は、CreateParamsをオーバーライドして、ウィンドウスタイルにWS_EX_TRANSPARENTを追加している所です。

背景色にα値を設定しても、他コントロールが描画される際にはα値を考慮してくれないため、他コントロールが先に描画される必要があるのですが、WS_EX_TRANSPARENTをウィンドウスタイルに設定することによって、自分が最後に描画されるようになります。

注意点としては、WS_EX_TRANSPARENTを設定したコントロール同士が重なった場合、どちらが先に描画されるか判らなくなるため、描画順がおかしくなります。

f:id:radian_jp:20141113140331j:plain

この画像の例では、デザイナ画面では黄色のラベルを最前面に設定しているのですが、青色のラベルが黄色の手前に表示されているのが判ると思います。WS_EX_TRANSPARENTを設定したコントロール同士は、重ねないようにしてください。

コントロールを半透明に見せる方法は色々ありますが、OSレベルで正式にサポートされている訳では無いので、色々不具合があるのが現状で、完全な解決策は
今のところありません。