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

DateTimePicker appears flat

By , 17 May 2005
 

Sample Image - FlatDateTimePicker.jpg

Introduction

All the while, we have been using Lumisoft flat controls for our project. However the date picker control supplied does not have support for the time element. I decided to use the standard .NET DateTimePicker control, but the 3D appearance of the control looked really awful in the middle of all the flat controls. After much searching and reading, I couldn't find a readily usable class/control for the purpose that I wanted, and some suggested codes were rather complicated. Based on these information and suggestions by other authors, I came out with this class which is simple and yet functional. It does exactly what I want in a very simple way as well, by using the ControlPaint class supplied in the .NET framework.

Using the Class

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

So instead of doing this:

DateTimePicker dtp = new DateTimePicker();

You will do:

DateTimePicker dtp = new FlatDateTimePicker();

// OR

FlatDateTimerPicker dtp = new FlatDateTimePicker();

That is how simple it is :)

Code Explanation

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

Note that in this method, we need to acquire the actual window DC for the control using WinAPI GetWindowDC and release it later by using ReleaseDC. One main reason of using this over simply creating the Graphics object is because we need the entire DC of the control (bound rectangle) not just the client rectangle, therefore we can paint the border.

  protected override void WndProc(ref Message m)
  {
   IntPtr hDC = GetWindowDC(m.HWnd);
   Graphics gdc = Graphics.FromHdc(hDC);
   switch (m.Msg)
   {
    case WM_NC_PAINT:
     SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
     SendPrintClientMsg(); 
     SendMessage(this.Handle, WM_PAINT, IntPtr.Zero, 0);
     OverrideControlBorder(gdc);

     m.Result = (IntPtr)1; // indicate msg has been processed
     break;     
    case WM_PAINT: base.WndProc(ref m);
     OverrideControlBorder(gdc);
     OverrideDropDown(gdc);
     break;     
      case  WM_NC_HITTEST:
     base.WndProc(ref m);
     if (DroppedDown)
      this.Invalidate(this.ClientRectangle, false);
     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 do the border painting myself without calling the base class method, because the base class will try to draw this with a 3D border. I also clear the area prior to the border drawing otherwise it may cause some potential redraw problem when a control/dialog overlays on top such as when a combo box pops up.

WM_PAINT message is received when the control needs to paint portions of its windows. Here we just pass the message to the base class and let the control take care of the drawing (including the display value), but after, we will override the border and the drop down button to ensure that they are flat. 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.

WM_NCHITTEST message is received when you move your mouse around within the control. Many have attempted to write flat controls but were unable to draw the pulldown arrow in flat when the popup appears, because of not trapping this message properly. Every time the pulldown is clicked, the CloseUp or DropDown is called and followed by a redraw which will paint the 3D arrow onto the control; therefore to achieve the flat look, we have to redraw it here. Simply calling Invalidate in this case block will cause flickering, so we will only want to redraw it when the Calendar control actually pops up.

Change Logs

  • 13 April 2004
    • Posted.
  • 22 April 2004
    • Fixed the missing text problem reported.
  • 18 May 2005
    • Fixed flickering problem.
    • DROPDOWNWIDTH value is dynamic now based on the system setting, hence will work well in different screen resolutions.
    • Fixed drop down button redraw problem when mouse is not moved.

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   
Questionflat drop-down arrow button?memberKaine2 Aug '10 - 0:46 
Hi,
 
Thanks for the great insight into the DateTimePicker control, it worked fabulously. However, the drop-down arrow button (same as combo-box drop-down button) doesn't appear to be flat styled. Is there a recent update that overrides this as well?
 
Cheers,
 

Kaine
 
P.S. - The button appears flat at design time but 3D during runtime. K
GeneralDropdown IconmemberDewayne Dodd6 Apr '09 - 15:02 
First, I wanted to say thanks for a great control. I have a problem and I am not sure exactly how to fix it. The flat DateTimePicker displays a calendar icon next to the dropdown arrow and on my Vista machine the calendar looking icon is slightly cut off by the dropdown arrow. Is there a way to make this icon move further left or disappear completely. Any help you can give me on this would be greatly appreciated.
Generalfine control with one questionmemberthirstyDev2 Dec '08 - 19:56 
I must say this is fine control.
I want to support showupdown property, so up-down box should be flat control.
The code that I change is:
 
private void OverrideDropDown(Graphics g)
 {
            if (!this.ShowUpDown)
            {
                Rectangle rect = new Rectangle(this.Width - DropDownButtonWidth, 0, DropDownButtonWidth, this.Height);
                ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
            }
            else
            {
                Rectangle rect = new Rectangle(this.Width - DropDownButtonWidth,
                    0, DropDownButtonWidth, this.Height/2);
                ControlPaint.DrawScrollButton(g, rect, ScrollButton.Up, ButtonState.Flat);
                rect.Y += this.Height / 2;
                ControlPaint.DrawScrollButton(g, rect, ScrollButton.Down, ButtonState.Flat);
         
            }
        }
 
But it does not display, flat up-down button continuously. I want to understand the reason for same.
Whether this is solvable or not, that is secondary thing.
GeneralRe: fine control with one questionmemberFadrian Sudaman3 Dec '08 - 2:10 
I had a quick look and it seems like when ShowUpDown is true, this control has a child control with the name "msctls_updown32" (I found it through Spy++). It looks like the child control repaint itself and hence caused the inconsitency with the float display you did above. I'm not sure how you can intercept the child window paint message, but if you can that will probably be the way to get this working nicely.
GeneralGood work!! Just a small correction on PInvoke declarationmemberCaio Proiete11 Jul '08 - 5:27 
Hi Fadrian, good job!
 
I would just add a small change on the SendMessage API declaration, to use the correct one and avoid the PInvoke exception (unbalanced stack):
 
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hwnd, int Msg, IntPtr wParam, IntPtr lParam);
 
Then, when calling, just pass IntPtr.Zero, instead of 0:
 
...
SendMessage(this.Handle, WM_ERASEBKGND, hDC, IntPtr.Zero);
 
...
SendMessage(this.Handle, WM_PRINTCLIENT, ptrClientDC, IntPtr.Zero);
 

Cheers,
 
Caio Proiete WTF | :WTF:
MCT, MCSD, MCDBA, MCAD .NET, MCSD .NET

GeneralRed Rectanglememberdiazpablomiguel23 Apr '08 - 5:22 
I would like to change the color of the red rectangle which highlight the selected date and which appears at the left of Today.
 
There is also another limitation I would like to work around. When you change the CalendarTitleBackColor the font of DaysOfWeek also change its color, I want to hack this so that I can assign the color for DaysOfWeek on another property. Do you guys have any clue about how to do this?
 
Thanks in advance,
GeneralVB.net versionmemberMSP129 May '07 - 22:20 
Here is the equivalent VB version.
 
Imports System
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Runtime.InteropServices
 
Public class FlatDateTimePicker
 
  Inherits DateTimePicker
 
	Private Class ComboInfoHelper
	
		Private Declare Function GetComboBoxInfo Lib "user32.dll" (hwndCombo As Int32, ByRef info As ComboBoxInfo) As Boolean
 
		Private Structure RECT 
			public Left    As Int32
			public Top     As Int32
			public Right   As Int32
			public Bottom  As Int32
		End Structure
 
    Private Structure ComboBoxInfo
      Public cbSize      As Int32
      Public rcItem      As RECT
      Public rcButton    As RECT
      Public stateButton As IntPtr
      Public hwndCombo   As IntPtr
      Public hwndEdit    As IntPtr
      Public hwndList    As IntPtr
    End Structure
 
    Public Shared Function GetComboDropDownWidth() As Int32
			Dim cb As ComboBox = new ComboBox()
			Dim width As Int32 = GetComboDropDownWidth(cb.Handle)
			cb.Dispose()
      Return Width
		End Function
		
    Public Shared Function GetComboDropDownWidth(ByVal handle As IntPtr) As Int32
      Dim cbi As ComboBoxInfo = New ComboBoxInfo()
      cbi.cbSize = Marshal.SizeOf(cbi)
			GetComboBoxInfo(handle.ToInt32, cbi)
      Dim Width As Int32 = cbi.rcButton.Right - cbi.rcButton.Left
      Return Width
    End Function
	
	End Class
	
  Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA"(hwnd As Int32, wMsg As Int32, wParam As Int32, lParam As Int32) As Int32
  
  Private Declare Function GetWindowDC Lib "user32.dll"(hwnd As Int32) As IntPtr
  
  Private Declare Function ReleaseDC   Lib "user32.dll"(hwnd As Int32, hDC As Int32) As Int32
 
  Const WM_ERASEBKGND                As Int32   = &H14
  Const WM_PAINT                     As Int32   = &HF
  Const WM_NC_HITTEST                As Int32   = &H84
  Const WM_NC_PAINT                  As Int32   = &H85
  Const WM_PRINTCLIENT               As Int32   = &H318
  Const WM_SETCURSOR                 As Int32   = &H20
 

  Private BorderPen                  As Pen     = New Pen(Color.Black, 2)
  Private BorderPenControl           As Pen     = New Pen(SystemColors.ControlDark, 2)
  Private DroppedDown                As Boolean = False
  Private InvalidateSince            As Int32   = 0
  Private Shared DropDownButtonWidth As Int32   = 17
 
	Shared Sub New()
		'2 pixel extra is for the 3D border around the pulldown button on the left and right
		DropDownButtonWidth = ComboInfoHelper.GetComboDropDownWidth() + 2	
	End Sub
 
  Public Sub New()
    MyBase.New()
    Me.SetStyle(ControlStyles.DoubleBuffer, True)
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
  End Sub
 
  Protected Overrides Function IsInputKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean
    If keyData = (Keys.Tab Or Keys.Shift) Then
      Return True
    ElseIf keyData = Keys.Tab Then
      Return True
    Else
      Return MyBase.IsInputKey(keyData)
    End If
  End Function
	
  Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)
    MyBase.OnValueChanged(eventargs)
    Me.Invalidate()
  End Sub
 
  Protected Overrides Sub WndProc(ByRef m As Message)
    Dim hDC As IntPtr = IntPtr.Zero
    Dim gdc As Graphics = Nothing
    Select Case m.Msg
      Case WM_NC_PAINT
        hDC = GetWindowDC(m.HWnd.ToInt32)
        gdc = Graphics.FromHdc(hDC)
        SendMessage(Me.Handle.ToInt32, WM_ERASEBKGND, hDC.ToInt32, 0)
        SendPrintClientMsg()
        SendMessage(Me.Handle.ToInt32, WM_PAINT, IntPtr.Zero.ToInt32, 0)
        OverrideControlBorder(gdc)
        m.Result = New IntPtr(1)  'Indicate msg has been processed
        ReleaseDC(m.HWnd.ToInt32, hDC.ToInt32)
        gdc.Dispose()
      Case WM_PAINT
        MyBase.WndProc(m)
        hDC = GetWindowDC(m.HWnd.ToInt32)
        gdc = Graphics.FromHdc(hDC)
        OverrideDropDown(gdc)
        OverrideControlBorder(gdc)
        ReleaseDC(m.HWnd.ToInt32, hDC.ToInt32)
        gdc.Dispose()
      Case WM_SETCURSOR
        MyBase.WndProc(m)
        ' The value 3 is discovered by trial on error, and cover all kinds of scenarios
        ' InvalidateSince < 2 wil have problem if the control is not in focus and dropdown is clicked
        If DroppedDown And (InvalidateSince < 3) Then
          Invalidate()
          InvalidateSince = InvalidateSince + 1
        End If
      Case Else
        MyBase.WndProc(m)
    End Select
  End Sub
 
  Private Sub SendPrintClientMsg()
    'We send this message for the control to redraw the client area
    Dim gClient As Graphics = Me.CreateGraphics()
    Dim ptrClientDC As IntPtr = gClient.GetHdc()
    SendMessage(Me.Handle.ToInt32, WM_PRINTCLIENT, ptrClientDC.ToInt32, 0)
    gClient.ReleaseHdc(ptrClientDC)
    gClient.Dispose()
  End Sub
 
  Private Sub OverrideDropDown(ByVal g As Graphics)
    If Not Me.ShowUpDown Then
      Dim rect As Rectangle = New Rectangle(Me.Width - DropDownButtonWidth, 0, DropDownButtonWidth, Me.Height)
      ControlPaint.DrawComboButton(g, rect, ButtonState.Flat)
    End If
  End Sub
 
  Private Sub OverrideControlBorder(ByVal g As Graphics)
    If Not Me.Focused Or Not Me.Enabled Then
      g.DrawRectangle(BorderPenControl, New Rectangle(0, 0, Me.Width, Me.Height))
    Else
      g.DrawRectangle(BorderPen, New Rectangle(0, 0, Me.Width, Me.Height))
    End If
  End Sub
 
  Protected Overrides Sub OnDropDown(ByVal eventargs As EventArgs)
    InvalidateSince = 0
    DroppedDown = True
    MyBase.OnDropDown(eventargs)
  End Sub
	
  Protected Overrides Sub OnCloseUp(ByVal eventargs As EventArgs)
    DroppedDown = False
    MyBase.OnCloseUp(eventargs)
  End Sub
 
  Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
    MyBase.OnLostFocus(e)
    Me.Invalidate()
  End Sub
 
  Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
    MyBase.OnGotFocus(e)
    Me.Invalidate()
  End Sub
		
  Protected Overrides Sub OnResize(ByVal e As EventArgs)
    MyBase.OnResize(e)
    Me.Invalidate()
  End Sub
 
