Click here to Skip to main content
15,911,711 members
Articles / Programming Languages / Visual Basic

True Transparency Support for the .NET CheckBox Control

Rate me:
Please Sign up or sign in to vote.
3.64/5 (22 votes)
25 Sep 2004CPOL5 min read 122.5K   2.2K   54   10
An article on adding true transparency support to the .NET CheckBox control, in C# or VB.NET
This article addresses failure of the .NET Tab control to Theme properly by adding transparency support to the CheckBox control.

Sample Image - customcheckbox.png

Introduction

There are several articles on the web bemoaning the failure of the .NET Tab control to Theme properly. Some of these mention that the CheckBox control, among others, does not draw its background correctly when used on the tab control. The core of the problem for the checkbox is that it does not properly support transparency.

This article addresses this issue and adds transparency support to the CheckBox control. Additionally, it makes the control draw in XP Style, when the FlatStyle is set to FlatStyle.Standard, even on older platforms that don't support themes.

Reproducing the Problem

The problem itself is easy to reproduce. While there is no problem using transparency on the CheckBox in simple applications, as soon as you start to control painting of controls yourself, for example, to add a gradient fill to a form, the problems begin! See for yourself.

Create a new Windows Application Project and drop the standard .NET CheckBox onto the Form1 that Visual Studio creates for you.

Now, add the following code to give the form a gradient filled background:

C#
protected override void OnPaintBackground(
    System.Windows.Forms.PaintEventArgs e)
{
    base.OnPaintBackground(e);
    System.Drawing.Drawing2D.LinearGradientBrush brush 
        = new System.Drawing.Drawing2D.LinearGradientBrush(
        this.ClientRectangle, 
        System.Drawing.SystemColors.Highlight, 
        System.Drawing.SystemColors.Window, 
        System.Drawing.Drawing2D.LinearGradientMode.BackwardDiagonal);

    e.Graphics.FillRectangle(brush, this.ClientRectangle);
    brush.Dispose();
}
VB
Protected Overrides Sub OnPaintBackground( _
    ByVal e As System.Windows.Forms.PaintEventArgs)
    MyBase.OnPaintBackground(e)

    Dim brush As New System.Drawing.Drawing2D.LinearGradientBrush( _
        Me.ClientRectangle, _
        System.Drawing.SystemColors.Highlight, _
        System.Drawing.SystemColors.Window, _
        System.Drawing.Drawing2D.LinearGradientMode.BackwardDiagonal)

    e.Graphics.FillRectangle(brush, Me.ClientRectangle)
    brush.Dispose()

End Sub

As you can see, the CheckBox picks up the BackColor of the control behind it, in this case the Form, and not the gradient fill you painted. You can't remedy this by setting the BackColor to Transparent as the control does not support it.

The Solution, Stage 1

At first, all seemed easy. If you subclass the CheckBox control and set the SupportsTransparentBackColor style bit to true, it becomes possible to set the BackColor to Transparent and all seems fine. The subclass looks like this:

C#
public class MyCheckBox : CheckBox
{
    public MyCheckBox(): base()
    {
    this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    }
}
VB
Public Class MyCheckBox : Inherits CheckBox

    Public Sub New()
        MyBase.New()
        Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
    End Sub

End Class

A few quick tests, changing the FlatStyle proves this to work fine for FlatStyle.Flat, FlatStyle.Popup and FlatStyle.Standard, but FlatStyle.System now displays a white background, even with themes enabled!

This may be sufficient for many of you, but the next stage deals with this problem as well.

The Final Solution

In the end, the only solution proved to lie in taking over the drawing of the check mark completely. It was necessary to hide the FlatStyle from the base class, and when it appears to be set to FlatStyle.System, set the base class property to FlatStyle.Standard and subvert the painting of the control. In fact, the standard check mark does draw underneath, but my custom check mark draws over the top.

In keeping with the way in which the .NET CheckBox control manages its drawing, I created a helper class to draw the check mark in a specified position. The colors are then provided by a third class that supplies the correct color set for the current theme by calling the Windows uxTheme.Dll to find out what the current theme is. If themes are not supported (Pre Windows XP), the color class returns the Blue theme colors, so you could use this to give your non themed apps the themed look and feel.

One side effect of calling the uxTheme.dll is that the UnManaged Code Access Security permission is required. Thankfully, I was able to avoid a stack walk at run time, however, the permission is required at JIT compile time. Normally, this is not an issue. However, you will find that you cannot use this control from a network share without fiddling with your .NET Security Configuration. However, this is the same problem you would have if you tried to use any other control set from a network share as all the decent ones call Unmanaged code at some point, and in fact, so many things are not possible in this configuration that it is advisable to run all .NET apps from a local drive.

