C# 使用PictureBox實現(xiàn)圖片按鈕控件的示例步驟
引言
我們有時候會在程序的文件夾里看見一些圖標,而這些圖標恰好是作為按鈕的背景圖片來使用的。鼠標指針在處于不同狀態(tài)時,有“進入按鈕”、“按下左鍵”,“松開”,“離開按鈕”,則按鈕的背景圖片也在發(fā)生改變。這些圖片大致如下(來自愛奇藝萬能播放器PC端):
全文僅以第一張圖片素材為例,這張圖片可以分為4段(下圖所示),恰好表示鼠標指針在操作控件時各個不同的狀態(tài),從左到右依次表示為“初始狀態(tài)”(默認顯示的背景)、“指針進入按鈕區(qū)域或鼠標左鍵松開”,“鼠標左鍵按下不動”,“鼠標指針離開按鈕區(qū)域”
本身這個圖片素材設計的就很巧妙,它的尺寸是164 * 41,因此每一段的尺寸剛好是41 * 41
在貼代碼之前請先看看效果:
編譯環(huán)境及說明
- Microsoft Visual Studio 2010
- C# .Net Framework 4.0
- 除了實現(xiàn)這個圖片按鈕的功能,還添加了一些代碼來減少甚至防止圖片按鈕在與鼠標指針交互時的閃爍
圖片素材分割
顯然上述的圖片素材需要分割為4段作為鼠標指針的不同狀態(tài),實現(xiàn)分割的思路為
- 把圖片轉(zhuǎn)換為Image對象
- 克隆該Image對象(防止直接操作控件背景導致問題)
- 創(chuàng)建元素類型為Bitmap的容器List,用于存放分割后的4個圖片對象
- 定義矩形區(qū)域Rectangle結(jié)構(gòu)體,它用來表明應該取整個圖片素材中的哪個部分,用for循環(huán)逐個計算出這4段圖片的左上角坐標(即起始坐標)、寬度、高度,再將值對應的賦予Rectangle結(jié)構(gòu)體中的屬性
- 克隆上一步Rectangle結(jié)構(gòu)體所對應區(qū)域下的圖片塊,并添加到第3步中提到的List容器中并返回該容器
由此可以定義一個函數(shù) ImageSplit ,代碼如下
/// /// 圖片分割函數(shù),此處僅僅按圖片寬度來分割 /// /// 圖片素材寬度 /// 要分割為幾段,默認是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(); // 如果圖片尺寸為負值 if (ImageWidth <= 0 || SegmentsNum <= 0) return SplitedImage; else { // 依據(jù)要分割的段數(shù)來做循環(huán) // 從 0(含) 到 SegmentsNum - 1(含) for (int i = 0; i < SegmentsNum; i++) { /* * 在這里要把圖片分割為4段小圖片,每一段圖片大小均為41 * 41 * 以下列舉出每個小圖片的左上角坐標(即起始坐標) * (0, 0) * (41, 0) * (82, 0) * (123, 0) * Y 坐標均為 0 * * 計算每個小圖片的寬度:ImageWidth / SegmentsNum (總寬度/要分割的段數(shù)) * 因此 X = i * (ImageWidth / SegmentsNum) */ SplitAreaRec.X = 0 + i * (ImageWidth / SegmentsNum); SplitAreaRec.Y = 0; // 小圖片為正方形,所以以下這兩個值一樣 SplitAreaRec.Width = ImageWidth / SegmentsNum; SplitAreaRec.Height = ImageWidth / SegmentsNum; // 以指定的像素格式,克隆分割的圖像 Bitmap SplitedBmp = SrcBmp.Clone(SplitAreaRec, ReslouteFormat); // 添加進集合 SplitedImage.Add(SplitedBmp); } GC.Collect(); return SplitedImage; } }
事件處理
該圖片按鈕控件有幾個事件需要處理,包括:
- OnPaint(控件繪制事件)
- OnMouseEnter(鼠標指針進入控件區(qū)域觸發(fā)事件)
- OnMouseDown (鼠標左鍵按下)
- OnMouseUp (鼠標左鍵松開)
- OnMouseLeave(鼠標指針離開控件區(qū)域)
OnPaint事件
首先在自定義控件類中定義私有對象,緩沖 Image 對象(最開始為空白圖形)和對應的緩沖 Graphics 對象(在空白圖形上繪制圖案),這是為了減少閃爍
Image buffImg; Graphics buffImgG;
具體代碼如下:
protected override void OnPaint(PaintEventArgs pe) { base.OnPaint(pe); // 創(chuàng)建空圖形 buffImg = new Bitmap(Width,Height); // 根據(jù)空圖形創(chuàng)建畫布Graphics對象 buffImgG = Graphics.FromImage(buffImg); // 用畫布對象,以背景色刷新空圖形 buffImgG.Clear(this.BackColor); //雙三次插值 pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; //抗鋸齒 pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias; //圖形軌跡 GraphicsPath gp = new GraphicsPath(); //限定圓形繪制長方形區(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); //重新設置邊界 Region rg = new Region(gp); this.Region = rg; //銷毀資源 rg.Dispose(); gp.Dispose(); }
鼠標交互事件
上述5個事件除 OnPaint 之外,其余均為鼠標交互事件
因為本文對控件閃爍的問題做了處理,所以在重寫(Override)此類事件函數(shù)時需要添加一個 BufferedGraphics 對象并為之分配空間,最后再使用它來渲染(Render)繪制好的圖形至當前控件的 Graphics 畫布(/設備)對象(相當于添加一個中間緩沖層將圖形繪制完成后再直接覆蓋到控件背景上以避免閃爍)
以下是OnMouseEnter事件的代碼:
//1.鼠標進入 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個小圖片 buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle); // 依據(jù)上述空白圖形buffImgG創(chuàng)建緩沖Graphics,指定區(qū)域為該控件工作區(qū) BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle); // BufferedGraphics繪制整個圖形,指定繪制區(qū)域為該控件工作區(qū) // 此處推薦使用DrawImageUnscaledAndClipped buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle); // 圖形緩沖區(qū)寫入到當前控件Graphics對象 buff.Render(g); } }
其它的鼠標交互事件類似,只是繪制的背景圖片不一樣而已,即這句代碼 buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle); 中的 SplitedImage 索引各有不同,就不一一重復了。
代碼匯總
那么完整的程序應該如何運行呢?
在VS2010中新建一個解決方案,其中添加2個項目,一個是WinForm窗體應用程序,這個是用來測試控件的;另一個是Windows窗體控件庫。窗體控件庫默認繼承的是 UserControl 這個類,但是在本文筆者將其改為繼承 PictureBox 類,即自己做的這個控件還是屬于PictureBox這個類型而不是 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("有的圖標是正圓形,因此此處設置控件的長寬是否相等")] public bool IsWeightWidthEqual { get; set; } // 該屬性尚未使用 [Category("派生屬性"), Description("表明是否由多個圖片來表示圖片框的按鈕特效")] 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項目\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)建空圖形 buffImg = new Bitmap(Width,Height); // 根據(jù)空圖形創(chuàng)建畫布Graphics對象 buffImgG = Graphics.FromImage(buffImg); // 用畫布對象,以背景色刷新空圖形 buffImgG.Clear(this.BackColor); //雙三次插值 pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; //抗鋸齒 pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias; //圖形軌跡 GraphicsPath gp = new GraphicsPath(); //限定圓形繪制長方形區(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); //重新設置邊界 Region rg = new Region(gp); this.Region = rg; //銷毀資源 rg.Dispose(); gp.Dispose(); } //繪制鼠標進入點擊并離開的圖像 /* * 完整的點擊過程如下(只考慮鼠標左鍵的情況) * 1. 鼠標指針進入PictureBox(以下簡稱“該控件”),觸發(fā)事件 MouseEnter * 2. 鼠標按下不動的一瞬間,觸發(fā)事件 MouseDown * 3. 鼠標松開一瞬間,觸發(fā)事件 MouseUp * 4. 鼠標指針離開該控件,觸發(fā)事件 MouseLeave */ //1.鼠標進入 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個小圖片 buffImgG.DrawImageUnscaledAndClipped(SplitedImage[1], ClientRectangle); // 依據(jù)上述空白圖形buffImgG創(chuàng)建緩沖Graphics,指定區(qū)域為該控件工作區(qū) BufferedGraphics buff = BufferedGraphicsManager.Current.Allocate(buffImgG, ClientRectangle); // BufferedGraphics繪制整個圖形,指定繪制區(qū)域為該控件工作區(qū) // 此處推薦使用DrawImageUnscaledAndClipped buff.Graphics.DrawImageUnscaledAndClipped(buffImg, ClientRectangle); // 圖形緩沖區(qū)寫入到當前控件Graphics對象 buff.Render(g); } } //2.鼠標按下 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. 鼠標按鍵松開 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.鼠標離開 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ù),此處僅僅按圖片寬度來分割 /// /// 圖片素材寬度 /// 要分割為幾段,默認是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(); // 如果圖片尺寸為負值 if (ImageWidth <= 0 || SegmentsNum <= 0) return SplitedImage; else { // 依據(jù)要分割的段數(shù)來做循環(huán) // 從 0(含) 到 SegmentsNum - 1(含) for (int i = 0; i < SegmentsNum; i++) { /* * 在這里要把圖片分割為4段小圖片,每一段圖片大小均為41 * 41 * 以下列舉出每個小圖片的左上角坐標(即起始坐標) * (0, 0) * (41, 0) * (82, 0) * (123, 0) * Y 坐標均為 0 * * 計算每個小圖片的寬度:ImageWidth / SegmentsNum (總寬度/要分割的段數(shù)) * 因此 X = i * (ImageWidth / SegmentsNum) */ SplitAreaRec.X = 0 + i * (ImageWidth / SegmentsNum); SplitAreaRec.Y = 0; // 小圖片為正方形,所以以下這兩個值一樣 SplitAreaRec.Width = ImageWidth / SegmentsNum; SplitAreaRec.Height = ImageWidth / SegmentsNum; // 以指定的像素格式,克隆分割的圖像 Bitmap SplitedBmp = SrcBmp.Clone(SplitAreaRec, ReslouteFormat); // 添加進集合 SplitedImage.Add(SplitedBmp); } GC.Collect(); return SplitedImage; } } } }
還有設計器的代碼:
using System.Windows.Forms; namespace PicBtn { partial class RoundPictureBox { /// /// 必需的設計器變量。 /// private System.ComponentModel.IContainer components = null; /// /// 清理所有正在使用的資源。 /// /// 如果應釋放托管資源,為 true;否則為 false。 protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region 組件設計器生成的代碼 /// /// 設計器支持所需的方法 - 不要 /// 使用代碼編輯器修改此方法的內(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 } }
在生成解決方案之后,需要在窗體應用程序項目中引用控件庫的項目,會在窗體設計器的工具箱中看見自己寫的這個圖片按鈕控件,如下圖
最后把這個控件拖到自己窗體上即可,再調(diào)試、運行并觀察結(jié)果,效果動態(tài)圖在引言部分
作者:TaeYoona
出處:https://www.cnblogs.com/SNSD-99/
以上就是C# 使用PictureBox實現(xiàn)圖片按鈕控件的示例步驟的詳細內(nèi)容,更多關(guān)于C# 使用PictureBox實現(xiàn)圖片按鈕控件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實現(xiàn)主窗體最小化后出現(xiàn)懸浮框及雙擊懸浮框恢復原窗體的方法
這篇文章主要介紹了C#實現(xiàn)主窗體最小化后出現(xiàn)懸浮框及雙擊懸浮框恢復原窗體的方法,涉及C#窗體及鼠標事件響應的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08