End Class

 
Malcolm Powell
GeneralRe: VB.net versionmemberMSP129 May '07 - 22:35 
I forgot to remove the following function before posting the VB version.
 
 Protected Overrides Function IsInputKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean
    If keyData = (Keys.Tab Or Keys.Shift) Then
      Return True
    ElseIf keyData = Keys.Tab Then
      Return True
    Else
      Return MyBase.IsInputKey(keyData)
    End If
  End Function
 
This was added because I needed special Tab key handling. You should remove this function if you want the default Tab key behaviour.
 
Malcolm Powell
GeneralRe: VB.net versionmemberS. Töpfer22 Feb '10 - 1:20 
Good work. That's exactly what I needed. Of course my thanks go also to Fadrian!
GeneralDateTimePicker with Popup FlatStyle supportmembervachaun4 May '07 - 10:30 
This is a work in progress, but can be used as it stands right now. This is an ExtendedDateTimePicker class which inherits from Windows.Systems.Forms.DateTimePicker and so can be used as a direct replacement.
 
**This control does not behave correctly as far as drawing in design mode, so if you can assist in that area it would be greatly appreciated....
 
#region ExtendedDateTimePicker
///
/// Class extends the datetimepicker with popup style border
///

public class ExtendedDateTimePicker : System.Windows.Forms.DateTimePicker
{
#region Enums
public enum FLATSTYLE_STYLES
{
None = 0,
Popup = 1,
Standard = 2,
System = 3
}
#endregion
 
#region Property Variables
public FLATSTYLE_STYLES _flatStyle = FLATSTYLE_STYLES.Standard;
#endregion
 
#region Properties
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[Category("Appearance")]
[Description("Determines the display of the control.")]
public FLATSTYLE_STYLES FlatStyle
{
get { return _flatStyle; }
set
{
_flatStyle = value;
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
_dropButton = new DropDownButton(this);
_dropButton.Width = _buttonWidth + 2;
_dropButton.Height = this.Height - 2;
_dropButton.Top = 0;
this.Controls.Add(_dropButton);
if (this.RightToLeft == RightToLeft.No || !this.RightToLeftLayout)
_dropButton.Left = this.Width - (_buttonWidth + 2);
}
else
{
_dropButton = null;
}
this.Invalidate(); }
}
#endregion
 
