Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
840 views
in Technique[技术] by (71.8m points)

c# - Customizing Border and Button of the DateTimePicker

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...