使用 C# 创建自定义位图按钮






4.74/5 (53投票s)
2005年3月3日
7分钟阅读

407426

9747
一篇关于创建位图按钮的文章。
引言
创建自定义位图控件的动机是为了能够为按钮的每种状态显示不同的位图图像。这包括禁用、正常、鼠标悬停和按钮按下状态。除了按钮图形外,包含按钮文本并控制文本相对于按钮图像的对齐方式也很重要。它遵循XP风格的外观和感觉,并带有一些独特的装饰。
Using the Code
代码可以分为三个主要部分:数据、渲染和事件。
- 数据:
private
变量保存状态和属性设置。在大多数情况下,此代码遵循属性引用或更改成员变量值的做法。每个属性都在下面的Properties
表中进行了描述。 - 渲染:按钮的渲染由多个方法实现;但是,
OnPaint
方法是驱动程序,它调用其他绘图方法来渲染按钮。 - 事件:重写了许多方法来处理事件和管理按钮的状态。这些方法是
OnMouseDown
、OnMouseUp
、OnMouseLeave
、OnMouseMove
、OnEnabledChanged
、OnLostFocus
。
查看源代码时,可以轻松找到这些部分,因为#region
关键字用于代码分隔。
Data
首先,让我们探讨一下属性。
位图按钮属性
BackColor | 按钮的背景颜色 |
BorderColor | 环绕按钮的细细的单像素宽边框的颜色 |
字体 | 用于渲染文本的字体 |
ForeColor | 按钮文本的颜色 |
ImageAlign | 指定图像的对齐方式 |
ImageBorderColor | 如果ImageBorderEnabled 为true ,则此属性包含渲染图像边框的颜色。此外,StretchImage 属性必须为false 。 |
ImageBorderEnabled | 如果渲染图像边框,则为true ,否则为false |
ImageDropShadow | 如果渲染图像边框周围的阴影,则为true |
ImageFocused | 当按钮获得焦点且处于正常状态时用于渲染的图像 |
ImageInactive | 按钮禁用时使用的图像。注意,如果未定义图像,则会使用正常图像的灰度版本作为替代。 |
ImageMouseOver | 鼠标悬停在按钮上但按钮未被按下时使用的图像 |
ImageNormal | 按钮处于正常状态时使用的图像。注意,必须为图像按钮设置此图像 |
ImagePressed | 按钮被按下时使用的图像 |
InnerBorderColor | 按钮处于正常状态时的内边框颜色 |
InnerBorderColor_Focus | 按钮获得焦点时的内边框颜色 |
InnerBorderColor_MouseOver | 鼠标悬停在按钮上时的内边框颜色 |
OffsetPressedContent | 如果设置为true 且按钮被按下,则按钮的内容会移动。 |
填充 | 它保存按钮内容之间的像素填充量。这是图像、文本和边框之间的空间。 |
StretchImage | 如果为true ,则表示将当前图像拉伸以填充整个按钮。 |
文本 | 要在按钮中显示的文本 |
TextAlignment | 定义文本的对齐方式 |
TextDropShadow | 如果为true ,则文本会投射阴影 |
所有属性都已添加到属性页的外观类别中。下图是它们的一个快照。
渲染
按钮的渲染由OnPaint
方法执行。然后,它调用多个例程来处理按钮的渲染细节。
CreateRegion
:为控件创建圆角透明按钮边缘paint_Background
:渲染按钮的背景paint_Text
:渲染文本和文本阴影paint_Border
:渲染按钮周围的1像素宽边框paint_InnerBorder
:渲染2像素宽的内边框paint_FocusBorder
:渲染按钮内的1像素宽虚线焦点边框
有关详细信息,请参阅下面的代码片段
/// <summary>
/// This method paints the button in its entirety.
/// </summary>
/// <param name="e">paint arguments use to paint the button</param>
protected override void OnPaint(PaintEventArgs e)
{
CreateRegion(0);
paint_Background(e);
paint_Text(e);
paint_Image(e);
paint_Border(e);
paint_InnerBorder(e);
paint_FocusBorder(e);
}
绘制背景可能很有意思。所采用的方法允许在多种颜色(意味着多于2种颜色)之间进行渐变背景插值。首先,需要使用颜色数组和插值位置来初始化一个混合对象。然后,像往常一样创建渐变画笔。最后一步是将混合对象链接到画笔。这通过设置画笔的InterpolationColors
属性来实现。
以下是插值多种颜色的示例
Color[] ColorArray = new Color[]{
System.Drawing.Color.White,
System.Drawing.Color.Yellow,
System.Drawing.Color.Blue,
System.Drawing.Color.Green,
System.Drawing.Color.Red,
System.Drawing.Color.Black};
float[] PositionArray = new float[]{0.0f,.15f,.40f,.65f,.80f,1.0f};
//
// create blend variable for the interpolate the colors
//
System.Drawing.Drawing2D.ColorBlend blend
= new System.Drawing.Drawing2D.ColorBlend();
blend.Colors = ColorArray;
blend.Positions = PositionArray;
//
// create vertical gradient brush
//
System.Drawing.Drawing2D.LinearGradientBrush brush
= new System.Drawing.Drawing2D.LinearGradientBrush(rect,
this.BackColor,Blend(this.BackColor,this.BackColor,10),
System.Drawing.Drawing2D.LinearGradientMode.Vertical);
brush.InterpolationColors = blend;
//
// fill the rectangle
//
g.FillRectangle(brush, rect);
//
// release resources
//
brush.Dispose();
对于文本渲染,我使用了System.Drawing.DrawString
方法。棘手的部分是确定在哪里绘制文本。由于代码量大,该功能被放入了辅助函数中,以免paint_Text
方法过于混乱。此方法的一个有趣之处在于它实现了阴影功能。这仅仅是创建带有alpha分量的画笔,然后像往常一样绘制文本。
//
// paint text shadow
//
if(TextDropShadow)
{
System.Drawing.Brush TransparentBrush0
= new System.Drawing.SolidBrush( System.Drawing.Color.FromArgb(50,
System.Drawing.Color.Black ) ) ;
System.Drawing.Brush TransparentBrush1
= new System.Drawing.SolidBrush( System.Drawing.Color.FromArgb(20,
System.Drawing.Color.Black ) ) ;
e.Graphics.DrawString(this.Text,this.Font,
TransparentBrush0,pt.X,pt.Y+1);
e.Graphics.DrawString(this.Text,this.Font,
TransparentBrush0,pt.X+1,pt.Y);
e.Graphics.DrawString(this.Text,this.Font,
TransparentBrush1,pt.X+1,pt.Y+1);
e.Graphics.DrawString(this.Text,this.Font,
TransparentBrush1,pt.X,pt.Y+2);
e.Graphics.DrawString(this.Text,this.Font,
TransparentBrush1,pt.X+2,pt.Y);
TransparentBrush0.Dispose();
TransparentBrush1.Dispose();
}
图像的绘制过程相当直接。但是,在使用以下方法时遇到了一些困难。使用资源编辑器创建的位图时,它能正常工作,但遗憾的是,使用第三方绘画程序创建的24位图像则失败了。解决方法是调用另一个DrawImage
方法。它可能速度较慢,但直到我理解问题所在,它暂时还可以。
// FAILED
g.DrawImage(image,rect.Left,rect.Top)
// WORKAROUND
g.DrawImage(image,rect, 0, 0 ,image.Width,image.Height, GraphicsUnit.Pixel);
用插值颜色绘制边框也不难。您需要经历与之前相同的创建渐变画笔的过程。渐变画笔在创建pen
对象时作为参数传递。下面的代码片段是此过程的一个示例。
....
//
// create brush and pens
//
System.Drawing.Drawing2D.LinearGradientBrush brush
= new System.Drawing.Drawing2D.LinearGradientBrush(rect,
this.BackColor,Blend(this.BackColor,this.BackColor,10),
System.Drawing.Drawing2D.LinearGradientMode.Vertical);
brush.InterpolationColors = blend;
System.Drawing.Pen pen0 = new System.Drawing.Pen(brush,1);
//
// draw line 0
//
g.DrawLine(pen0 , point_0,point_1);
....
事件
图像的数据和渲染已简要描述。按钮的下一个重要方面是输入的捕获和处理。所采取的方法是重写基类按钮的方法。然后,这些方法直接通过属性更改按钮的状态。一旦状态被更改,它们会使控件失效,以让OnPaint()
机制刷新图像。以下是事件方法及其用途的列表
事件方法 | 按钮状态 |
OnMouseDown | 将BtnState 设置为Pushed ,并将CapturingMouse 设置为true |
OnMouseUp | 将BtnState 设置为Normal ,并将CapturingMouse 设置为false |
OnMouseLeave | 如果CapturingMouse = true ,则将BtnState 设置为normal |
OnMouseMove | 如果CapturingMouse = true 且鼠标坐标在按钮区域内,则将BtnState 设置为Pushed ,否则将BtnState 设置为Normal 。如果 |
OnEnabledChanged | 按钮变为启用或禁用。如果按钮变为启用,则将BtnState 设置为Normal ,否则将BtnState 设置为Inactive |
OnLostFocus | 将btnState 设置为Normal |
下面的代码块显示了一个事件代码的示例。事件通常由少量代码组成。我应该涵盖的一个信息点是控件的Capture
属性。将其设置为true
,当指针位于按钮区域外时,按钮不会丢失输入焦点。这一点很重要,因为如果按住鼠标按钮并且用户将鼠标指针移进移出按钮区域,按钮的状态需要相应地改变。如果未设置Capture
属性,当指针离开按钮区域时,控件将停止捕获输入事件。
/// <summary>
/// Mouse Down Event:
/// set BtnState to Pushed and Capturing mouse to true
/// </summary>
/// <param name="e"></param>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown (e);
this.Capture = true;
this.CapturingMouse = true;
btnState = BtnState.Pushed;
this.Invalidate();
}
总结...
这是该控件的第一个版本,所有源代码都在一个文件中,即BitmapButton.cs。以后,如果时间允许并且该控件引起了兴趣,源代码可以利用接口来组件化不同的功能方面,从而促进可扩展性和可维护性。将主题包含进来并从XML源访问它们将是很棒的。缓存(双缓冲)图像也应该是一个选项。我期待您的建议,并希望位图控件能满足您的需求或为您的控件提供一些想法。
历史
版本 | 日期 | 变更 |
1.0 | 02-27-2005 | 控件的初始发布 |
许可证
本文没有明确的许可证附加,但可能包含文章文本或下载文件中的使用条款。如有疑问,请通过下面的讨论区联系作者。作者可能使用的许可证列表可以在此处找到。