自定义 DateTimePicker 的边框和按钮

Posted

技术标签:

【中文标题】自定义 DateTimePicker 的边框和按钮【英文标题】:Customizing Border and Button of the DateTimePicker 【发布时间】:2021-02-07 13:16:41 【问题描述】:

目标是创建类似于this question 的屏幕截图的 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)。

【问题讨论】:

参见此处:How can I set the DateTimePicker dropdown to select Years or Months only? OnDropDown() 覆盖,您可以在其中获得 MonthCalendar 控件的句柄和 ShowMonCalToday(),您可以在其中使用发送消息自定义其样式。 -- 您可以使用其句柄将 MonthCalendar 附加到 NativeWindow 并覆盖 WndProc 以绘制其部分。 -- 关于按钮,您必须在绘制 DateTimePicker 的其他部分时绘制它。参见,例如,Reza Aghaei 的回答 here。 感谢@Jimi 的回复!您能否建议如何使用 WndProc 强制不绘制原始按钮? 我检查了这个:我想改变 DateTimePicker 的高度以匹配 ComboBox 的高度(目前它们相差 1px)。 → 我检查了这个。我没有看到任何区别,可能是因为使用 125% 作为显示的缩放比例,还使用 ​​app.manifest 在我的应用程序中启用了 dpiaware。 顺便说一下,我在 GitHub 上上传了我的代码的扩展版本。随意克隆/下载并基于此自定义。 【参考方案1】:

您可以自己处理WM_PAINT并绘制边框和按钮。要获得下拉菜单的准确大小,请发送DTM_GETDATETIMEPICKERINFO 消息。

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

平面日期时间选择器

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;
    

克隆或下载扩展版

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

您可以下载或关闭代码:

r-aghaei/FlatDateTimePickerExample master.zip

相关帖子

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

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

【讨论】:

感谢您的代码有效!您还可以建议如何在不更改字体的情况下调整控件的高度吗?还有一个问题——如果我减小按钮宽度,下面会出现旧按钮,有什么办法可以避免这种情况吗? 没问题 :) 对于更改日历大小,这是我知道的唯一解决方案:Change month calendar size 我想改变的不是日历的高度,而是文本框的高度。设置 MinimumSize 就可以了。-- 有没有关于改变按钮宽度的解决方案? 我在这里没有看到任何关于按钮大小的消息:Date and Time Picker Messages

以上是关于自定义 DateTimePicker 的边框和按钮的主要内容,如果未能解决你的问题,请参考以下文章

iOS:自定义按钮(UIButton 的子类) - 无法更改边框属性

如何删除自定义按钮上的细边框?

jQuery DateTimePicker 日期和时间插件

自定义 UIButton 类 - 渐变离开按钮边框

为按钮添加边框和阴影

删除以编程方式创建的自定义按钮的边框