On the plus side, my control actually themes better than the default .NET CheckBox control, as the color of the check mark border does not vary with the themes in the .NET control, whereas my control sets the check mark border to the same color as the font color used in the expander panel headers of the Explorer bar shown on the left of the My Computer view of Windows Explorer. I chose this as the colors match for the default (Blue) theme and I think it is an improvement.

Aside from defaulting the BackColor to Transparent, and the FlatStyle to FlatStyle.System, I have made no changes to the default action of this customization of the CheckBox control. In my personal version, I have defaulted the text to an empty string, forced the DataBindings to persist in the designer, and added a databinding between the checked state and the font style so that the text changes to bold font when the CheckBox is checked.

Please note that the control does not provide a view through to the windows behind when using the Form, TransparencyKey property. This is because I have had to set the AllPaintingInWMPaint style bit to true to ensure that the check mark draws correctly.

Using the Control

The demo project and source code differ only in the inclusion of a Windows application project to demonstrate the difference between the CustomCheckBox and the default CheckBox. Both zip files contain the entire source code for the three classes required, packaged as Windows Control Projects in C#.NET and VB.NET so that you can copy the source code into your own projects.

If you wish to use the control as it is, build either Windows control project, C#.NET or VB.NET as your preference takes you, and add the control to your toolbox by selecting the resulting assembly from the Add/Remove Items dialog when customizing your Toolbox.

History

  • 25th September, 2004: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Unfortunately my real name was already in use as a code project login. For those of you who are wondering I am really Napoleon Solo. Sorry, I mean, Mark Jackson. Well, I do look a bit like him I think.

Comments and Discussions

 
GeneralThanks Pin
Leonardo Daga1-Sep-08 7:21
Leonardo Daga1-Sep-08 7:21 
Questionmaking image transparent over other in vb.net Pin
vikasmaderna29-Jul-07 8:48
vikasmaderna29-Jul-07 8:48 
Hi,

In this program there are 6 pictureboxes and i want to make the now if i keep and transparent image in picturebox then i should be able to see the contents of picturebox1 and picturebox2 and the form, whichever portion of image is transparent the image behind should be visible, i am missing something, please help me

This is my code
Public Class Form1
Dim SelectObj As Object
Dim RecWidth, RecHeight, Rect, RecLeft As Integer
Private handleSize As Integer = 5
Private overHandle As Boolean
Private inPB As Boolean = False
Private resizing As Boolean = False
Private rc1 As Rectangle
Private rc2 As Rectangle
Private rc3 As Rectangle
Private rc4 As Rectangle
Private startPoint As Point
Private endPoint As Point
Private lastPoint As Point
Private screenRC As Rectangle
Public OldLeft As Integer
Public OldTop As Integer
Public startX, startY As Integer
Dim tpVal As Single = 1.0F

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
PictureBox1.BackColor = Color.Transparent
PictureBox2.BackColor = Color.Transparent
PictureBox3.BackColor = Color.Transparent
PictureBox4.BackColor = Color.Transparent
PictureBox5.BackColor = Color.Transparent
PictureBox6.BackColor = Color.Transparent
PictureBox1.BorderStyle = BorderStyle.FixedSingle
PictureBox2.BorderStyle = BorderStyle.FixedSingle
PictureBox3.BorderStyle = BorderStyle.FixedSingle
PictureBox4.BorderStyle = BorderStyle.FixedSingle
PictureBox5.BorderStyle = BorderStyle.FixedSingle
' PictureBox6.BorderStyle = BorderStyle.FixedSingle
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Dim frm1 As New MyForm
'frm1.Show()
PictureBox1.Parent = Me
PictureBox2.Parent = Me
PictureBox3.Parent = Me
PictureBox4.Parent = Me
PictureBox5.Parent = Me
PictureBox6.Parent = Me


End Sub

Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click, PictureBox2.Click, PictureBox3.Click, PictureBox4.Click, PictureBox5.Click, PictureBox6.Click
SelectObj = DirectCast(sender, PictureBox)
End Sub

Private Sub BringToFrontToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BringToFrontToolStripMenuItem.Click
SelectObj.BringToFront()
End Sub


Private Sub PictureBox1_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles PictureBox1.MouseEnter, PictureBox2.MouseEnter, PictureBox3.MouseEnter, PictureBox4.MouseEnter, PictureBox5.MouseEnter, PictureBox6.MouseEnter
UpdateHandles()
inPB = True
DirectCast(sender, PictureBox).Refresh()
SelectObj = DirectCast(sender, PictureBox)
Me.TransparencyKey = Color.Red
End Sub

