<!-- Download Links -->
Flatten That Combobox
In .NET, Microsoft has given us many things to work with. It's nice how they standardized properties like Text and Name on most controls. However, what's the story with the flat look?
Some controls have a flat look as a property, either through the FlatStyle property or the Border property. I love the flat look and have been using it for all my controls, except one. The combobox. Do you know how funny a data entry form looks when all its text boxes, checkboxes, radio buttons, command buttons, etc are flat, but its combo boxes are 3D? You probably do, and that's why you're looking here...
This project is nothing more than a simple combobox that inherits from combobox. All events and properties of the regular combo box are there. It even does data binding. The great thing is that it's flat.
The flat look is accomplished by subclassing, a cool OO concept that old VB developers new to VB.NET are just getting used to. There are 6 overridden methods:
Protected Overrides Sub OnMouseEnter(ByVal e As System.EventArgs)
Protected Overrides Sub OnMouseLeave(ByVal e As System.EventArgs)
Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
Protected Overrides Sub OnMouseHover(ByVal e As System.EventArgs)
Protected Overrides Sub WndProc(ByRef m As Message)
The focus and enter methods all do pretty much the same thing. They call MyBase.OnMethod (which, amongst other things, is responsible for raising the proper events), set the color of the brush we will use to paint the background, and call
Me.Invalidate(), which causes the control to repaint.
The real work is done by the WndProc method. For some reason that I have not figured out yet, the combobox does not call OnPaint in the base class. The control is actually drawn using windows messages. I used a Select Case to evaluate the message. Case &HF (15) is the value for the paint method. It comes from the windows message constant
Once we capture the WM_PAINT message, the work can begin. I simply color the background color according to the color that was set by one of the events mentioned earlier. (For example, OnGotFocus sets the color to
SystemColors.Highlight, which is found in the System.Drawing namespace). I do this by creating a handle to a Graphics object and calling the
Dim g As Graphics = Me.CreateGraphics
The base class will draw an edit box on top of this background fill, giving a highlighted effect to the control.
The next thing is to draw the rectangle for the dropdown button. This will always have a fixed width and location, relevant to the width of the combobox control. This is how the base control works, and it also makes locating the drawing area of the dropdown arrow much easier.
Dim rect As Rectangle = New Rectangle(Me.Width - 15, 3, 12, _
Me.Height - 6)
The last thing to do is to draw the dropdown arrow. This is done by creating a path and filling it. The Drawing2D.GraphicsPath object serves as a way to draw lines in a “freestyle” manner. These lines will always be connected. The path will always close itself in a straight line from the last mapped point to the first point. It’s kind of like drawing with an old etch-a-sketch where the pen never leaves the drawing surface. For this reason, to draw a triangle, it is only necessary to draw 2 lines.
What I did in the code below is declare the 3 points on the triangle (relative to the vertical middle of the control). Then I draw 2 lines. One line runs from top left to top right, and the other from the top right to the bottom.
Dim pth As Drawing2D.GraphicsPath = New Drawing2D.GraphicsPath()
Dim TopLeft As PointF = New PointF(Me.Width - 13, (Me.Height - 5) / 2)
Dim TopRight As PointF = New PointF(Me.Width - 6, (Me.Height - 5) / 2)
Dim Bottom As PointF = New PointF(Me.Width - 9, (Me.Height + 2) / 2)
I then set the smoothing mode property of the graphics object so that the triangle will look crisper.
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
Whenever we fill something, we need to use a Brush. The signature of the Fill methods ask for a Brush, but you cannot create an instance of Brush. The Brush class is abstract, meaning it must be inherited to be used. What to do? The best bet for a solid fill is to use the SolidBrush class. This can be tough to figure out, as it was for me when I was first starting with GDI+.
To determine the color to fill the triangle with, I check the DroppedDown property. I noticed on the combobox control, the arrow is 2 different colors, depending on if it is dropped down or not.
If Me.DroppedDown Then
ArrowBrush = New SolidBrush(SystemColors.HighlightText)
ArrowBrush = New SolidBrush(SystemColors.ControlText)
Finally, it is time to fill the triangle.
Voila! There you have it. A flat combobox. I added a property to the control for button color, so the artsy GUI designers can have more flexibility. It’s code is straight forward for the most part, but I did want to point out something about the property set statement.
Public Property ButtonColor() As Color
Set(ByVal Value As Color)
_ButtonColor = Value
DropButtonBrush = New SolidBrush(Me.ButtonColor)
Notice the call to
Me.Invalidate(). This causes the control to repaint itself. Now, at design time, when the property is changed, the designer will show the new button color.
Short and sweet this project is. There are several good things to learn out of it though. Drawing custom controls using GDI+ and subclassing are 2 very important things to learn in .NET. I hope this article helps you to understand the breakdown of the code behind the FlatCombo, as well as helped to give you some more understanding about these important concepts.
I had previously done this project as a conversion from a C# project I found. If you download the source code, you will see the old version. It had a few quirky bugs around the way that it drew. It was also way overcomplicated. I thought I would leave it behind incase you wanted to see a different solution.