C# 使用PictureBox實(shí)現(xiàn)圖片按鈕控件的示例步驟
引言
我們有時(shí)候會(huì)在程序的文件夾里看見(jiàn)一些圖標(biāo),而這些圖標(biāo)恰好是作為按鈕的背景圖片來(lái)使用的。鼠標(biāo)指針在處于不同狀態(tài)時(shí),有“進(jìn)入按鈕”、“按下左鍵”,“松開(kāi)”,“離開(kāi)按鈕”,則按鈕的背景圖片也在發(fā)生改變。這些圖片大致如下(來(lái)自愛(ài)奇藝萬(wàn)能播放器PC端):



全文僅以第一張圖片素材為例,這張圖片可以分為4段(下圖所示),恰好表示鼠標(biāo)指針在操作控件時(shí)各個(gè)不同的狀態(tài),從左到右依次表示為“初始狀態(tài)”(默認(rèn)顯示的背景)、“指針進(jìn)入按鈕區(qū)域或鼠標(biāo)左鍵松開(kāi)”,“鼠標(biāo)左鍵按下不動(dòng)”,“鼠標(biāo)指針離開(kāi)按鈕區(qū)域”

本身這個(gè)圖片素材設(shè)計(jì)的就很巧妙,它的尺寸是164 * 41,因此每一段的尺寸剛好是41 * 41
在貼代碼之前請(qǐng)先看看效果:

