Click here to Skip to main content
Click here to Skip to main content

Making Standard ComboBox appear flat

By , 18 May 2005
 

Sample Image - flatcombo.jpg

Introduction

Many flat combobox controls out there are not based on the standard ComboBox control that is supplied by .NET, instead they have their own interfaces and requires a customized type of ComboBoxItem when inserting into the Items container. If you already have code written based on the standard .NET ComboBox, changing to the flat look may also require you to modify your codes. I have personally experienced that as a problem, and someone else has brought that up as a problem as well in my recent article DateTimePicker appears Flat. Many of the information provided here may appear in the DateTimePicker appears Flat article already, or maybe explained better in there, so please check it out.

Using the Class

This class inherits from ComboBox, therefore you can use it in exactly the same way as the ComboBox control or even as a replacement.

So instead of doing this:

ComboBox  cmb = new ComboBox();

You will do:

ComboBox cmb = new FlatComboBox();
// OR
FlatComboBox cmb = new FlatComboBox();

That is how simple it is :)

Code Explanation

To achieve the flat look for the control, I have to override the WndProc method, which is the method that processes through all the window messages for this control. We are particularly interested with WM_NC_PAINT and WM_PAINT messages.

IntPtr hDC = GetWindowDC(this.Handle);

Graphics gdc = Graphics.FromHdc(hDC);

switch (m.Msg)

{
    case WM_NC_PAINT: 
        SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
        SendPrintClientMsg(); // send to draw client area
        PaintFlatControlBorder(this, gdc);
        m.Result = (IntPtr) 1; // indicate msg has been processed 
        break;
    case WM_PAINT: 
        base.WndProc(ref m);
        // flatten the border area again
        Pen p = new Pen((this.Enabled? BackColor:SystemColors.Control), 2); 
        gdc.DrawRectangle(p, new Rectangle(2, 2, this.Width-3, this.Height-3));
        PaintFlatDropDown(this, gdc);
        PaintFlatControlBorder(this, gdc);
        break;
    default:
        base.WndProc(ref m);
        break;

}

ReleaseDC(m.HWnd, hDC);

gdc.Dispose(); 

}

WM_NC_PAINT message is received when the control needs to paint its border. Here I trapped the message and send WM_PRINTCLIENT message so that the ComboBox will draw its client area properly, then followed by drawing a flat border around it.

WM_PAINT message is received when the control needs to paint portion of its windows. ComboBox internally embeds a textbox and will draw the textbox with 3D border. A quick way to achieve the flat look is to paint a rectangle overlaying the 3D border of the textbox, therefore it will appear flat. We then paint the flat dropdown and border over it. Overriding the border is optional here, but I did it for the user experience where if the control is in focus, it will have a black line border or otherwise none.

Limitation

Currently, this FlatComboBox does not draw with ComboBox.Simple style. When this style is set for the ComboBox object, this class will let the base class perform the standard drawing.

History

  • 18 May 2005
    • DROPDOWNWIDTH value is dynamic now based on the system setting hence will work well in different screen resolutions.
    • Only acquires and creates window DC when necessary.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Fadrian Sudaman
Architect SMS Management and Technology
Australia Australia
Member
Fadrian Sudaman is an experienced IT professional who has worked with .NET technology since the early beta. His background stems from a strong C/C++ development experience in building large commercial applications and great appreciation for best practice and modern approaches for building quality software. Currently, Fadrian works as a senior consultant specialises in .NET technology involved in variety of roles including project management, solution architecture, presales and application development. Fadrian is also completing his PhD part time at Monash University, Australia.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Thanks, but one problemmemberFadrian Sudaman31 May '05 - 17:22 
Thanks for the post and suggestion. I really don't have much idea on how to fix that, as my control is drawing on top of existing control to leverage all the existing functionality. If someone has a solution to this I will be please to know.
 
Fadrian
GeneralRe: Thanks, but one problemmemberSerdar YILMAZ1 Jun '05 - 22:22 
I changed Draw Mode of this control.
Now, there are NO flicker.
And I added ImageList properties.
 