///
/// Required by designer
///

private System.ComponentModel.Container components = null;
 
#region Member variables
protected int _buttonWidth = 17;
protected DropDownButton _dropButton = null;
protected bool _dropped = false;
#endregion
 
#region Contructors
///
/// Constructor for datetimepicker
///

public ExtendedDateTimePicker()
{
InitializeComponent();
 
// Add 2 pixels for the 3D border
_buttonWidth = GetComboDropDownWidth();
}
 
///
/// Clean up any resources being used.
///

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
 
#region Designer Generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///

private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
 
#region Overridden Control Functions
///
/// Repaint the control border and button when the mouse enteres
///

///
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
Win32.SendMessage(this.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
_dropButton.Invalidate();
}
}
 
///
/// Repaint the border and button when the mouse leaves
///

///
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
Win32.SendMessage(this.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
_dropButton.Invalidate();
}
}
 
///
/// Redraw the control button and button when the control gets focus
///

///
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
Win32.SendMessage(this.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
_dropButton.Invalidate();
}
}
 
///
/// Redraw border and button when the control loses focus
///

///
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
Win32.SendMessage(this.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
_dropButton.Invalidate();
}
}
 
///
/// Move the drop down button on resize.
///

///
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
if (this.RightToLeft == RightToLeft.No || !this.RightToLeftLayout)
_dropButton.Left = this.Width - (_buttonWidth + 2);
 
