前面的几篇文章中介绍了一些自定义控件开发过程中的基本 这一篇主要是针对前面的两篇做一个案例 就做一个Metro风格的Button吧:
从左到右风别是控件的 默认状态-鼠标悬浮-鼠标点下-获得焦点及ALT点下-禁用
代码很简单 一共也就几十行:
(注意由于为了代码简洁 所以里面颜色什么的 我直接定死的 因为要不然一会代码贴上来 篇幅就太长了 如果你自己要写的话 还是尽量把那些定义成属性 让用户自己选择)
首先来完成基本的绘制:
protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; sf.HotkeyPrefix = this.ShowKeyboardCues ? HotkeyPrefix.Show : HotkeyPrefix.Hide; g.FillRectangle(Brushes.Black, this.ClientRectangle); if (this.ShowFocusCues && this.ContainsFocus) { //绘制焦点框 using (Pen p = new Pen(Color.Gray)) { p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; g.DrawRectangle(p, 2, 2, this.Width - 5, this.Height - 5); } } g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, sf); base.OnPaint(e); }这样程序一运行起来 就会看到一个黑色的按钮和白色的文字 虽然上面有写绘制焦点框的代码 但是当控件得到或者失去焦点的时候 并不一定就立马有效果 因为没有谁去通知它重绘 所以还得绑定Got/LostFocus事件:
public MetroBtn() { this.GotFocus += (s, e) => this.Invalidate(); this.LostFocus += (s, e) => this.Invalidate(); }
这样一来 基本的绘制就差不多了 现在还需要鼠标移动上去和点击下去的时候的效果
首先来看鼠标移动上去的时候的效果 要怎么样才能知道鼠标放到控件或者离开控件了?MouseEnter和MouseLeave事件就可以知道了 但是还需要一个标志来确定 当前是否鼠标在控件上 好在Paint的时候进行判断 然后做处理 然后我声明了一个变量来表示m_bMouseOn(名字而已 自己取名)然后绘制的时候就可以这样判断:
if (m_bMouseOn) { //如果鼠标在控件上 g.FillRectangle(Brushes.SlateBlue, this.ClientRectangle); } else { //否则依然黑色背景 g.FillRectangle(Brushes.Black, this.ClientRectangle); }绘制中的判断处理完了 不过还得处理一下MouseEnter/MouseLeave去修改m_bMouseOn这个标识 并且通知重绘:
protected override void OnMouseEnter(EventArgs e) { m_bMouseOn = true; this.Invalidate(); this.Focus(); //随你切换不切换焦点Focus()会自动触发paint的 所一有这行上面一行可以不要 base.OnMouseEnter(e); } protected override void OnMouseLeave(EventArgs e) { m_bMouseOn = false; this.Invalidate(); base.OnMouseLeave(e); }这样鼠标移动上去和离开的时候就能看到效果了 然后是处理鼠标点下的时候 依然声明一个变量标识m_bMouseDown然后去处理MouseDown/MouseUp事件:
protected override void OnMouseDown(MouseEventArgs e) { m_bMouseDown = true; this.Invalidate(); base.OnMouseDown(e); } protected override void OnMouseUp(MouseEventArgs e) { m_bMouseDown = false; this.Invalidate(); base.OnMouseUp(e); }然后依然在Paint里面判断:
if (m_bMouseDown) { //鼠标点下 g.FillRectangle(Brushes.DarkSlateBlue, this.ClientRectangle); } else if (m_bMouseOn) { //注意MouseDown应该优先于MouseOn判断 g.FillRectangle(Brushes.SlateBlue, this.ClientRectangle); } else { g.FillRectangle(Brushes.Black, this.ClientRectangle); }但是这里要注意 应该先处理鼠标点下 因为鼠标点下必然鼠标是在控件上面 如果把第二个判断放上面去 那么就会悲剧
最后就是绘制控件禁用时候的外观了 这里我是单独在Paint外面写的方法去绘制的:
protected virtual void DrawDisable(Graphics g, StringFormat sf) { g.FillRectangle(Brushes.LightGray, this.ClientRectangle); g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, sf); g.TranslateTransform(-1, -1); g.DrawString(this.Text, this.Font, Brushes.Gray, this.ClientRectangle, sf); g.ResetTransform(); }可以看到一来我就直接填充了一个浅灰色的颜色 然后绘制了两次文本 第一次是直接白色 然后把坐标系像左上角偏移了一个像素 然后再用灰色绘制了一次文字 这样看上去就会有一种阴影的效果
然后在Paint里面判断控件是否禁用 如果禁用就调用绘制:
if (!this.Enabled) { //绘制禁用时候外观 this.DrawDisable(g, sf); base.OnPaint(e); return; //如果中途要返回 记得调用base 不然用户绑定的Paint事件不会触发 }绘制到此就差不多了 然后是处理一些焦点的点击和热键点下的时候:
//当点下回车或者空格的时候 默认触发点击事件 protected override void OnKeyDown(KeyEventArgs e) { if (e.Modifiers == Keys.None && (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)) { this.OnClick(new EventArgs()); } base.OnKeyDown(e); } //当热键被按下的时候 默认触发点击事件 protected override bool ProcessMnemonic(char charCode) { if (this.Enabled && this.Visible && IsMnemonic(charCode, this.Text)) { this.OnClick(new EventArgs()); this.Focus();//随你要不 貌似系统的button热键点击的时候 焦点没有切换 return true; } return base.ProcessMnemonic(charCode); }代码到此也就基本结束了 现在我把代码全部贴上来:
public class MetroBtn : Control { public MetroBtn() { this.GotFocus += (s, e) => this.Invalidate(); this.LostFocus += (s, e) => this.Invalidate(); } private bool m_bMouseOn; //鼠标是在控件上 private bool m_bMouseDown; //鼠标是否按下 public override string Text { get { return base.Text; } set { if (base.Text == value) return; base.Text = value; this.Invalidate(); } } protected override void OnMouseEnter(EventArgs e) { m_bMouseOn = true; this.Invalidate(); base.OnMouseEnter(e); } protected override void OnMouseLeave(EventArgs e) { m_bMouseOn = false; this.Invalidate(); base.OnMouseLeave(e); } protected override void OnMouseDown(MouseEventArgs e) { m_bMouseDown = true; this.Invalidate(); this.Focus(); //随你切换不切换焦点Focus()会自动触发paint的 所一有这行上面一行可以不要 base.OnMouseDown(e); } protected override void OnMouseUp(MouseEventArgs e) { m_bMouseDown = false; this.Invalidate(); base.OnMouseUp(e); } //当点下回车或者空格的时候 默认触发点击事件 protected override void OnKeyDown(KeyEventArgs e) { if (e.Modifiers == Keys.None && (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)) { this.OnClick(new EventArgs()); } base.OnKeyDown(e); } //当热键被按下的时候 默认触发点击事件 protected override bool ProcessMnemonic(char charCode) { if (this.Enabled && this.Visible && IsMnemonic(charCode, this.Text)) { this.OnClick(new EventArgs()); this.Focus();//随你要不 貌似系统的button热键点击的时候 焦点没有切换 return true; } return base.ProcessMnemonic(charCode); } protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; sf.HotkeyPrefix = this.ShowKeyboardCues ? HotkeyPrefix.Show : HotkeyPrefix.Hide; if (!this.Enabled) { //绘制禁用时候外观 this.DrawDisable(g, sf); base.OnPaint(e); return; //如果中途要返回 记得调用base 不然用户绑定的Paint事件不会触发 } if (m_bMouseDown) { //如果可用状态根据情况 填充底色 g.FillRectangle(Brushes.DarkSlateBlue, this.ClientRectangle); } else if (m_bMouseOn) { //注意MouseDown应该优先于MouseOn判断 g.FillRectangle(Brushes.SlateBlue, this.ClientRectangle); } else { g.FillRectangle(Brushes.Black, this.ClientRectangle); } if (this.ShowFocusCues && this.ContainsFocus) { //绘制焦点框 using (Pen p = new Pen(Color.Gray)) { p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; g.DrawRectangle(p, 2, 2, this.Width - 5, this.Height - 5); } } g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, sf); base.OnPaint(e); } protected virtual void DrawDisable(Graphics g, StringFormat sf) { g.FillRectangle(Brushes.LightGray, this.ClientRectangle); g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, sf); g.TranslateTransform(-1, -1); g.DrawString(this.Text, this.Font, Brushes.Gray, this.ClientRectangle, sf); g.ResetTransform(); } }代码非常简单 不用做过多解释也能明白是啥意思 为了减少代码量 所以控件里面的那些颜色我直接给死的 建议还是提供属性 让用户去设置 比如鼠标移动上去时候的背景颜色[MouseOnBackColor] 鼠标点击下去的背景颜色[MouseDownBackColor] 当然默认时候的颜色就可以不用提供属性了 直接用控件自己的[BackColor]属性就可以了
如果你有用到控件自己的[BackColor]的话不用写代码去绘制 因为Paint本身就是在控件上绘制的 而[BackColor][BackgroundImage]之类的属性控件是自身处理过了的 所以你没有什么特殊需求 完全没有必要自己再去处理一次 因为你怎么设置这个值 控件背景就是什么颜色 只是这里我强制的控件上填充了一个黑色区域
最后来一个效果图:
不过注意:这里只是为了演示案例 如果真打算从Button角度去做的话 还是建议不要直接继承Control 而继承Button去写 因为如果直接继承Control的话 一些Button具有的特性就没了 虽然平时使用的时候觉得Button就是拿来被人点的 没啥特殊的地方 比如有时候 要设置一个窗体的默认按键的时候(点击回车或者ESC的时候) 这个时候就需要一个Button控件了