我们有时候会在程序的文件夹里看见一些图标,而这些图标恰好是作为按钮的背景图片来使用的。鼠标指针在处于不同状态时,有“进入按钮”、“按下左键”,“松开”,“离开按钮”,则按钮的背景图片也在发生改变。这些图片大致如下(来自爱奇艺万能播放器PC端):
全文仅以第一张图片素材为例,这张图片可以分为4段(下图所示),恰好表示鼠标指针在操作控件时各个不同的状态,从左到右依次表示为“初始状态”(默认显示的背景)、“指针进入按钮区域或鼠标左键松开”,“鼠标左键按下不动”,“鼠标指针离开按钮区域”
本身这个图片素材设计的就很巧妙,它的尺寸是164 * 41,因此每一段的尺寸刚好是41 * 41
在贴代码之前请先看看效果:
编译环境及说明
- Microsoft Visual Studio 2010
- C# .Net Framework 4.0
- 除了实现这个图片按钮的功能,还添加了一些代码来减少甚至防止图片按钮在与鼠标指针交互时的闪烁
图片素材分割
显然上述的图片素材需要分割为4段作为鼠标指针的不同状态,实现分割的思路为
-
- 把图片转换为Image对象
- 克隆该Image对象(防止直接操作控件背景导致问题)
- 创建元素类型为Bitmap的容器List,用于存放分割后的4个图片对象
- 定义矩形区域Rectangle结构体,它用来表明应该取整个图片素材中的哪个部分,用for循环逐个计算出这4段图片的左上角坐标(即起始坐标)、宽度、高度,再将值对应的赋予Rectangle结构体中的属性
- 克隆上一步Rectangle结构体所对应区域下的图片块,并添加到第3步中提到的List容器中并返回该容器
由此可以定义一个函数 ImageSplit ,代码如下
1 /// <summary> 2 /// 图片分割函数,此处仅仅按图片宽度来分割 3 /// </summary> 4 /// <param name="ImageWidth">图片素材宽度</param> 5 /// <param name="SegmentsNum">要分割为几段,默认是1段</param> 6 /// <returns>分割后的图片集合</returns> 7 private List<Bitmap> ImageSplit(int ImageWidth, int SegmentsNum = 1) 8 { 9 // 定义分割后的图片存放容器 10 List<Bitmap> SplitedImage = new List<Bitmap>(); 11 // 克隆按钮背景图片 12 Bitmap SrcBmp = new Bitmap(this.Image); 13 // 指定图片像素格式为ARGB型 14 PixelFormat ReslouteFormat = PixelFormat.Format32bppArgb; 15 // 指定分割区域 16 Rectangle SplitAreaRec = new Rectangle(); 17 // 如果图片尺寸为负值 18 if (ImageWidth <= 0 || SegmentsNum <= 0) 19 return SplitedImage; 20 else 21 { 22 // 依据要分割的段数来做循环 23 // 从 0(含) 到 SegmentsNum - 1(含) 24 for (int i = 0; i < SegmentsNum; i++) 25 { 26 /* 27 * 在这里要把图片分割为4段小图片,每一段图片大小均为41 * 41 28 * 以下列举出每个小图片的左上角坐标(即起始坐标) 29 * (0, 0) 30 * (41, 0) 31 * (82, 0) 32 * (123, 0) 33 * Y 坐标均为 0 34 * 35 * 计算每个小图片的宽度:ImageWidth / SegmentsNum (总宽度/要分割的段数) 36 * 因此 X = i * (ImageWidth / SegmentsNum) 37 */ 38 SplitAreaRec.X = 0 + i * (ImageWidth / SegmentsNum); 39 SplitAreaRec.Y = 0; 40 // 小图片为正方形,所以以下这两个值一样 41 SplitAreaRec.Width = ImageWidth / SegmentsNum; 42 SplitAreaRec.Height = ImageWidth / SegmentsNum; 43 // 以指定的像素格式,克隆分割的图像 44 Bitmap SplitedBmp = SrcBmp.Clone(SplitAreaRec, ReslouteFormat); 45 // 添加进集合 46 SplitedImage.Add(SplitedBmp); 47 } 48 GC.Collect(); 49 return SplitedImage; 50 } 51 }
事件处理
该图片按钮控件有几个事件需要处理,包括:
-
- OnPaint(控件绘制事件)
- OnMouseEnter(鼠标指针进入控件区域触发事件)
- OnMouseDown (鼠标左键按下)
- OnMouseUp (鼠标左键松开)
- OnMouseLeave(鼠标指针离开控件区域)
OnPaint事件
首先在自定义控件类中定义私有对象,缓冲 Image 对象(最开始为空白图形)和对应的缓冲 Graphics 对象(在空白图形上绘制图案),这是为了减少闪烁
Image buffImg;
Graphics buffImgG;
具体代码如下:
1 protected override void OnPaint(PaintEventArgs pe) 2 { 3 base.OnPaint(pe); 4 // 创建空图形 5 buffImg = new Bitmap(Width,Height); 6 // 根据空图形创建画布Graphics对象 7 buffImgG = Graphics.FromImage(buffImg); 8 // 用画布对象,以背景色刷新空图形 9 buffImgG.Clear(this.BackColor); 10 11 //双三次插值 12 pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 13 //抗锯齿 14 pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias; 15 16 //图形轨迹 17 GraphicsPath gp = new GraphicsPath(); 18 //限定圆形绘制长方形区域 19 //限定为正方形 20 Rectangle limitedRec = new Rectangle(); 21 Point startDrawingPoint = new Point(0, 0); 22 limitedRec.Location = startDrawingPoint; 23 limitedRec.Size = new Size(Width - 1, Height - 1); 24 25 if (IsWeightWidthEqual) 26 { 27 int fixedWidth = Width - 1; 28 Height = Width; 29 Width = Height; 30 limitedRec.Size = new Size(fixedWidth, fixedWidth); 31 } 32 //以下代码视为了把图片框的显示边界改成圆形 33 //添加轨迹为椭圆 34 gp.AddEllipse(limitedRec); 35 //重新设置边界 36 Region rg = new Region(gp); 37 this.Region = rg; 38 //销毁资源 39 rg.Dispose(); 40 gp.Dispose(); 41 }