Private Sub UpdateHandles()
On Error Resume Next
rc1 = New Rectangle(0, 0, handleSize, handleSize)
rc2 = New Rectangle(0, SelectObj.Height - handleSize - 2, handleSize, handleSize)
rc3 = New Rectangle(SelectObj.Width - handleSize - 2, 0, handleSize, handleSize)
rc4 = New Rectangle(SelectObj.Width - handleSize - 2, SelectObj.Height - handleSize - 2, handleSize, handleSize)

End Sub

Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint, PictureBox2.Paint, PictureBox3.Paint, PictureBox4.Paint, PictureBox5.Paint, PictureBox6.Paint
If inPB Then
e.Graphics.FillRectangle(Brushes.Red, rc1)
e.Graphics.FillRectangle(Brushes.Red, rc2)
e.Graphics.FillRectangle(Brushes.Red, rc3)
e.Graphics.FillRectangle(Brushes.Red, rc4)
End If
SelectObj = DirectCast(sender, PictureBox)
' ControlPaint.DrawReversibleFrame(New Rectangle(RecLeft, Rect, RecWidth - 2, RecHeight - 2), Color.Cyan, FrameStyle.Thick)

'Me.RectangleToScreen(New Rectangle(0, 0, RecWidth, RecHeight))
' e.Graphics.DrawRectangle(Pens.Black, New Rectangle(RecLeft, Rect, RecWidth, RecHeight))
End Sub

Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown, PictureBox2.MouseDown, PictureBox3.MouseDown, PictureBox4.MouseDown, PictureBox5.MouseDown, PictureBox6.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left AndAlso overHandle Then
resizing = True
If rc1.Contains(e.X, e.Y) Then
startPoint = New Point(DirectCast(sender, PictureBox).Width, DirectCast(sender, PictureBox).Height)
endPoint = New Point(0, 0)
ElseIf rc2.Contains(e.X, e.Y) Then
startPoint = New Point(DirectCast(sender, PictureBox).Width, 0)
endPoint = New Point(0, DirectCast(sender, PictureBox).Height)
ElseIf rc3.Contains(e.X, e.Y) Then
startPoint = New Point(0, DirectCast(sender, PictureBox).Height)
endPoint = New Point(DirectCast(sender, PictureBox).Width, 0)
ElseIf rc4.Contains(e.X, e.Y) Then
startPoint = New Point(0, 0)
endPoint = New Point(DirectCast(sender, PictureBox).Width, DirectCast(sender, PictureBox).Height)
End If
lastPoint = New Point(e.X, e.Y)
' screenRC = PictureBox1.RectangleToScreen(NormalizedRC(startPoint, endPoint))
' ControlPaint.DrawReversibleFrame(screenRC, PictureBox1.BackColor, FrameStyle.Dashed)

Else
startX = e.X
startY = e.Y
End If
SelectObj = DirectCast(sender, PictureBox)
' ControlPaint.DrawReversibleFrame(New Rectangle(RecLeft, Rect, RecWidth - 2, RecHeight - 2), Color.Cyan, FrameStyle.Thick)

End Sub

Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove, PictureBox2.MouseMove, PictureBox3.MouseMove, PictureBox4.MouseMove, PictureBox5.MouseMove, PictureBox6.MouseMove
overHandle = rc1.Contains(e.X, e.Y) OrElse rc2.Contains(e.X, e.Y) OrElse rc3.Contains(e.X, e.Y) OrElse rc4.Contains(e.X, e.Y)
If resizing Then
' ControlPaint.DrawReversibleFrame(screenRC, PictureBox1.BackColor, FrameStyle.Dashed)
endPoint.X = endPoint.X + (e.X - lastPoint.X)
endPoint.Y = endPoint.Y + (e.Y - lastPoint.Y)
lastPoint = New Point(e.X, e.Y)
screenRC = PictureBox1.RectangleToScreen(NormalizedRC(startPoint, endPoint))
ControlPaint.DrawReversibleFrame(screenRC, PictureBox1.BackColor, FrameStyle.Dashed)