編譯環(huán)境及說(shuō)明
- Microsoft Visual Studio 2010
- C# .Net Framework 4.0
- 除了實(shí)現(xiàn)這個(gè)圖片按鈕的功能,還添加了一些代碼來(lái)減少甚至防止圖片按鈕在與鼠標(biāo)指針交互時(shí)的閃爍
圖片素材分割
顯然上述的圖片素材需要分割為4段作為鼠標(biāo)指針的不同狀態(tài),實(shí)現(xiàn)分割的思路為
- 把圖片轉(zhuǎn)換為Image對(duì)象
- 克隆該Image對(duì)象(防止直接操作控件背景導(dǎo)致問(wèn)題)
- 創(chuàng)建元素類型為Bitmap的容器List,用于存放分割后的4個(gè)圖片對(duì)象
- 定義矩形區(qū)域Rectangle結(jié)構(gòu)體,它用來(lái)表明應(yīng)該取整個(gè)圖片素材中的哪個(gè)部分,用for循環(huán)逐個(gè)計(jì)算出這4段圖片的左上角坐標(biāo)(即起始坐標(biāo))、寬度、高度,再將值對(duì)應(yīng)的賦予Rectangle結(jié)構(gòu)體中的屬性
- 克隆上一步Rectangle結(jié)構(gòu)體所對(duì)應(yīng)區(qū)域下的圖片塊,并添加到第3步中提到的List容器中并返回該容器
由此可以定義一個(gè)函數(shù) ImageSplit ,代碼如下
///
/// 圖片分割函數(shù),此處僅僅按圖片寬度來(lái)分割
///
/// 圖片素材寬度
/// 要分割為幾段,默認(rèn)是1段
/// 分割后的圖片集合
private List ImageSplit(int ImageWidth, int SegmentsNum = 1)
{
// 定義分割后的圖片存放容器
List SplitedImage = new List();
// 克隆按鈕背景圖片
Bitmap SrcBmp = new Bitmap(this.Image);
// 指定圖片像素格式為ARGB型
PixelFormat ReslouteFormat = PixelFormat.Format32bppArgb;
// 指定分割區(qū)域
Rectangle SplitAreaRec = new Rectangle();
// 如果圖片尺寸為負(fù)值
if (ImageWidth <= 0 || SegmentsNum <= 0)
return SplitedImage;
else
{
// 依據(jù)要分割的段數(shù)來(lái)做循環(huán)
// 從 0(含) 到 SegmentsNum - 1(含)
for (int i = 0; i < SegmentsNum; i++)
{
/*
* 在這里要把圖片分割為4段小圖片,每一段圖片大小均為41 * 41
* 以下列舉出每個(gè)小圖片的左上角坐標(biāo)(即起始坐標(biāo))
* (0, 0)
* (41, 0)
* (82, 0)
* (123, 0)
* Y 坐標(biāo)均為 0
*
* 計(jì)算每個(gè)小圖片的寬度:ImageWidth / SegmentsNum (總寬度/要分割的段數(shù))
* 因此 X = i * (ImageWidth / SegmentsNum)
*/
SplitAreaRec.X = 0 + i * (ImageWidth / SegmentsNum);
SplitAreaRec.Y = 0;
// 小圖片為正方形,所以以下這兩個(gè)值一樣
SplitAreaRec.Width = ImageWidth / SegmentsNum;
SplitAreaRec.Height = ImageWidth / SegmentsNum;
// 以指定的像素格式,克隆分割的圖像
Bitmap SplitedBmp = SrcBmp.Clone(SplitAreaRec, ReslouteFormat);
// 添加進(jìn)集合
SplitedImage.Add(SplitedBmp);
}
GC.Collect();
return SplitedImage;
}
}
事件處理
該圖片按鈕控件有幾個(gè)事件需要處理,包括:
- OnPaint(控件繪制事件)
- OnMouseEnter(鼠標(biāo)指針進(jìn)入控件區(qū)域觸發(fā)事件)
- OnMouseDown (鼠標(biāo)左鍵按下)
- OnMouseUp (鼠標(biāo)左鍵松開(kāi))
- OnMouseLeave(鼠標(biāo)指針離開(kāi)控件區(qū)域)
OnPaint事件
首先在自定義控件類中定義私有對(duì)象,緩沖 Image 對(duì)象(最開(kāi)始為空白圖形)和對(duì)應(yīng)的緩沖 Graphics 對(duì)象(在空白圖形上繪制圖案),這是為了減少閃爍
Image buffImg; Graphics buffImgG;
具體代碼如下:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
// 創(chuàng)建空?qǐng)D形
buffImg = new Bitmap(Width,Height);
// 根據(jù)空?qǐng)D形創(chuàng)建畫(huà)布Graphics對(duì)象
buffImgG = Graphics.FromImage(buffImg);
// 用畫(huà)布對(duì)象,以背景色刷新空?qǐng)D形
buffImgG.Clear(this.BackColor);
//雙三次插值
pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//抗鋸齒
pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//圖形軌跡
GraphicsPath gp = new GraphicsPath();
//限定圓形繪制長(zhǎng)方形區(qū)域
//限定為正方形
Rectangle limitedRec = new Rectangle();
Point startDrawingPoint = new Point(0, 0);
limitedRec.Location = startDrawingPoint;
limitedRec.Size = new Size(Width - 1, Height - 1);
if (IsWeightWidthEqual)
{
int fixedWidth = Width - 1;
Height = Width;
Width = Height;
limitedRec.Size = new Size(fixedWidth, fixedWidth);
}
//以下代碼視為了把圖片框的顯示邊界改成圓形
//添加軌跡為橢圓
gp.AddEllipse(limitedRec);
//重新設(shè)置邊界
Region rg = new Region(gp);
this.Region = rg;
//銷毀資源
rg.Dispose();
gp.Dispose();
}
鼠標(biāo)交互事件
上述5個(gè)事件除 OnPaint 之外,其余均為鼠標(biāo)交互事件
因?yàn)楸疚膶?duì)控件閃爍的問(wèn)題做了處理,所以在重寫(xiě)(Override)此類事件函數(shù)時(shí)需要添加一個(gè) BufferedGraphics 對(duì)象并為之分配空間,最后再使用它來(lái)渲染(Render)繪制好的圖形至當(dāng)前控件的 Graphics 畫(huà)布(/設(shè)備)對(duì)象(相當(dāng)于添加一個(gè)中間緩沖層將圖形繪制完成后再直接覆蓋到控件背景上以避免閃爍)
以下是OnMouseEnter事件的代碼:
//1.鼠標(biāo)進(jìn)入
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
// 雙三次插值
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// 抗鋸齒
g.SmoothingMode = SmoothingMode.AntiAlias;
// 再次以背景色刷新空白圖形
buffImgG.Clear(this.BackColor);
// 在空白圖形上繪制分割后的第2個(gè)小圖片
buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle);
// 依據(jù)上述空白圖形buffImgG創(chuàng)建緩沖Graphics,指定區(qū)域?yàn)樵摽丶ぷ鲄^(qū)
BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle);
// BufferedGraphics繪制整個(gè)圖形,指定繪制區(qū)域?yàn)樵摽丶ぷ鲄^(qū)
// 此處推薦使用DrawImageUnscaledAndClipped
buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle);
// 圖形緩沖區(qū)寫(xiě)入到當(dāng)前控件Graphics對(duì)象
buff.Render(g);
}
}
其它的鼠標(biāo)交互事件類似,只是繪制的背景圖片不一樣而已,即這句代碼 buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle); 中的 SplitedImage 索引各有不同,就不一一重復(fù)了。
代碼匯總
那么完整的程序應(yīng)該如何運(yùn)行呢?
在VS2010中新建一個(gè)解決方案,其中添加2個(gè)項(xiàng)目,一個(gè)是WinForm窗體應(yīng)用程序,這個(gè)是用來(lái)測(cè)試控件的;另一個(gè)是Windows窗體控件庫(kù)。窗體控件庫(kù)默認(rèn)繼承的是 UserControl 這個(gè)類,但是在本文筆者將其改為繼承 PictureBox 類,即自己做的這個(gè)控件還是屬于PictureBox這個(gè)類型而不是 UserControl
所以完整的代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Drawing.Imaging;
namespace PicBtn
{
public partial class RoundPictureBox : PictureBox
{
[Category("派生屬性"), Description("有的圖標(biāo)是正圓形,因此此處設(shè)置控件的長(zhǎng)寬是否相等")]
public bool IsWeightWidthEqual { get; set; }
// 該屬性尚未使用
[Category("派生屬性"), Description("表明是否由多個(gè)圖片來(lái)表示圖片框的按鈕特效")]
public bool IsMultiImage { get; set; }
// 分割后圖片容器
List SplitedImage = null;
Image buffImg;
Graphics buffImgG;
public RoundPictureBox()
{
InitializeComponent();
//雙緩沖區(qū)繪制
DoubleBuffered = true;
SizeMode = PictureBoxSizeMode.Normal;
// 圖片素材路徑,視具體情況而定(可以更改)
this.ImageLocation = @"D:\文檔\VS項(xiàng)目\PicButton\view_next.png";
//按鈕圖片分割
this.Image = Image.FromFile(ImageLocation);
// 圖片寬度(Width)164,將其分為4段并放到容器中
SplitedImage = ImageSplit(164, 4);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
// 創(chuàng)建空?qǐng)D形
buffImg = new Bitmap(Width,Height);
// 根據(jù)空?qǐng)D形創(chuàng)建畫(huà)布Graphics對(duì)象
buffImgG = Graphics.FromImage(buffImg);
// 用畫(huà)布對(duì)象,以背景色刷新空?qǐng)D形
buffImgG.Clear(this.BackColor);
//雙三次插值
pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//抗鋸齒
pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//圖形軌跡
GraphicsPath gp = new GraphicsPath();
//限定圓形繪制長(zhǎng)方形區(qū)域
//限定為正方形
Rectangle limitedRec = new Rectangle();
Point startDrawingPoint = new Point(0, 0);
limitedRec.Location = startDrawingPoint;
limitedRec.Size = new Size(Width - 1, Height - 1);
if (IsWeightWidthEqual)
{
int fixedWidth = Width - 1;
Height = Width;
Width = Height;
limitedRec.Size = new Size(fixedWidth, fixedWidth);
}
//以下代碼是為了把圖片框的顯示邊界改成圓形
//添加軌跡為橢圓
gp.AddEllipse(limitedRec);
//重新設(shè)置邊界
Region rg = new Region(gp);
this.Region = rg;
//銷毀資源
rg.Dispose();
gp.Dispose();
}
//繪制鼠標(biāo)進(jìn)入點(diǎn)擊并離開(kāi)的圖像
/*
* 完整的點(diǎn)擊過(guò)程如下(只考慮鼠標(biāo)左鍵的情況)
* 1. 鼠標(biāo)指針進(jìn)入PictureBox(以下簡(jiǎn)稱“該控件”),觸發(fā)事件 MouseEnter
* 2. 鼠標(biāo)按下不動(dòng)的一瞬間,觸發(fā)事件 MouseDown
* 3. 鼠標(biāo)松開(kāi)一瞬間,觸發(fā)事件 MouseUp
* 4. 鼠標(biāo)指針離開(kāi)該控件,觸發(fā)事件 MouseLeave
*/
//1.鼠標(biāo)進(jìn)入
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
// 雙三次插值
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// 抗鋸齒
g.SmoothingMode = SmoothingMode.AntiAlias;
// 再次以背景色刷新空白圖形
buffImgG.Clear(this.BackColor);
// 在空白圖形上繪制分割后的第2個(gè)小圖片
buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle);
// 依據(jù)上述空白圖形buffImgG創(chuàng)建緩沖Graphics,指定區(qū)域?yàn)樵摽丶ぷ鲄^(qū)
BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle);
// BufferedGraphics繪制整個(gè)圖形,指定繪制區(qū)域?yàn)樵摽丶ぷ鲄^(qū)
// 此處推薦使用DrawImageUnscaledAndClipped
buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle);
// 圖形緩沖區(qū)寫(xiě)入到當(dāng)前控件Graphics對(duì)象
buff.Render(g);
}
}
//2.鼠標(biāo)按下
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
buffImgG.InterpolationMode = InterpolationMode.HighQualityBicubic;
buffImgG.SmoothingMode = SmoothingMode.HighQuality;
buffImgG.Clear(BackColor);
buffImgG.DrawImageUnscaledAndClipped(SplitedImage[2],ClientRectangle);
BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle);
buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle);
buff.Render(g);
}
}
//3. 鼠標(biāo)按鍵松開(kāi)
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
buffImgG.Clear(BackColor);
buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle);
BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle);
buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle);
buff.Render(g);
}
}
//4.鼠標(biāo)離開(kāi)
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
buffImgG.Clear(BackColor);
buffImgG.DrawImageUnscaledAndClipped(SplitedImage[3], ClientRectangle);
BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle);
buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle);
buff.Render(g);
}
}
///
/// 圖片分割函數(shù),此處僅僅按圖片寬度來(lái)分割
///
/// 圖片素材寬度
/// 要分割為幾段,默認(rèn)是1段
/// 分割后的圖片集合
private List ImageSplit(int ImageWidth, int SegmentsNum = 1)
{
// 定義分割后的圖片存放容器
List SplitedImage = new List();
// 克隆按鈕背景圖片
Bitmap SrcBmp = new Bitmap(this.Image);
// 指定圖片像素格式為ARGB型
PixelFormat ReslouteFormat = PixelFormat.Format32bppArgb;
// 指定分割區(qū)域
Rectangle SplitAreaRec = new Rectangle();
// 如果圖片尺寸為負(fù)值
if (ImageWidth <= 0 || SegmentsNum <= 0)
return SplitedImage;
else
{
// 依據(jù)要分割的段數(shù)來(lái)做循環(huán)
// 從 0(含) 到 SegmentsNum - 1(含)
for (int i = 0; i < SegmentsNum; i++)
{
/*
* 在這里要把圖片分割為4段小圖片,每一段圖片大小均為41 * 41
* 以下列舉出每個(gè)小圖片的左上角坐標(biāo)(即起始坐標(biāo))
* (0, 0)
* (41, 0)
* (82, 0)
* (123, 0)
* Y 坐標(biāo)均為 0
*
* 計(jì)算每個(gè)小圖片的寬度:ImageWidth / SegmentsNum (總寬度/要分割的段數(shù))
* 因此 X = i * (ImageWidth / SegmentsNum)
*/
SplitAreaRec.X = 0 + i * (ImageWidth / SegmentsNum);
SplitAreaRec.Y = 0;
// 小圖片為正方形,所以以下這兩個(gè)值一樣
SplitAreaRec.Width = ImageWidth / SegmentsNum;
SplitAreaRec.Height = ImageWidth / SegmentsNum;
// 以指定的像素格式,克隆分割的圖像
Bitmap SplitedBmp = SrcBmp.Clone(SplitAreaRec, ReslouteFormat);
// 添加進(jìn)集合
SplitedImage.Add(SplitedBmp);
}
GC.Collect();
return SplitedImage;
}
}
}
}
還有設(shè)計(jì)器的代碼:
using System.Windows.Forms;
namespace PicBtn
{
partial class RoundPictureBox
{
///
/// 必需的設(shè)計(jì)器變量。
///
private System.ComponentModel.IContainer components = null;
///
/// 清理所有正在使用的資源。
///
/// 如果應(yīng)釋放托管資源,為 true;否則為 false。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region 組件設(shè)計(jì)器生成的代碼
///
/// 設(shè)計(jì)器支持所需的方法 - 不要
/// 使用代碼編輯器修改此方法的內(nèi)容。
///
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
// this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.
SetStyle(ControlStyles.DoubleBuffer, true); // 雙緩沖
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//解決閃爍
UpdateStyles();
}
#endregion
}
}
在生成解決方案之后,需要在窗體應(yīng)用程序項(xiàng)目中引用控件庫(kù)的項(xiàng)目,會(huì)在窗體設(shè)計(jì)器的工具箱中看見(jiàn)自己寫(xiě)的這個(gè)圖片按鈕控件,如下圖

