65.9K
CodeProject 正在变化。 阅读更多。
Home

WinForm ImageButton

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (28投票s)

2008年9月2日

CC (ASA 2.5)

6分钟阅读

viewsIcon

173460

downloadIcon

9979

一个 WinForm .NET 类,用于图像按钮,支持常规、悬停和按下时的图像,并支持文本。

引言

ImageButton 是一个简单的 .NET 类,用于 WinForm 版本的 Web 悬停按钮,支持“空闲”图像、悬停图像和“按下”图像。在 .NET 中设计自定义 UI 时,它可能会非常有用。

背景

如果您要修改源代码,对 .NET 中的重写、继承、隐藏、图像和属性有一定的了解会很有帮助。您需要知道如何使用图像编辑器来创建按钮,或使用网络上的按钮生成器。

Using the Code

图像中的文本

在开始使用代码之前,您需要创建一组图像 - 常规、悬停和按下 - 这些都是可选的,但全部组合在一起可以达到最佳效果。

您将创建类似这样的内容

正常 ExampleButton.png
悬停 ExampleButtonHover.png
向下 ExampleButtonDown.png

然后,将 bin/Release 中的 ImageButton.dll 添加到您的项目中作为引用,并将 ImageButton 控件拖到您的窗体上。您的窗体上将出现一个类似 PictureBox 的控件。

在属性窗口中更改 NormalImageHoverImageDownImage 属性,以匹配创建的图像集。

图像中没有文本

您可以创建一个不包含文本的图像集,这样您就可以在 VS 设计器中使用文本。这意味着您不需要为每个按钮创建单独的图像集。

注意:文本功能不支持自动换行,您需要自己添加换行符(VB 中的 vbCrLf 或 C# 中的 \n)。

首先,创建一个不包含按钮文本的图像集;类似这样

正常 ExampleButtonA.png
悬停 ExampleButtonHoverA.png
向下 ExampleButtonDownA.png

然后,和之前一样,添加 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 类所需的 TextFont 属性,但它们未实现,并在属性窗口中隐藏。我们可以更改此设置,以便渲染文本

[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 从属性窗口隐藏了 TextFont 属性。为了解决这个问题,我们为 TextFont 属性创建了“虚拟”重写,它们只是设置基类属性,但为它们分配了 BrowsableDesignerSerializationVisibility 属性,这将告诉 Designer“注意”这些属性。

绘制

由于我们继承的 PictureBox 控件不渲染文本,我们必须在 OnPaint 中添加代码来绘制文本。首先,我们调用 PictureBox 基类的 OnPaint 方法,该方法处理图像和其他所有内容的绘制。在此之后,我们将文本绘制在已绘制图像的中间,方法是测量在选定 Font 下的 Text 大小,并将其与 ImageButton 的大小进行比较,以确定从哪里开始绘制文本。

OnTextChanged

当控件的文本更改时,我们必须重绘控件。因此,我们重写了 OnTextChanged 方法,并在其中添加了对 Refresh 方法(继承自 PictureBox)的调用,该方法会重绘按钮。

隐藏属性

对于 ImageButton 来说,有些属性不像 PictureBox 那样有用,我们希望将它们从属性窗口中隐藏。为此,我们执行与 TextFont 属性相反的操作,使用以下代码

[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; } }

描述更改

SizeModeBorderStyle 属性提到了 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);
}

简单来说,我们捕获发送到控件的消息,如果它是按键抬起或按键按下事件。如果是,我们就检查它是什么键。如果是回车键,我们就简单地调用一个点击事件。如果是空格键,我们就让按钮在按下时保持按下状态,直到

  1. 用户松开空格键,在这种情况下,我们执行一次点击,或者 
  2. 用户按下 Escape、Tab,或者控件失去焦点,在这种情况下,我们不调用点击事件

如果不是空格键也不是回车键,我们就让 PictureBox 基类方法处理消息。

演示应用程序

ZIP 文件中包含了一个演示应用程序。这是截图

imagebuttondemo.png

尽情享用!

© . All rights reserved.