public syComboBox()
{
...
...
base.SetStyle(ControlStyles.ResizeRedraw, true);
this.DrawMode = DrawMode.OwnerDrawVariable;
}
...
...
protected virtual void Draw(bool fillContent)
{
Graphics graphics1 = Graphics.FromHwnd(base.Handle);
 
// Drawing Border
... drawing border procedures
... if (this.isMouseOver | this.Focused) then draw HOT else draw NORMAL

// Drawing Button
ButtonState bs = (DroppedDown) ? ButtonState.Pushed : ButtonState.Normal;
... drawing combobox button procedures

if (fillContent)
{
Rectangle rectangle1 = (1, 1, this.Width-SystemInformation.HorizontalScrollBarArrowWidth-2, this.Height-2);
Color color1 = this.BackColor;
if (!base.Enabled)
{
color1 = SystemColors.Control;
}
SolidBrush brush1 = new SolidBrush(color1);
graphics1.FillRectangle(brush1, 1, 1, rectangle1.Width, 2); // top
graphics1.FillRectangle(brush1, 1, rectangle1.Bottom - 2, rectangle1.Width, 2); // bottom
graphics1.FillRectangle(brush1, 1, 1, 2, rectangle1.Height); // left
graphics1.FillRectangle(brush1, rectangle1.Right - 2, 1, 2, rectangle1.Height); // right
}
}
...
 
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.isMouseOver = false;
this.Draw(false);
}
 
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.isMouseOver = true;
this.Draw(false);
}
 
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
this.Draw(false);
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnLostFocus(e);
this.Draw(false);
}
 
protected override void WndProc(ref Message msg)
{
if (msg.Msg == 15)
{
base.DefWndProc(ref msg);
this.Draw(true);
}
else
{
base.WndProc(ref msg);
}
}

 
-----------------
 Serdar YILMAZ
Senior Developer
GeneralRe: Thanks, but one problemmemberLe_MuLoT15 Nov '05 - 3:33 
please, can you post the modified source ?
GeneralGreat control, but a couple of questionsmemberJazzJackRabbit13 May '05 - 3:26 
Great control, small, simple, and yet does all it's supposed to do. Well, most of it anyway, that's where I want to ask you some questions.
 
1.First is that I implemented a mouseenter/mouseleave event to highlight the border with different color depending on whether the mouse is hovering above the control or not. However if I repeatedly move my mouse in and out of control at least three-four times a second I notice a lot of flickering at the drop down button. My guess is that when you do a WM_PAINT event you let the default wndproc to take over and then paint the custom border and dropdown button which causes the flickering. Is there any way to redraw this manually without base class? I tried it, but I'm very new at this whole GDI and window messages thing, so, not surprisingly I failed.
 
2.This is also related to mouseenter/mouseleave thing. You said that your combobox did not work with simple style. What did you mean by that, what exactly did not work? Because for some reason, and I don't know why I have problems when the style is set to dropdown instead of dropdownlist. The issue I have is that when I set the style to dropdown, sometimes, when I move the mouse quickly in or out of the control mouseenter/mouseleave events do not fire. So the border of the control does not change, it's really annoying. I tried catching wm_mouseleave message in the wndproc, but it seems it never gets sent. Any idea what's causing it?
 

I also posted a question about your datetimepicker control. Can you take a look at it too?
 

Thanks.
GeneralRe: Great control, but a couple of questionsmemberFadrian Sudaman14 May '05 - 17:23 
Thanks for your input.
I haven't looked at the control for a while, so it is a slow start for me as well. These are my answers to your questions:
1. Instead of doing the drawing in mouseenter and mouseleave, I suggest that you just set a bool flag in those function such as m_bMouseEntered = true in the mouseenter even tna m_bMouseEntered = false in the mouseleave event. Change the implementation of PaintFlatControlBorder so that it first test for if (m_bMouseEntered) then call ControlPaint.DrawBorder(g, rect, MyColor, ButtonBorderStyle.Solid);
 
2. ComboBox has 3 style, simple style looks like a normal List control. you can set that property and have a look at your winform for the difference. I don't think the dropdownlist and dropdown style would be different in dealing with those events.
Generalgood job and small suggestionmemberGiancarlo Aguilera17 Jan '05 - 4:37 
first and foremost, good job!
 
second, I noticed that in WndProc you have:
 
IntPtr hDC = GetWindowDC(this.Handle);
Graphics gdc = Graphics.FromHdc(hDC);
 
however, only messages WM_NC_PAINT and WM_PAINT make use of them, while for any other message they're not used at all, therefore, my advice to you would be to limit their scope to just the observed messages. In any case I know you're cleaning up:
 
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
 
however, why create and dispose of resources, in this case, relatively expensive resources, that will not be used.
 
good luck
 