最后把這個(gè)控件拖到自己窗體上即可,再調(diào)試、運(yùn)行并觀察結(jié)果,效果動(dòng)態(tài)圖在引言部分
作者:TaeYoona
出處:https://www.cnblogs.com/SNSD-99/
以上就是C# 使用PictureBox實(shí)現(xiàn)圖片按鈕控件的示例步驟的詳細(xì)內(nèi)容,更多關(guān)于C# 使用PictureBox實(shí)現(xiàn)圖片按鈕控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#網(wǎng)站生成靜態(tài)頁(yè)面的實(shí)例講解
今天小編就為大家分享一篇關(guān)于C#網(wǎng)站生成靜態(tài)頁(yè)面的實(shí)例講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
一文帶你吃透C#中面向?qū)ο蟮南嚓P(guān)知識(shí)
這篇文章主要為大家詳細(xì)介紹了C#中面向?qū)ο蟮南嚓P(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,需要的小伙伴可以參考一下2023-02-02
C#對(duì)DataTable里數(shù)據(jù)排序的方法
在日常開(kāi)發(fā)過(guò)程中,有一個(gè)DataTable集合,里面有很多字段,現(xiàn)在要求針對(duì)某一列進(jìn)行排序,如果該列為數(shù)字的話,進(jìn)行ASC即可實(shí)現(xiàn),但是該字段類型為string,此時(shí)排序就有點(diǎn)不正確了2013-11-11
C#實(shí)現(xiàn)主窗體最小化后出現(xiàn)懸浮框及雙擊懸浮框恢復(fù)原窗體的方法
這篇文章主要介紹了C#實(shí)現(xiàn)主窗體最小化后出現(xiàn)懸浮框及雙擊懸浮框恢復(fù)原窗體的方法,涉及C#窗體及鼠標(biāo)事件響應(yīng)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
C#實(shí)現(xiàn)的pdf生成圖片文字水印類實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)的pdf生成圖片文字水印類,結(jié)合完整實(shí)例形式分析了C#針對(duì)pdf文件的創(chuàng)建、添加文字、水印等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09

