1

Goal is to create DateTimePicker similar to the screen shot of this question.

First attempt overriding OnPaint:

public class MyDateTimePicker : DateTimePicker
{
    private Image _image;

    public MyDateTimePicker() : base()
    {
        SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw |
            ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }

    [Browsable(true)]
    public override Color BackColor
    {
        get
        {
            return base.BackColor;
        }
        set
        {
            base.BackColor = value;
        }
    }

    [Category("Appearance")]
    public Color BorderColor { get; set; } = Color.Black;

    [Category("Appearance")]
    public Color TextColor { get; set; } = Color.Black;

    [Category("Appearance")]
    public Image Image
    {
        get
        {
            return _image;
        }
        set
        {
            _image = value;
            Invalidate();
        }
    }

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

        // Fill the Background
        e.Graphics.FillRectangle(new SolidBrush(this.BackColor), 0, 0, ClientRectangle.Width, ClientRectangle.Height);

        // Draw DateTime text
        e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), 5, 2);

        // Draw Icon
        if (_image != null)
        {
            Rectangle im_rect = new Rectangle(ClientRectangle.Width - 20, 2, ClientRectangle.Height - 4, ClientRectangle.Height - 4);
            e.Graphics.DrawImage(_image, im_rect);
        }

        // Draw Border
        e.Graphics.DrawRectangle(Pens.Black, new Rectangle(0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1));
    }
} 

This solution has the following issues: date fields are not clickable, text artifacts when changing date with arrow keys, narrow clickable area of the button.

Second solution overriding WndProc:

public class MyDateTimePicker : DateTimePicker
{
    private const int WM_PAINT = 0x000F;
    private Color _borderColor = Color.Black;

    public MyDateTimePicker() { }

    [Category("Appearance")]
    public Color BorderColor
    {
        get { return _borderColor; }
        set
        {
            if (_borderColor != value)
            {
                _borderColor = value;
                this.Invalidate();
            }
        }
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_PAINT:
                base.WndProc(ref m);

                using (var g = Graphics.FromHwnd(m.HWnd))
                {
                    var rect = new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
                    g.DrawRectangle(new Pen(this.BorderColor), rect);
                }
                m.Result = IntPtr.Zero;
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }
}

This solution lacks the customization of the button. Maybe anyone knows how to customize button in this way, or how to solve issues of the first solution?

