自定义 DateTimePicker 的边框和按钮

Customizing Border and Button of the DateTimePicker

目标是创建类似于 屏幕截图的 DateTimePicker。

第一次尝试覆盖 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));
    }
} 

此解决方案存在以下问题:日期字段不可点击,使用箭头键更改日期时出现文本伪影,按钮的可点击区域变窄。

覆盖 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;
        }
    }
}

此解决方案缺少按钮的自定义。也许有人知道如何通过这种方式自定义按钮,或者如何解决第一个解决方案的问题?

此外,如果可能的话,我想更改 DateTimePicker 的高度以匹配 ComboBox 的高度(目前它们相差 1px)。

您可以处理 WM_PAINT and draw the border and button yourself. To get the accurate size of the dropdown, send DTM_GETDATETIMEPICKERINFO 条消息。

下拉按钮的宽度可能会根据控件的大小和控件文本要求的space而有所不同:

平面日期时间选择器

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;
    }
}

克隆或下载扩展版本

我创建了这个答案的扩展版本,它支持以平面样式呈现上下按钮和复选框,还突出显示鼠标移动时的箭头,如下所示:

您可以下载或关闭代码:

相关帖子

您可能还想看看以下平面样式控件:

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