GeneralRe: good job and small suggestionmemberFadrian Sudaman17 Jan '05 - 11:16 
Thanks. I agree with your point. The code was structured that way was partially caused by my laziness and also trying to make the code as easy to read as possible. I did plan to build up a library of these controls, but I have stopped for a while due to some change in circumstances. Keep your suggestions coming and will benefit me and others Smile | :)
GeneralBug on 1900x1200 resolutionmemberElizabeth Gee13 Dec '04 - 7:58 
I have a Sony Vaio with 1900x1200 resolution.
 
This FlatCombobox works great, except there is an odd artifact:
Ther is a wide 3D gap showing between the Textbox portion of the control and the arrow part of the control.
 
Any ideas on this? I tried it as a C# dll and also with the VB.Net code snippet. They both do the same thing.Eek! | :eek:
 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionmemberFadrian Sudaman13 Dec '04 - 11:19 
Liz,
Geez.. that is a very wide screen that you have! I haven't got the environment to try, but did you try my suggested code in other thread:
 
====================================================
When it come to adjusting for drawing, I tempt to use the graphic resolution, so say "g" is the Graphics object, I would do something like this:
int iWidth = (int) ((g.DpiX/96.0f) * DROPDOWNWIDTH);
 
I use 96 there because that is the pixel size for standard font display. I also make it a float so that we keep the precision when performing the math.
====================================================
 
See if that works for you.
 
Fadrian

GeneralRe: Bug on 1900x1200 resolutionmemberElizabeth Gee13 Dec '04 - 11:33 
Fadrian,
 
I am new to CodeProject.com and am excited to hear from you. Yes I did add this dropdownwidth code snippet from ont of the threads as well as the resize snippet.
 
I also added the OnResize snippet
 
Everything compiles well.
 
I think it may be the videocard on the Sony Vaio stretches things, as I am having troubles with the rest of my form where relative co-ordinates are working out better than absolute co-ordinates. This is interesting, and may become more of an issue for software developers as the panoramic screen resolutions become more common over time.
 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionmemberElizabeth Gee13 Dec '04 - 11:43 
BTW,
 
I went to the Form level properties and set the AutoScale to False. This did not change the Bug on the control where there is a 3D wide artifact between the textbox and the arrow.Eek! | :eek: Sigh | :sigh:
 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionmemberFadrian Sudaman13 Dec '04 - 12:30 
Liz,
AutoScale can be nasty and I constantly run into problem with it.
Can you capture the screen of the 3D wide artifact problem and send to my email a/c? Once I see the exact prob, I may be able to make other suggestion.
 
Fadrian
GeneralRe: Bug on 1900x1200 resolutionmemberElizabeth Gee14 Dec '04 - 5:18 
Here is a link to a screenshot of your Flat Combo on my Sony Vaio at 16oox1200 resolution:
http://www32.brinkster.com/icontoo/Temp/FlatComboOnSonyVaio.html
 
Also, here is your code with the two modifications made by others, which I compiled down to a C# DLL as depicted in the screenshot:
 
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
 