Win32.SendMessage(this.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
}
}
 
///
/// Override WndProc to do some drawing in the control
///

///
protected override void WndProc(ref Message m)
{
IntPtr hDC = IntPtr.Zero;
Graphics gDC = null;
 
//base.WndProc(ref m);
 
if (_flatStyle == FLATSTYLE_STYLES.Popup)
{
switch (m.Msg)
{
case Win32.WM_NCCALCSIZE:
if (m.WParam == IntPtr.Zero)
{
Win32.NCCALCSIZE_PARAMS csp;
 
csp = (Win32.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam,
typeof(Win32.NCCALCSIZE_PARAMS));
 
csp.rgrc0.Top += 1;
csp.rgrc0.Bottom -= 1;
csp.rgrc0.Left += 1;
csp.rgrc0.Right -= 1;
 
Marshal.StructureToPtr(csp, m.LParam, false);
}
else if (m.WParam == new IntPtr(1))
{
Win32.NCCALCSIZE_PARAMS csp;
 
csp = (Win32.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam,
typeof(Win32.NCCALCSIZE_PARAMS));
 
csp.rgrc0.Top += 1;
csp.rgrc0.Bottom -= 1;
csp.rgrc0.Left += 1;
csp.rgrc0.Right -= 1;
Marshal.StructureToPtr(csp, m.LParam, false);
}
break;
case Win32.WM_NCPAINT:
hDC = Win32.GetWindowDC(m.HWnd);
gDC = Graphics.FromHdc(hDC);
Win32.SendMessage(m.HWnd, Win32.WM_ERASEBKGND, hDC, 0);
OverrideControlBorder(gDC);
m.Result = (IntPtr)0; // we handled the message
Win32.ReleaseDC(m.HWnd, hDC);
gDC.Dispose();
break;
default:
base.WndProc(ref m);
break;
}
}
else
{
base.WndProc(ref m);
}
}
#endregion
 