Also if it is possible I would like to change the height of DateTimePicker to match height of ComboBox (currently they differ by 1px).

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Creek Drop
  • 464
  • 3
  • 13
  • See here: [How can I set the DateTimePicker dropdown to select Years or Months only?](https://stackoverflow.com/a/61287097/7444103) the `OnDropDown()` override, where you get the handle of the MonthCalendar Control and the `ShowMonCalToday()`, where you use Send Message to customize its styles. -- You can attach the MonthCalendar to a NativeWindow using its handle and override WndProc to paint its parts. -- In relation to the Button, you have to paint it as you're drawing other parts of the DateTimePicker. See, e.g., Reza Aghaei's answer [here](https://stackoverflow.com/q/65877575/7444103). – Jimi Feb 07 '21 at 18:58
  • Thanks for response @Jimi! Can you advise how to force the original button not to be drawn using WndProc? – Creek Drop Feb 07 '21 at 19:34
  • I checked this: *I would like to change the height of DateTimePicker to match height of ComboBox (currently they differ by 1px).* → I checked this. I don't see any difference, maybe because of using 125% as scaling of my display, also enabling dpiaware in my application using [app.manifest](https://stackoverflow.com/a/33588482/3110834). – Reza Aghaei Feb 08 '21 at 12:21
  • By the way, I uploaded an extended version of my code on GitHub. Feel free to clone/download and customize based on that. – Reza Aghaei Feb 08 '21 at 12:22

1 Answers1

5

You can handle WM_PAINT and draw the border and button yourself. To get the accurate size of the dropdown, send DTM_GETDATETIMEPICKERINFO message.

The width of the dropdown button may vary depending to the size of the control and the space required by the text of the control:

enter image description here

Flat DateTimePicker

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class FlatDateTimePicker : DateTimePicker
{
    public FlatDateTimePicker()
    {
        SetStyle(ControlStyles.ResizeRedraw |
            ControlStyles.OptimizedDoubleBuffer, true);
    }

    private Color borderColor = Color.DeepSkyBlue;
    [DefaultValue(typeof(Color), "RoyalBlue")]
    public Color BorderColor
    {
        get { return borderColor; }
        set
        {
            if (borderColor != value)
            {
                borderColor = value;
                Invalidate();
            }
        }
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == WM_PAINT)
        {
            var info = new DATETIMEPICKERINFO();
            info.cbSize = Marshal.SizeOf(info);
            SendMessage(Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, ref info);
            using (var g = Graphics.FromHwndInternal(Handle))
            {
                var clientRect = new Rectangle(0,0,Width, Height);
                var buttonWidth = info.rcButton.R - info.rcButton.L;
                var dropDownRect = new Rectangle(info.rcButton.L, info.rcButton.T,
                   buttonWidth, clientRect.Height);
                if (RightToLeft == RightToLeft.Yes && RightToLeftLayout == true)
                {
                    dropDownRect.X = clientRect.Width - dropDownRect.Right;
                    dropDownRect.Width += 1;
                }
                var middle = new Point(dropDownRect.Left + dropDownRect.Width / 2,
                    dropDownRect.Top + dropDownRect.Height / 2);
                var arrow = new Point[]
                {
                        new Point(middle.X - 3, middle.Y - 2),
                        new Point(middle.X + 4, middle.Y - 2),
                        new Point(middle.X, middle.Y + 2)
                };

                var borderAndButtonColor = Enabled ? BorderColor : Color.LightGray;
                var arrorColor = BackColor;
                using (var pen = new Pen(borderAndButtonColor))
                    g.DrawRectangle(pen, 0, 0, 
                        clientRect.Width - 1, clientRect.Height - 1);
                using (var brush = new SolidBrush(borderAndButtonColor))
                    g.FillRectangle(brush, dropDownRect);
                g.FillPolygon(Brushes.Black, arrow);
            }
        }
    }
    const int WM_PAINT = 0xF;
    const int DTM_FIRST = 0x1000;
    const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;

    [DllImport("user32.dll")]
    static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
        IntPtr wParam, ref DATETIMEPICKERINFO info);

    [StructLayout(LayoutKind.Sequential)]
    struct RECT
    {
        public int L, T, R, B;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct DATETIMEPICKERINFO
    {
        public int cbSize;
        public RECT rcCheck;
        public int stateCheck;
        public RECT rcButton;
        public int stateButton;
        public IntPtr hwndEdit;
        public IntPtr hwndUD;
        public IntPtr hwndDropDown;
    }
}

Clone or Download Extended version

I have created an extended version of this answer, which supports rendering the up-down button and the checkbox in flat style, also highlighting the arrow on mouse move, something like this:

enter image description here

You can download or close the code:

Related Posts

You may also want to take a look at the following flat style controls:

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Thanks your code works! Can you also advise how to adjust the height of the control without changing font? One more problem - if I reduce button width, old button appears underneath, is there any way to avoid this? – Creek Drop Feb 07 '21 at 20:35
  • No problem :) For changing the calendar size, this is the only solution that I know: [Change month calendar size](https://stackoverflow.com/a/54912594/3110834) – Reza Aghaei Feb 07 '21 at 20:39
  • I want to change height not of the calendar but of the textbox. Setting MinimumSize did the trick.-- Is there any solution regarding changing the button width? – Creek Drop Feb 07 '21 at 21:29
  • I don't see any message regarding to button size here: [Date and Time Picker Messages](https://learn.microsoft.com/en-us/windows/win32/controls/bumper-date-and-time-picker-control-reference-messages?WT.mc_id=DT-MVP-5003235) – Reza Aghaei Feb 07 '21 at 21:33