VB.NET Tips / Tricks / Examples and Help

VB.NET : Translucent Controls in Windows Forms

Windows Forms controls in .NET do a lot more for us than they did in previous versions of VB. One of the best things about getting all the OO features .NET brought us, is the ability to extend controls through inheritance or some other means. Some controls directly support custom drawing of their visuals, while others need to be extended to do so.

One thing I have seen asked a few times, was how to make a given control translucent, which I like to think of a little differently than transparent.

So after some investigation, I came up with a method that works decently to do this for most standard WinForm controls you would want to do this with. The biggest issues I came across, were dealing with scrolling and the custom drawing, which I am still looking at for controls that scroll. However some controls do not, like a button, and that is what we are going to take a look at today.

The idea of translucent controls really revolves around the concept that your WinForm is going to have an image as its background. It also assumes this image is set to stretch so that the image takes up the entire form background. The code in this example can be tweaked if this is not your exact scenario, as this is more or less a proof of concept on doing things this way.

So first let me explain the concept we are going for by illustrating the end result:

You can see we have a standard button just to pair against the custom button. Then a custom button that looks the same because its IsTranslucent property (one of the custom properties we add to the button) is set to false. The two bottom buttons have IsTranslucent set to true, and the left button has the default gamma setting of 1.0, while the one on the right has a frosted effect by settings its gamma to 0.15.

So now lets talk about how we actually do this.

The first thing I did was identify the common elements to a translucent control. I found that (for now) they are:

  • Background Source - where are we getting the background from to paint onto the button
  • Gamma - value to indicate the over/under saturation of the image to give it a frosted or darkened effect
  • IsTranslucent - indicates if the control should be translucent at all. When this is false, all the custom code is skipped, and the control behaves like a normal button
  • Offset - Since controls often have borders and a 3D look, this property allows an offset of the painted image so it perfectly matches with the background of the form/container. Buttons I found work well with an offset of 5/5, while other controls needed no offset, because they appear flat already.

So because all controls that I want to make translucent will use these features, I created an interface that the controls can implement.

Lets define that interface now:

Public Interface ITranslucentControl

 

    Enum eBackgroundImageSource

        Form = 0

        Container = 1

    End Enum

 

    Property Gamma() As Single

    Property IsTranslucent() As Boolean

    Property Offset() As System.Drawing.Point

    Property BackgroundImageSource() As eBackgroundImageSource

End Interface

Pretty straight forward, it has all the things I described in the bullet points above. If you are not familiar with interfaces, then all you should really know, is that our custom button will implement this interface, which means it will have all these properties in itself, however the actual code in these properties is totally up to the class to implement. Most of these properties simply access backing fields in the class though. There is no real magic going on here, I just made the interface because I know all my custom translucent controls will use these items, but inheritance from a base class was not really an option because we need to inherit from the control we want to extend. So if you are wondering "Couldn't I do this without the interface?" the answer is yes, I just decided to use one so I wouldn't forget any of the common properties I want the control to have.

Ok, so now that we have our interface, we need to make our button class.

Imports System.Windows.Forms

Imports System.Drawing

 

Public Class TranslucentButton

    Inherits System.Windows.Forms.Button

    Implements ITranslucentControl

 

End Class

So our class name is TranslucentButton, and it inherits from button and implement our ITranslucentControl interface.

Lets add a constructor which sets double buffering to true. This can reduce flicker where possible, which is a common issue for WinForms and graphics.

#Region "CONSTRUCTOR(S)"

Public Sub New()

        'SET DOUBLE BUFFERING TO REDUCE FLICKER WHERE POSSIBLE

        MyBase.DoubleBuffered = True

    End Sub

#End Region 

You will note I make use of code regions to keep things separated and easy to manage in the code window.

Next we have all the public properties which came from the ITranslucentControl interface, along with backing private fields to maintain the state of the object. Interfaces can't define fields, so we need to manually do that in each one.