#region Utility Functions
///
/// Determines if the mouse is within the control.
///

[Browsable(false)]
private bool IsMouseInControl
{
get
{
if (this.DesignMode)
{
return false;
}
 
Point mPos = Control.MousePosition;
bool retVal = this.ClientRectangle.Contains(this.PointToClient(mPos));
return retVal;
}
}
 
///
/// This draws the controls border as required.
///

///
private void OverrideControlBorder(Graphics g)
{
bool allowHot = (this.Enabled && !this.DesignMode) && !(this.IsMouseInControl && Control.MouseButtons == MouseButtons.Left && !this.ContainsFocus);
bool hot = (this.ContainsFocus || this.IsMouseInControl) && allowHot;
Pen border = null;
 
Rectangle rect = new Rectangle(0, 0,
this.ClientRectangle.Width + 1, this.ClientRectangle.Height + 1);
 
if (allowHot && hot)
{
//border = new Pen(SystemColors.HotTrack, 1);
border = new Pen(SystemColors.GrayText, 1);
}
else
{
border = new Pen(Color.White, 1);
}
 
g.DrawRectangle(border, rect);
border.Dispose();
}
 
///
/// Overrides and draws the combobox drop down button
///

///
private void OverrideDropDown(Graphics g)
{
bool allowHot = (this.Enabled && !this.DesignMode) && !(this.IsMouseInControl && Control.MouseButtons == MouseButtons.Left && !this.ContainsFocus);
bool hot = (this.ContainsFocus || this.IsMouseInControl) && allowHot;
Pen border = null;
Brush button = new SolidBrush(SystemColors.Control);
 
border = (allowHot && hot) ? new Pen(SystemColors.GrayText, 1) : new Pen(Color.White, 1);
//button = (allowHot && hot) ? Brushes.Gray : Brushes.White;
 
if (!this.ShowUpDown)
{
Rectangle rect = new Rectangle(this.Width - _buttonWidth + 1, 0, _buttonWidth - 1, this.Height - 1);
g.DrawRectangle(border, rect);
rect.Inflate(-1, -1);
rect.Height += 1;
g.FillRectangle(button, rect);
}
else
{
Rectangle rect = new Rectangle(this.Width - _buttonWidth + 1, 0, _buttonWidth - 1, this.Height - 1);
g.DrawRectangle(border, rect);
rect.Inflate(-1, -1);
rect.Height += 1;
g.FillRectangle(button, rect);
}
border.Dispose();
button.Dispose();
}
 
///
/// Gets the width of the drop down combobox button
///

///
protected int GetComboDropDownWidth()
{
ComboBox cbo = new ComboBox();
Win32.COMBOBOXINFO cbi = new Win32.COMBOBOXINFO();
cbi.cbSize = Marshal.SizeOf(cbi);
Win32.GetComboBoxInfo(cbo.Handle, ref cbi);
cbo.Dispose();
return cbi.rcButton.Right - cbi.rcButton.Left;
}
#endregion
 
#region DropDownButton Private Class
protected class DropDownButton : System.Windows.Forms.Button
{
#region Windows API
[DllImport("user32")]
protected static extern IntPtr GetWindowDC(IntPtr hWnd);
 
[DllImport("user32")]
protected static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
#endregion
 
///
/// Required by designer
///

private System.ComponentModel.Container components = null;
 
#region Member Variables
private ExtendedDateTimePicker _owner = null;
#endregion

#region Constructor
///
/// Construct the control
///

public DropDownButton(
ExtendedDateTimePicker owner)
{
InitializeComponent();
 
// Make this an ownerdrawn control
this._owner = owner;
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.Selectable, false);
this.FlatStyle = FlatStyle.Popup;
}
 
///
/// Clean up any resources being used.
///

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
 
#region Designer Generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///

private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
 
#region Overrides
///
/// Override the paint procedure to create the flat control
///

///
protected override void OnPaint(PaintEventArgs e)
{
DrawControlBorder(e.Graphics);
DrawButton(e.Graphics);
DrawGlyph(e.Graphics);
}
 
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
 
Win32.SendMessage(_owner.Handle, Win32.WM_NCPAINT, (IntPtr)1, 0);
}
 
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
 
Win32.SendMessage(_owner.Handle, Win32.WM_MOUSELEAVE, IntPtr.Zero, 0);
}
 
protected override void OnMouseDown(MouseEventArgs mevent)
{
base.OnMouseDown(mevent);
 
if (mevent.Button == MouseButtons.Left)
{
int x = _owner.Width - 10;
int y = _owner.Height / 2;
int lParam = x + y * 0x00010000;
Win32.SendMessage(_owner.Handle, Win32.WM_LBUTTONDOWN, (IntPtr)1, lParam);
}
}
#endregion
 
#region Drawing Functions
///
/// Draw the controls border in the appropriate style
///

///
protected virtual void DrawControlBorder(Graphics g)
{
/*bool allowHot = (this.Enabled && !this.DesignMode) &&
!(this.IsMouseInControl && Control.MouseButtons == MouseButtons.Left
&& !this.ContainsFocus);*/
bool allowHot = (this.Enabled && !this.DesignMode) &&
!(this.IsMouseInControl && this.ContainsFocus && _owner.ContainsFocus);
bool hot = (this.ContainsFocus || this.IsMouseInControl || _owner.ContainsFocus) && allowHot;
 
Rectangle rect = new Rectangle(0, 0,
this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
 
Pen border = (hot) ? new Pen(SystemColors.GrayText, 1) : new Pen(Color.White, 1);
 
RightToLeft rtl = _owner.RightToLeft;
bool rtll = _owner.RightToLeftLayout;
 
Point leftTop = new Point(rect.Left, rect.Top);
Point leftBottom = new Point(rect.Left, rect.Bottom);
Point topLeft = new Point(rect.Left + 1, rect.Top);
Point topRight = new Point(rect.Right - 1, rect.Top);
Point rightTop = new Point(rect.Right, rect.Top);
Point rightBottom = new Point(rect.Right, rect.Bottom);
Point bottomLeft = new Point(rect.Left + 1, rect.Bottom);
Point bottomRight = new Point(rect.Right - 1, rect.Bottom);
 
Pen sides = new Pen(SystemColors.Control, 1);
 
g.DrawLine((rtl == RightToLeft.Yes || rtll) ? sides : border, leftTop, leftBottom);
g.DrawLine(sides, topLeft, topRight);
g.DrawLine((rtl == RightToLeft.Yes || rtll) ? border : sides, rightTop, rightBottom);
g.DrawLine(sides, bottomRight, bottomLeft);
 
// g.DrawRectangle(border, rect);
border.Dispose();
sides.Dispose();
}
 
///
/// Draw the client area of the control with the appropriate style
///

///
protected virtual void DrawButton(Graphics g)
{
bool allowHot = (this.Enabled && !this.DesignMode) &&
!(this.IsMouseInControl && Control.MouseButtons == MouseButtons.Left
&& !this.ContainsFocus);
bool hot = (this.ContainsFocus || this.IsMouseInControl) && allowHot;
 
Rectangle rect = new Rectangle(1, 1,
this.ClientRectangle.Width - 2, this.ClientRectangle.Height - 2);
 
Brush filler = (hot) ? new SolidBrush(SystemColors.Control) :
new SolidBrush(SystemColors.Control);
 
g.FillRectangle(filler, rect);
filler.Dispose();
}
 
///
/// Draw the combobutton glyph
///

///
protected virtual void DrawGlyph(Graphics g)
{
RightToLeft rtl = _owner.RightToLeft;
bool rtll = _owner.RightToLeftLayout;

int xC = this.ClientRectangle.Left + ((this.ClientRectangle.Width / 2) - 1);
int yC = this.ClientRectangle.Top + ((this.ClientRectangle.Height / 2) - 1);
 
Pen pen = new Pen(SystemColors.ControlText, 1);
 
g.DrawLine(pen, xC - 2, yC - 1, xC + 2, yC - 1);
g.DrawLine(pen, xC - 1, yC, xC + 1, yC);
g.DrawLine(pen, xC, yC - 1, xC, yC + 1);
 
pen.Dispose();
}
#endregion
 
#region Utilities
///
/// Determines if the mouse is within the control.
///

[Browsable(false)]
private bool IsMouseInControl
{
get
{
if (this.DesignMode)
{
return false;
}
 
Point mPos = Control.MousePosition;
bool retVal = this.ClientRectangle.Contains(this.PointToClient(mPos)) ||
_owner.ClientRectangle.Contains(_owner.PointToClient(mPos));
return retVal;
}
}
#endregion
}
#endregion
}
#endregion

 
ToDo:
1. Make it draw properly in designer
2. Support the blue highlighting with the blue button when mousing over like VS2005 comboboxes
3. Figure out the difference between System and Standard drawing styles in other controls to implement here
4. Implement None flatstyle
5. Make RightToLeft adjustments work correctly (something is screwy there with the button as it doesn't draw as expected);
 

How it works:
 
Since pre-Windows Vista, there was no way to retrieve the internal structure of the datetimepicker control, I simply created an internal class to create and draw a button as a child of the date time picker, and simple moved my button over the datetimepicker's button. The button is only created when the control's flatstyle is set to Popup at this time. All other styles simple are the default datetimepicker.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 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