namespace DrawFlat
{
[ToolboxBitmap(typeof(System.Windows.Forms.ComboBox))]
public class FlatComboBox: ComboBox
{
public const int WM_ERASEBKGND = 0x14;
public const int WM_PAINT = 0xF;
public const int WM_NC_PAINT = 0x85;
public const int WM_PRINTCLIENT = 0x318;
 
[DllImport("user32.dll", EntryPoint="SendMessageA")]
public static extern int SendMessage (IntPtr hwnd, int wMsg, IntPtr wParam, object lParam);
 
[DllImport("user32")]
public static extern IntPtr GetWindowDC (IntPtr hWnd );
 
[DllImport("user32")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC );
 
public FlatComboBox()
: base()
{
this.SetStyle(ControlStyles.DoubleBuffer, true);
}
 
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged (e);
this.Invalidate();
}
 
protected override void WndProc(ref Message m)
{
if (this.DropDownStyle == ComboBoxStyle.Simple)
{
base.WndProc(ref m);
return;
}
 
IntPtr hDC = GetWindowDC(this.Handle);
Graphics gdc = Graphics.FromHdc(hDC);
switch (m.Msg)
{
case WM_NC_PAINT:
SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
SendPrintClientMsg(); // send to draw client area
PaintFlatControlBorder(this, gdc);
m.Result = (IntPtr) 1; // indicate msg has been processed
break;
case WM_PAINT:
base.WndProc(ref m);
// flatten the border area again
Pen p = new Pen((this.Enabled? BackColor:SystemColors.Control), 2);
gdc.DrawRectangle(p, new Rectangle(2, 2, this.Width-3, this.Height-3));
PaintFlatDropDown(this, gdc);
PaintFlatControlBorder(this, gdc);
break;
default:
base.WndProc(ref m);
break;
}
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
}
private void SendPrintClientMsg()
{
// We send this message for the control to redraw the client area
Graphics gClient = this.CreateGraphics();
IntPtr ptrClientDC = gClient.GetHdc();
SendMessage(this.Handle, WM_PRINTCLIENT, ptrClientDC, 0);
gClient.ReleaseHdc(ptrClientDC);
gClient.Dispose();
}
 
private void PaintFlatControlBorder(Control ctrl, Graphics g)
{
Rectangle rect = new Rectangle(0, 0, ctrl.Width, ctrl.Height);
if (ctrl.Focused == false || ctrl.Enabled == false )
ControlPaint.DrawBorder(g, rect, SystemColors.ControlDark, ButtonBorderStyle.Solid);
else
ControlPaint.DrawBorder(g, rect, Color.Black, ButtonBorderStyle.Solid);
}
public static void PaintFlatDropDown(Control ctrl, Graphics g)
{
const int DROPDOWNWIDTH = 18;
int iWidth = (int) ((g.DpiX/96.0f) * DROPDOWNWIDTH);
//Rectangle rect = new Rectangle(ctrl.Width-DROPDOWNWIDTH, 0, DROPDOWNWIDTH, ctrl.Height);
Rectangle rect = new Rectangle(ctrl.Width-iWidth, 0, iWidth, ctrl.Height);
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
}
 
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
this.Invalidate();
}
 
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
this.Invalidate();
}

protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}
}
}
 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionmemberFadrian Sudaman14 Dec '04 - 11:22 
Liz,
Looks like the width of the dropdown combo is 1 pixel out. I would just change the DROPDOWNWIDTH to 19. It doesn't really matter in normal screen display for the dropdown combo to be 1 pixel wider. Perhaps 19 would be a better size to be used as it will cover the standard 17 pixel width combo and total of 2 3D pixels outer border for the left and right.
 
Fadrian
GeneralRe: Bug on 1900x1200 resolutionmemberElizabeth Gee14 Dec '04 - 12:26 
Beautiful!
I changed the DROPDOWNWIDTH from 18 to 20 then tho 21, and 21 closed it up perfectly at 1900x1200 resolution
 
Thanks!

 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionmemberFadrian Sudaman14 Dec '04 - 17:30 
Glad to hear. Hmm... the solution may only work with high res display, and may not view nicely in low res display. I still suspect the problem lies with the AutoScale setting and the font used, but don't take my word for that. I yet to find a programatic way to solve this problem. If anyone knows the answer, I will appreciate your feedback.
GeneralRe: Bug on 1900x1200 resolutionmemberElizabeth Gee15 Dec '04 - 11:33 
Solved: (Fairly much so)
 
I tested this on my Sony Vaio at:1920x1200, 1600x1200, and 1280x1024. At the higher two resolutions the below fix works great.
 
Also on my standard Dell desktop machine at 1280x1024, it works great.
 
I added a check to your code to check for the Screen Resolution width, and it adjusts the DROPDOWNWIDTH for the resolution above or below 1500.
 
I compiled this C# into a DLL and then tested it on both machines.
What I ran into was the Sony Vaio does a software screen resolution change, not a hardware one, thus when I set the Sony Vaio to 1280x1024, get that small vertical artifact between the textbox and the arrow parts of the combo box.
 
All the rest of the code I left the same as the last time I posted it on this thread. I included the VB.Net and C#.Net variations of this code snippet below...
[Visual Basic.Net]
Public Sub PaintFlatDropDown(ByVal ctrl As Control, ByVal g As Graphics)
Dim DROPDOWNWIDTH As Integer
Dim scrRes As Screen
Dim intScrWidth As Integer
intScrWidth = scrRes.PrimaryScreen.Bounds.Width
 
If (intScrWidth < 1500) Then
DROPDOWNWIDTH = 18
Else
DROPDOWNWIDTH = 21
End If
Dim rect As Rectangle = New Rectangle(ctrl.Width - DROPDOWNWIDTH, 0, DROPDOWNWIDTH, ctrl.Height)
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat)
End Sub
 
