原文地址:https://www.codeproject.com/Articles/29010/WinForm-ImageButton
自定义winfrom图片按钮:支持鼠标正常、悬停、按下更改图片,支持文本。
首先,创建没有按钮文本的图片,这样的:
正常: 悬停: 按下:
添加ImageButton控件并设置图像属性,然后设置文本Text,设置字体Font。
1.创建ImageButton类,重写PictureBox控件,实现IButtonControl接口。
实现IButtonControl接口,将允许ImageButton按钮与窗体上的任何其他按钮一起使用,作为默认按钮或取消按钮。
public class ImageButton : PictureBox, IButtonControl
2.鼠标方法
思路很简单。就是创建一个图片显示在屏幕上,如果用户将鼠标停留在图片上,就会切换图片到HoverImage,
鼠标按下就会切换图片到DownImage。
2.1 设置属性
private bool hover = false; private bool down = false; #region HoverImage private Image m_HoverImage; [Category("Appearance")] [Description("Image to show when the button is hovered over.")] public Image HoverImage { get { return m_HoverImage; } set { m_HoverImage = value; if (hover) Image = value; } } #endregion #region DownImage private Image m_DownImage; [Category("Appearance")] [Description("Image to show when the button is depressed.")] public Image DownImage { get { return m_DownImage; } set { m_DownImage = value; if (down) Image = value; } } #endregion #region NormalImage private Image m_NormalImage; [Category("Appearance")] [Description("Image to show when the button is not in any other state.")] public Image NormalImage { get { return m_NormalImage; } set { m_NormalImage = value; if (!(hover || down)) Image = value; } } #endregion
2.2 重写OnMouseMove事件
当鼠标移动到ImageButton上时调用OnMouseMove。避免使用OnMouseHover,因为这个方法会延迟调用。
设置hover=true,表示鼠标悬停在ImageButton上,
然后判断down的值,down=true时,设置按钮图片为DownImage;down=false时,设置按钮图片为HorverImage。
protected override void OnMouseMove(MouseEventArgs e) { hover = true; if (down) { if ((m_DownImage != null) && (Image != m_DownImage)) Image = m_DownImage; } else if (m_HoverImage != null) Image = m_HoverImage; else Image = m_NormalImage; base.OnMouseMove(e); }
2.3 重写OnMouseLeave事件
如果鼠标已经离开ImageButton的边界,设置hover=false。然后切换图片为NormalImage。
protected override void OnMouseLeave(EventArgs e) { hover = false; Image = m_NormalImage; base.OnMouseLeave(e); }
2.4 重写OnMouseDown事件
如果鼠标按下控件,我们将焦点转移到ImageButton上(这不是默认行为,需要实现),
然后设置down为true,并切换图片为DownImage。
protected override void OnMouseDown(MouseEventArgs e) { base.Focus(); down = true; if (m_DownImage != null) Image = m_DownImage; base.OnMouseDown(e); }
2.5 重写OnMouseUp事件
当鼠标不再被按下,设置down为false。
如果鼠标悬停在ImageButton上,切换图片为HoverImage。
如果鼠标离开ImageButton,切换图片为NormalImage。
protected override void OnMouseUp(MouseEventArgs e) { down = false; if (hover) { if (m_HoverImage != null) Image = m_HoverImage; } else Image = m_NormalImage; base.OnMouseUp(e); }
3.空值
你应该注意到,我们在切换图片之前会检查HoverImage和DowmImage是否为空,而NormalImage不用检查,这是为什么呢?
这是为了防止当NormalImage没有指定图片,而HoverImage和DowmImage有图片时,显示图片出来。
4.文本
PictureBox控件继承了Control类,而Control类具有Text和Font属性,
所以Text和Font属性没有实现,而是隐藏在属性里。
我们可以改变这一点,使文本呈现:
4.1 重写Text属性和Font属性
[Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Appearance")] [Description("The text associated with the control.")] public override string Text { get { return base.Text; } set { base.Text = value; } } [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Appearance")] [Description("The font used to display text in the control.")] public override Font Font { get { return base.Font; } set { base.Font = value; } }
4.2 重写方法:绘制文本
OnPaint
由于ImageButton继承的PictureBox控件不会呈现文本,我们必须添加代码来绘制按钮上的文本。
OnPain可以处理图像的绘制和其他。然后根据Text的大小,与ImageButton大小比较,找到开始绘制文本的位置。
OnTextChanged
当控件文本改变时,我们必须重新绘制控件。
因此重写OnTextChanged方法,并调用Refresh方法,该方法重新绘制了该按钮(Refresh方法继承自PictureBox)。
protected override void OnPaint(PaintEventArgs pe) { base.OnPaint(pe); if ((!string.IsNullOrEmpty(Text)) && (pe != null) && (base.Font != null)) { SolidBrush drawBrush = new SolidBrush(base.ForeColor); SizeF drawStringSize = pe.Graphics.MeasureString(base.Text, base.Font); PointF drawPoint; if (base.Image != null) drawPoint = new PointF(base.Image.Width / 2 - drawStringSize.Width / 2, base.Image.Height / 2 - drawStringSize.Height / 2); else drawPoint = new PointF(base.Width / 2 - drawStringSize.Width / 2, base.Height / 2 - drawStringSize.Height / 2); pe.Graphics.DrawString(base.Text, base.Font, drawBrush, drawPoint); } } protected override void OnTextChanged(EventArgs e) { Refresh(); base.OnTextChanged(e); }
5.隐藏属性
对于一些继承了PictureBox,而对ImageButton不是很有用的属性,我们希望它们从Property窗口隐藏起来。
为了做到这一点,我们使用了与Text和Font属性相反的处理。
[Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Image Image { get { return base.Image; } set { base.Image = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new ImageLayout BackgroundImageLayout { get { return base.BackgroundImageLayout; } set { base.BackgroundImageLayout = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Image BackgroundImage { get { return base.BackgroundImage; } set { base.BackgroundImage = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new String ImageLocation { get { return base.ImageLocation; } set { base.ImageLocation = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Image ErrorImage { get { return base.ErrorImage; } set { base.ErrorImage = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Image InitialImage { get { return base.InitialImage; } set { base.InitialImage = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new bool WaitOnLoad { get { return base.WaitOnLoad; } set { base.WaitOnLoad = value; } }
6.说明变更
SizeMode和BorderStyle属性描述提到的是pictureBox,而不是ImageButton。下面的代码会改变属性的Description。
[Description("Controls how the ImageButton will handle image placement and control sizing.")] public new PictureBoxSizeMode SizeMode { get { return base.SizeMode; } set { base.SizeMode = value; } } [Description("Controls what type of border the ImageButton should have.")] public new BorderStyle BorderStyle { get { return base.BorderStyle; } set { base.BorderStyle = value; } }
7.实现IButtonControl
我们也要实现IButtonControl,很简单,我们所要做的就是实现这些方法:
private bool isDefault = false; private DialogResult m_DialogResult; public DialogResult DialogResult { get { return m_DialogResult; } set { m_DialogResult = value; } } public void NotifyDefault(bool value) { isDefault = value; } public void PerformClick() { base.OnClick(EventArgs.Empty); }
8.键盘方法
我们必须实现键盘事件,以便用户可以使用空格键和输入键“点击”按钮。
如果它是一个key up或key down事件,我们会将消息发送到控件。如果是,我们检查它是什么键。如果是输入键,我们只需调用点击事件。
如果是空格键,则按住按钮直到:
- 用户放开空格键,在这种情况下,我们执行点击或
- 用户按Escape,Tab或控件丢失焦点,在这种情况下,我们不调用点击事件
如果不是空格键,而不是输入键,我们让PictureBox
基类方法处理消息。
private const int WM_KEYDOWN = 0x0100; private const int WM_KEYUP = 0x0101; private bool holdingSpace = false; public override bool PreProcessMessage(ref Message msg) { if (msg.Msg == WM_KEYUP) { if (holdingSpace) { if ((int)msg.WParam == (int)Keys.Space) { OnMouseUp(null); PerformClick(); } else if ((int)msg.WParam == (int)Keys.Escape || (int)msg.WParam == (int)Keys.Tab) { holdingSpace = false; OnMouseUp(null); } } return true; } else if (msg.Msg == WM_KEYDOWN) { if ((int)msg.WParam == (int)Keys.Space) { holdingSpace = true; OnMouseDown(null); } else if ((int)msg.WParam == (int)Keys.Enter) { PerformClick(); } return true; } else return base.PreProcessMessage(ref msg); } protected override void OnLostFocus(EventArgs e) { holdingSpace = false; OnMouseUp(null); base.OnLostFocus(e); }
9.演示程序