ElseIf e.Button = Windows.Forms.MouseButtons.Left Then
DirectCast(sender, PictureBox).Location = New Point(DirectCast(sender, PictureBox).Left - (startX - e.X), DirectCast(sender, PictureBox).Top - (startY - e.Y))
End If
' ControlPaint.DrawReversibleFrame(New Rectangle(RecLeft, Rect, RecWidth - 2, RecHeight - 2), Color.Cyan, FrameStyle.Thick)
SelectObj = DirectCast(sender, PictureBox)
End Sub

Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp, PictureBox2.MouseUp, PictureBox3.MouseUp, PictureBox4.MouseUp, PictureBox5.MouseUp, PictureBox6.MouseUp
If resizing Then
resizing = False
' ControlPaint.DrawReversibleFrame(screenRC, DirectCast(sender, PictureBox).BackColor, FrameStyle.Dashed)
Dim newPosition As Rectangle = Me.RectangleToClient(DirectCast(sender, PictureBox).RectangleToScreen(NormalizedRC(startPoint, endPoint)))
DirectCast(sender, PictureBox).SuspendLayout()
DirectCast(sender, PictureBox).Location = newPosition.Location
DirectCast(sender, PictureBox).Size = newPosition.Size
DirectCast(sender, PictureBox).ResumeLayout()
UpdateHandles()
' PictureBox1.Image = MakePicSmall(PictureBox3, newPosition.Width, newPosition.Height, True)
DirectCast(sender, PictureBox).Refresh()
End If
' ControlPaint.DrawReversibleFrame(New Rectangle(RecLeft, Rect, RecWidth - 2, RecHeight - 2), Color.Cyan, FrameStyle.Thick)
SelectObj = DirectCast(sender, PictureBox)
End Sub

Private Sub PictureBox1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles PictureBox1.MouseLeave, PictureBox2.MouseLeave, PictureBox3.MouseLeave, PictureBox4.MouseLeave, PictureBox5.MouseLeave, PictureBox6.MouseLeave
inPB = False
DirectCast(sender, PictureBox).Refresh()
ControlPaint.DrawReversibleFrame(New Rectangle(RecLeft, Rect, RecWidth - 2, RecHeight - 2), Color.Cyan, FrameStyle.Thick)
SelectObj = DirectCast(sender, PictureBox)
End Sub

Private Function NormalizedRC(ByVal ptA As Point, ByVal ptB As Point) As Rectangle
Return New Rectangle(Math.Min(ptA.X, ptB.X), Math.Min(ptA.Y, ptB.Y), Math.Abs(ptA.X - ptB.X), Math.Abs(ptA.Y - ptB.Y))
End Function


Private Function GetImage() As Image

Dim filname As String
OpenFileDialog1.ShowDialog()
filname = OpenFileDialog1.FileName

Return Image.FromFile(filname)

End Function

Private Sub PictureBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles PictureBox1.DoubleClick, PictureBox2.DoubleClick, PictureBox3.DoubleClick, PictureBox4.DoubleClick, PictureBox5.DoubleClick, PictureBox6.DoubleClick
DirectCast(sender, PictureBox).Image = GetImage()
DirectCast(sender, PictureBox).SizeMode = PictureBoxSizeMode.AutoSize
End Sub

Private Sub ApplyMaskToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ApplyMaskToolStripMenuItem.Click
' SelectObj.image = ProcessImageMask(SelectObj.image)
End Sub

Private Sub TrackBar1_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TrackBar1.Scroll
tpVal = CSng(TrackBar1.Value) / 10
Me.Invalidate()

End Sub
End Class

can any one solve this problem
thanks


dsd

Generalvery nice! Pin
iamcoder10-Apr-06 17:51
iamcoder10-Apr-06 17:51 
GeneralProblem with classic style Pin
rvdenden9-Feb-06 7:36
rvdenden9-Feb-06 7:36 
QuestionRe: Problem with classic style Pin
The Man from U.N.C.L.E.9-Feb-06 22:52
The Man from U.N.C.L.E.9-Feb-06 22:52 
AnswerRe: Problem with classic style Pin
rvdenden9-Feb-06 23:31
rvdenden9-Feb-06 23:31 
AnswerRe: Problem with classic style Pin
The Man from U.N.C.L.E.10-Feb-06 1:06
The Man from U.N.C.L.E.10-Feb-06 1:06 
GeneralUpdate to the Transparency and Theme Support Tale of Woe Pin
The Man from U.N.C.L.E.6-Feb-06 6:11
The Man from U.N.C.L.E.6-Feb-06 6:11 
GeneralRegular Transparency works for me. Pin
mintguy29-Dec-05 21:01
mintguy29-Dec-05 21:01 
GeneralTransparent Labels Pin
fedeburin14-Aug-05 6:20
fedeburin14-Aug-05 6:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.