[C#.Net]
public static void PaintFlatDropDown(Control ctrl, Graphics g)
{
int DROPDOWNWIDTH = 18;
Screen screen = Screen.PrimaryScreen;
if (screen.Bounds.Width > 1500)
DROPDOWNWIDTH = 21;

int iWidth = (int) ((g.DpiX/96.0f) * DROPDOWNWIDTH);
//Rectangle rect = new Rectangle(ctrl.Width-DROPDOWNWIDTH, 0, DROPDOWNWIDTH, ctrl.Height);
Rectangle rect = new Rectangle(ctrl.Width-iWidth, 0, iWidth, ctrl.Height);
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
}
 
Elizabeth Gee
Information Architect - .Net Developer
http://www.nwtd.com
GeneralRe: Bug on 1900x1200 resolutionsussJuan Pedro Gonzalez20 Jan '05 - 16:22 
Hi Elizabeth,
 
You could also try the snippet:
 
[C# .NET]
public static void PaintFlatDropDown(Control ctrl, Graphics g)
{
Rectangle rect = new Rectangle(ctrl.Width-SystemInformation.VerticalScrollBarWidth, 0, SystemInformation.VerticalScrollBarWidth, ctrl.Height);
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
}
 
It works fine at lower resulutions, and I guess it should also work fine at higher resolutions since it gets the width of the button from the system... you'll also reduce a bit your code.
 
Best wishes
GeneralRe: Bug on 1900x1200 resolutionmemberJuan Pedro Gonzalez20 Jan '05 - 23:04 
Small correction over my own code.
 
I've just found that my code snippet shows part of the 3D button aswel, but not due to the resolution but due to the OS it's running. On XP it runs perfectlly and it adjusts to the button width, but the 3D presentation of the button on Windows 2000, is a bit different snd it messes up a bit.
 
To correct this problem and hide the 3D section of the button (Same as your posted image) I did the following extra modification:
 
[C# .NET]
public static void PaintFlatDropDown(Control ctrl, Graphics g)
{
int myWidth = SystemInformation.VerticalScrollBarWidth;
myWidth += SystemInformation.Border3DSize.Width ;
 
Rectangle rect = new Rectangle(ctrl.Width-myWidth, 0, myWidth, ctrl.Height);
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
}
 
As you can see I've added the 3D border default width, this hides completelly the button... I've used the variable "myWidth" as you should add a check in order to add the border Width if windows themes are disabled, and if they are enabled we just skip this check.
 
Best wishes and hope you find it usefull.
 
And just a question: You're running the control on a Pre-Windows XP, right?
GeneralRe: Bug on 1900x1200 resolutionmemberFadrian Sudaman18 May '05 - 17:51 
Hi all,
This maybe a bit late, but I have properly fix this now in the latest version. if you have a chance, give it a try and let me know.
 
Fadrian
GeneralCool ComboxmemberSawjai29 Sep '04 - 17:49 
Thanks Fadrian
-Samson
Generalconst int DROPDOWNWIDTH = 18;member_DD_MORPH_4 Aug '04 - 22:13 
Why you use this const? Is there any way to check width of this 'dropdown button'? I have bigger fonts than normal - and there is small part of normal 3D button
GeneralRe: const int DROPDOWNWIDTH = 18;member_DD_MORPH_4 Aug '04 - 23:07 
I used:
int DROPDOWNWIDTH = ctrl.Height-2;
in my situation it helped - work for normal and large fonts.
GeneralRe: const int DROPDOWNWIDTH = 18;memberFadrian Sudaman5 Aug '04 - 13:50 
Hi,
I haven't tried the approach you suggested, but it is great knowing that it works for you.
 
When it come to adjusting for drawing, I tempt to use the graphic resolution, so say "g" is the Graphics object, I would do something like this:
int iWidth = (int) ((g.DpiX/96.0f) * DROPDOWNWIDTH);
 
I use 96 there because that is the pixel size for standard font display. I also make it a float so that we keep the precision when performing the math.
 
Cheers,
Fadrian
QuestionHi Fadrian, can I use flat combo in VB.Net?memberKingVikram24 May '04 - 20:41 
Hi Fadrian, I am trying to use your Flat combo in VB.net. But I am very confused, can u help me out?
If you can give me any code (as simple as this one). I will be very thankful to u...
 
KingVikram

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 18 May 2005
Article Copyright 2004 by Fadrian Sudaman
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid