WinForm ImageButton






4.81/5 (28投票s)
一个 WinForm .NET 类,用于图像按钮,支持常规、悬停和按下时的图像,并支持文本。
引言
ImageButton
是一个简单的 .NET 类,用于 WinForm 版本的 Web 悬停按钮,支持“空闲”图像、悬停图像和“按下”图像。在 .NET 中设计自定义 UI 时,它可能会非常有用。
背景
如果您要修改源代码,对 .NET 中的重写、继承、隐藏、图像和属性有一定的了解会很有帮助。您需要知道如何使用图像编辑器来创建按钮,或使用网络上的按钮生成器。
Using the Code
图像中的文本
在开始使用代码之前,您需要创建一组图像 - 常规、悬停和按下 - 这些都是可选的,但全部组合在一起可以达到最佳效果。
您将创建类似这样的内容
正常 | ![]() |
悬停 | ![]() |
向下 | ![]() |
然后,将 bin/Release 中的 ImageButton.dll
添加到您的项目中作为引用,并将 ImageButton
控件拖到您的窗体上。您的窗体上将出现一个类似 PictureBox
的控件。
在属性窗口中更改 NormalImage
、HoverImage
和 DownImage
属性,以匹配创建的图像集。
图像中没有文本
您可以创建一个不包含文本的图像集,这样您就可以在 VS 设计器中使用文本。这意味着您不需要为每个按钮创建单独的图像集。
注意:文本功能不支持自动换行,您需要自己添加换行符(VB 中的 vbCrLf 或 C# 中的 \n)。
首先,创建一个不包含按钮文本的图像集;类似这样
正常 | ![]() |
悬停 | ![]() |
向下 | ![]() |
然后,和之前一样,添加 ImageButton
控件并设置图像属性,但这次,将 Text
属性设置为您要显示的文本,并将 Font
属性设置为您要使用的字体。
工作原理
为了创建此控件,我创建了我的 ImageButton
类,重写了 PictureBox
控件并实现了 IButtonControl
。实现 IButtonControl
将允许 ImageButton
像窗体上的任何其他按钮一样,用作默认按钮或取消按钮。
鼠标事件
概念很简单 - 我们创建一个图像并在屏幕上显示它。如果用户将鼠标悬停在图像上,我们将其切换为悬停图像,如果用户按下鼠标,我们将其更改为按下(“down”)图像。
因此,为此目的存在以下方法重写
#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
private bool hover = false;
private bool down = false;
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);
}
protected override void OnMouseLeave(EventArgs e)
{
hover = false;
Image = m_NormalImage;
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.Focus();
down = true;
if (m_DownImage != null)
Image = m_DownImage;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
down = false;
if (hover)
{
if (m_HoverImage != null)
Image = m_HoverImage;
}
else
Image = m_NormalImage;
base.OnMouseUp(e);
}
我们来一一过一遍
OnMouseMove
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);
}
OnMouseMove
在按钮在窗体上移动时被调用。我们避免使用 OnMouseHover
,因为它调用该方法之前有延迟。
我们将 hover 布尔值设置为 true
,以便其他方法知道鼠标是否悬停在控件上。然后我们检查鼠标按钮是否被按下。如果是,我们就将按钮图像设置为 DownImage
**如果** down 图像不为 null
并且图像不是已经是 down 图像。
如果鼠标未被按下,我们检查是否存在悬停图像 - 如果存在,我们就将其设置为悬停图像,否则,我们就将其设置为常规图像。
在最后一行,我们调用 PictureBox
版本的 OnMouseMove
。
OnMouseLeave
protected override void OnMouseLeave(EventArgs e)
{
hover = false;
Image = m_NormalImage;
base.OnMouseLeave(e);
}
这需要一点解释。:) 如果鼠标已离开控件的边界,我们将关于鼠标是否悬停在按钮上的开关设置为 false
,并将图像设置为“常规”图像。我们让 PictureBox
从那里接管。
OnMouseDown
protected override void OnMouseDown(MouseEventArgs e)
{
base.Focus();
down = true;
if (m_DownImage != null)
Image = m_DownImage;
base.OnMouseDown(e);
}
如果鼠标已按下控件,我们将焦点转移到图像按钮(这不是默认行为,因此我们必须实现它)并将 down 布尔值设置为 true
。如果 designer 已设置了 down 图像,我们就将图像更改为 down 图像。然后我们调用 PictureBox
自己的 OnMouseDown
副本。
OnMouseUp
protected override void OnMouseUp(MouseEventArgs e)
{
down = false;
if (hover)
{
if (m_HoverImage != null)
Image = m_HoverImage;
}
else
Image = m_NormalImage;
base.OnMouseUp(e);
}
鼠标按钮不再被按下,因此我们将 down
设置为 false
。如果我们悬停在控件上并已松开,那么我们就将图像设置为悬停图像(如果它不是 null),否则,我们就将其设置为“常规图像”。之后,我们告诉 PictureBox
继续处理 OnMouseUp
。
Null 值
正如您可能注意到的,我们在更改图像之前检查 hover 和 down 图像是否为 null
,但我们不这样做。这是为了防止在用户没有悬停或单击控件时,如果用户未指定常规图像,则 hover/down 图像会卡住。在演示应用程序中,这在示例 F 中进行了演示。
文本
PictureBox
控件具有 Control
类所需的 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;
}
}
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);
}
Overrides
PictureBox
从属性窗口隐藏了 Text
和 Font
属性。为了解决这个问题,我们为 Text
和 Font
属性创建了“虚拟”重写,它们只是设置基类属性,但为它们分配了 Browsable
和 DesignerSerializationVisibility
属性,这将告诉 Designer“注意”这些属性。
绘制
由于我们继承的 PictureBox
控件不渲染文本,我们必须在 OnPaint
中添加代码来绘制文本。首先,我们调用 PictureBox
基类的 OnPaint
方法,该方法处理图像和其他所有内容的绘制。在此之后,我们将文本绘制在已绘制图像的中间,方法是测量在选定 Font
下的 Text
大小,并将其与 ImageButton
的大小进行比较,以确定从哪里开始绘制文本。
OnTextChanged
当控件的文本更改时,我们必须重绘控件。因此,我们重写了 OnTextChanged
方法,并在其中添加了对 Refresh
方法(继承自 PictureBox
)的调用,该方法会重绘按钮。
隐藏属性
对于 ImageButton
来说,有些属性不像 PictureBox
那样有用,我们希望将它们从属性窗口中隐藏。为此,我们执行与 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; } }
描述更改
SizeMode
和 BorderStyle
属性提到了 PictureBox
而不是 ImageButton
。为了解决这个问题,我们只是使用属性和“虚拟”属性来更改属性的描述,使用以下代码
[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; } }
IButtonControl
我们还必须实现 IButtonControl
。这很容易做到,我们只需要实现方法如下
private bool isDefault = false;
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);
}
键盘事件
我们必须实现键盘事件,以便用户可以使用空格键和回车键像任何其他 Windows 窗体上的按钮一样“单击”按钮。
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);
}
简单来说,我们捕获发送到控件的消息,如果它是按键抬起或按键按下事件。如果是,我们就检查它是什么键。如果是回车键,我们就简单地调用一个点击事件。如果是空格键,我们就让按钮在按下时保持按下状态,直到
- 用户松开空格键,在这种情况下,我们执行一次点击,或者
- 用户按下 Escape、Tab,或者控件失去焦点,在这种情况下,我们不调用点击事件
如果不是空格键也不是回车键,我们就让 PictureBox
基类方法处理消息。
演示应用程序
ZIP 文件中包含了一个演示应用程序。这是截图

尽情享用!