#Region "PUBLIC PROPERTIES AND ASSOCIATED PRIVATE FIELDS"

 

    Private _gamma As Single = 1.0

    ''' <summary>

    ''' Gets or Sets the gamma correction to

    ''' use for the translucent image background

    ''' </summary>

    ''' <value>Single</value>

    ''' <returns>Value (as single) for the gamma correction</returns>

    ''' <remarks>None</remarks>

    Public Property Gamma() As Single _

                            Implements ITranslucentControl.Gamma

        Get

            Return _gamma

        End Get

        Set(ByVal value As Single)

            If value < 0.01 Then value = 0.01

            _gamma = value

        End Set

    End Property

 

    Private _isTranslucent As Boolean = False

    ''' <summary>

    ''' Indicates if control will be drawn translucent. False by default.

    ''' </summary>

    ''' <value>Boolean</value>

    ''' <returns>True if control is translucent, otherwise False.</returns>

    ''' <remarks>None</remarks>

    Public Property IsTranslucent() As Boolean _

                              Implements ITranslucentControl.IsTranslucent

        Get

            Return _isTranslucent

        End Get

        Set(ByVal value As Boolean)

            If value <> _isTranslucent AndAlso value = False Then

                Me.BackgroundImage.Dispose()

                Me.BackgroundImage = Nothing

            End If

            _isTranslucent = value

        End Set

    End Property

 

    Private _Offset As New Point(5, 5)

    ''' <summary>

    ''' Provides an offset value incase the image is 

    ''' not correctly aligning in this control. 

    ''' This can occur when special using a 3d border, 

    ''' or something similar that would cause an offset

    ''' </summary>

    ''' <value>Point. Default value is 5, 5 for this control</value>

    ''' <returns>

    ''' Current offset for this controls background drawing

    '''</returns>

    ''' <remarks>None</remarks>

    Public Property Offset() _

                    As Point _

                    Implements ITranslucentControl.Offset

        Get

            Return _Offset

        End Get

        Set(ByVal value As Point)

            _Offset = value

        End Set

    End Property

 

    Private _backgroundImageSource As _

            ITranslucentControl.eBackgroundImageSource = _

            ITranslucentControl.eBackgroundImageSource.Form

    ''' <summary>

    ''' Gets/Sets the source of the background image for this control

    ''' </summary>

    ''' <value>eBackgroundImageSource enumeration value</value>

    ''' <returns>

    ''' eBackgroundImageSource.Form when the forms

    ''' background should be used, eBackgroundImageSource.Parent

    ''' when the forms immediate parent should be used

    ''' </returns>

    ''' <remarks>

    ''' This setting is ignored when

    ''' IsTranslucent is False

    ''' </remarks>

    Public Property BackgroundImageSource() As _

                    ITranslucentControl.eBackgroundImageSource _

                    Implements ITranslucentControl.BackgroundImageSource

        Get

            Return _backgroundImageSource

        End Get

        Set(ByVal value As ITranslucentControl.eBackgroundImageSource)

            _backgroundImageSource = value

        End Set

    End Property

 

#End Region

This looks like a lot of code, but all it is, is each of the implemented properties from our interface, along with a backing field. There is a decent amount of comments too. You could add some validation to the properties if you wanted, lets say, to limit the gamma value.

We use one private field here that is not part of the interface, and it is also not exposed via a public property. It is called _bypassPainting and it is just a boolean flag we set to skip the paint event of the control from firing twice, which would affect performance.

#Region "PRIVATE FIELDS"

    'FLAG USED TO BYPASS REPAINTING AFTER THE BACKGROUND PICTURE IS SET

    Private _bypassPainting As Boolean = False

#End Region

So that is the entire class, with the exception of the paint event which does all the hard work. Time to dig into that one and see where the "magic" happens.

So the magic that happens, is actually that the button looks at its parent control, which may be a form, or it may be another container, such as a groupbox or panel. You may want to have a button in a panel, but still have it use the forms background image for its translucency. Or maybe you do actually want the panels background image to be the image we see through the button. This is why we have the BackgroundImageSource property, which if you remember is an enum value of Form or Container. This value does not matter at all if the button is directly on the form to begin with. It only comes into play when the button is in an additional container.

The button takes the image should be seen through the button, and calculates which part of this image the button is actually covering. It then takes that region of the image, and creates a new image from that, and uses that as the buttons background image. This gives the button its transparent look, but still gives it all the standard features of a button. From there you can set gamma to make the image in the button lighter or darker if needed. I made the default offset of the button 5,5 which takes into account the buttons 3D appearance. This can be customized in the event the images are not matching perfectly between the parent image and the button image.

I won't go into too much more detail about the paint event, as it is commented pretty well, and if you follow it along, should be self explanatory.

#Region "CUSTOM PAINTING ROUTINE"

    Protected Overrides Sub OnPaint( _

        ByVal pevent As System.Windows.Forms.PaintEventArgs)

        MyBase.OnPaint(pevent)

 

        'IF BYPASSPAINTING FLAG WAS SET, RESET IT AND DO NOT PAINT

        If _bypassPainting Then

            _bypassPainting = False

            Return

        End If

 

        'DON'T DO ANY CUSTOM PAINTING IF THE FEATURE IS DISABLED

        If Not _isTranslucent Then Return

 

        'GET THE CONTROL THAT THE IMAGE

        'IS ON WE WANT TO USE FOR TRANSLUCENCY

        Dim parentControl As Control = Nothing

        If _backgroundImageSource = _

           ITranslucentControl.eBackgroundImageSource.Form Then

            'GET FORM

            parentControl = Me.FindForm

        Else

            'GET PARENT (WILL BE FORM IF NO CONTAINER EXISTS)

            parentControl = Me.Parent

        End If

 

        'IF THE PARENT CONTROL WE ARE REFERENCING

        'HAS NO BACKGROUND IMAGE, DO NOTHING

        'ALSO CHECK TO MAKE SURE STRETCH IS THE LAYOUT TYPE

        If parentControl.BackgroundImage Is Nothing Then Return

        If parentControl.BackgroundImageLayout <> _

           ImageLayout.Stretch Then Return

 

        'SOURCE RECTANGLE IS THE CLIPPED REGION 

        'OF THE FORMS BACKGROUND IMAGE

        'THAT IS BEHIND THE TABPAGE. CLIPPING ALLOWS

        'US TO THEN PASTE THE COVERED PART

        'OF THE FORM BACKGROUND IMAGE ONTO

        'THE LISTVIEW BACKGROUND TO MIMIC TRANSPARENCY

        Dim srcRect As Rectangle = Nothing

 

        If _backgroundImageSource = _

           ITranslucentControl.eBackgroundImageSource.Form Then

            srcRect = New Rectangle(GetPointFromForm(Me), Me.Size)

        Else

            srcRect = Me.Bounds

        End If

 

        'IMAGE WITH THE CURRENT CLIENT SIZE OF THE BACKGROUND

        Dim mySourceImage As New Bitmap(parentControl.BackgroundImage, _

                                        parentControl.ClientSize.Width, _

                                        parentControl.ClientSize.Height)

 

        'BLANK IMAGE WE WANT TO DRAW THE

        'SECTION TO THAT WE WILL DISPLAY IN THE LISTVIEW

        Dim myButtonImage As New Bitmap(srcRect.Width, srcRect.Height)

 

        'CREATE A GRAPHICS OBJECT FROM THE IMAGE

        Dim g As Graphics = Graphics.FromImage(myButtonImage)

 

        'IMAGE ATTRIBUTES SO WE CAN SET GAMMA (TO MAKE IMAGE LIGHTER)

        Dim image_attr As New Drawing.Imaging.ImageAttributes

        image_attr.SetGamma(_gamma)

 

        'DRAW CROPPED AND LIGHTENED IMAGE TO

        'THE GRAPHICS OBJECT (WRITES TO myListViewImage OBJECT)

        g.DrawImage(mySourceImage, _

                    Me.ClientRectangle, _

                    (srcRect.X + _Offset.X), _

                    (srcRect.Y + _Offset.Y), _

                    srcRect.Width, _

                    srcRect.Height, _

                    GraphicsUnit.Pixel, _

                    image_attr)

 

        image_attr.Dispose()

        g.Dispose()

        image_attr = Nothing

        g = Nothing

 

        'BEFORE WE SET THE BACKGROUND IMAGE, SET THE BYPASS FLAG

        'SO WE DONT GET STUCK IN A PAINTING LOOP

        _bypassPainting = True

 

        Me.BackgroundImageLayout = ImageLayout.None

        Me.BackgroundImage = myButtonImage

 

    End Sub

#End Region

There is one other method we use for support here, and it gets the location of the button in relation to the form when the button is not directly on the form (it is in a container or multiple containers on the form)

#Region "SUPPORT METHODS"

    Private Function GetPointFromForm(ByVal C As Control) As Point

        Try

            Return C.FindForm.PointToClient( _

                   C.Parent.PointToScreen(C.Location))

        Catch ex As Exception

            Return New Point(0, 0)

        End Try

    End Function

#End Region

So that is it. Above is all the code you need to get this working, and you can find the source code attached to this post. It was written in Visual Studio 2008, however for maximum compatibility, it is targeting the .NET 2.0 framework since this doesn't use any 3.x exclusive features. That also means you could copy/paste this code into Visual Studio 2005 and it would also work.

One last note I want to touch on again, is that this is just a proof of concept, and has not been fully tested on all operating systems and under various different settings and configurations you may have on your WinForm. That being said, most likely you can modify this code to meet your needs.

Next time I will show you how to do this same thing, but with specific container controls, such as the GroupBox, Panel, and TabControl. I also have done this with the label control. The label support transparent backgrounds right out of the box, however adding my method adds the ability for the frosted effect, as well as the ability to target a parent form, instead of just the label container.

As always, send me your comments or code improvements if you happen to find any.


Posted May 05 2008, 06:44 PM by Matthew Kleinwaks

Comments

Andre wrote re: VB.NET : Translucent Controls in Windows Forms
on Mon, May 26 2008 10:54 AM

Nice way to do but if I see right you just fill your gamma button with an image of the part of the backgroud where it is placed.

A better solution would to copy it from the background of the form where the button is placed so it would be much more flexible ...

Matthew Kleinwaks wrote re: VB.NET : Translucent Controls in Windows Forms
on Tue, May 27 2008 11:14 AM

Andre,

The code in this example works properly when things like the form and button are resized, and it does this without any additional code in the button class. This is due to the fact that the code clips the image from the form, and then uses that new image as the backgroundimage property of the button. Custom painting the button requires additional code to account for size changes. I am not quite sure of the better solution you are suggesting, but please feel free to email me (matt@zerosandtheone.com) with a demo or some more information about it. I would be interested in hearing more about your suggestion.

ShariqDON wrote re: VB.NET : Translucent Controls in Windows Forms
on Thu, Sep 24 2009 7:18 PM

Hi This is ShariqDON

during searching a desighning a buttons i found this i cant make it

Matthew Kleinwaks wrote re: VB.NET : Translucent Controls in Windows Forms
on Fri, Sep 25 2009 5:49 PM

There is a link at the bottom of the article above to download the entire source code of this example.



© 2014 - ZerosAndTheOne.com - Hosting by Orcsweb (http://www.orcsweb.com/)
Powered by Community Server (Non-Commercial Edition), by Telligent Systems