自定义 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
目标是创建类似于
第一次尝试覆盖 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