Customizing Border and Button of the DateTimePicker

白昼怎懂夜的黑 提交于 2021-02-11 06:52:08

问题


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).


回答1:


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:

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:

You can download or close the code:

  • r-aghaei/FlatDateTimePickerExample
  • master.zip

Related Posts

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

  • Flat TextBox - Change border color of TextBox
  • Flat ComboBox - Change border color and dropdown button of ComboBox
  • Flat NumericUpDown - Change border color and spin buttons of NumericUpDown


来源:https://stackoverflow.com/questions/66088379/customizing-border-and-button-of-the-datetimepicker

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!