系統(tǒng):WindowsXP P
環(huán)境:Visual Studio.NET 2003 語言:C# 源碼下載
在開始我們的講解之前先看一下我們位圖Button控件的效果(很漂亮吧。┤绻銓(duì)這個(gè)控件感興趣的話,就跟著我們的腳步學(xué)習(xí)如何做出這樣的一個(gè)控件吧!
簡介:
創(chuàng)建自定義位圖控件的目的是允許在每一種按鈕狀態(tài)下呈現(xiàn)不同的位圖,這些狀態(tài)包括:disabled, normal, mouse over,還有button pressed;除了按鈕的圖像,讓我們的按鈕飽含文本,并且根據(jù)按鈕圖片控制文本的對(duì)齊方式也很重要。按鈕采用XP樣式,還包含了我們定制的一些特性。
代碼使用:
程序的源碼可以分為3大部分:data(數(shù)據(jù)), rendering(表現(xiàn)), and events(事件)
Data:存儲(chǔ)狀態(tài)和設(shè)置屬性的私有變量,下表中有每一個(gè)屬性的描述。
Rendering:按鈕的呈現(xiàn)是靠幾個(gè)方法來實(shí)現(xiàn)的,OnPaint方法是調(diào)用其它繪制方法的一個(gè)驅(qū)動(dòng)性質(zhì)的方法(意思就是靠我們的OnPaint方法,調(diào)用自定義的繪制方法),用來呈現(xiàn)我們的Button控件
Events:事件處理操作按鈕的狀態(tài)這些事件有:OnMouseDown
, OnMouseUp
, OnMouseLeave
, OnMouseMove
, OnEnabledChanged
, OnLostFocus
.
Data:
首先讓我們研究一下這些屬性
BITMAP BUTTON PROPERTIES | ||||||||||||||||||||||||||||||||||||||||||||
|
所有的這些屬性都被加到屬性標(biāo)簽頁了,下面是個(gè)截圖:
Rendering:
按鈕控件的呈現(xiàn)工作是OnPaint方法實(shí)現(xiàn)的,它輪流調(diào)用幾個(gè)方法呈現(xiàn)Button我們?cè)O(shè)置的狀態(tài)。
CreateRegion
: 創(chuàng)建按鈕的圓角邊框paint_Background
: 繪制呈現(xiàn)按鈕背景paint_Text
: 繪制呈現(xiàn)按鈕文本和文本陰影paint_Border
: 繪制 1像素的按鈕邊框paint_InnerBorder
: 繪制 2像素的按鈕內(nèi)邊框paint_FocusBorder
: 繪制 1像素的按鈕虛線焦點(diǎn)邊框
/// 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);
}
繪制背景應(yīng)該是很有趣的:
Painting the background can be of some interest. The approach that was taken allows for a gradient background interpolation between multiple colors (meaning more then 2 colors). First, a blend object needs to be initialized with an array of colors, and the position of interpolation. Next, the gradient brush can be created as usual. The Final step involves linking the blend object to the brush. This is accomplished by setting the InterpolationColors property of a brush.
下面是復(fù)合顏色的代碼示例:
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()方法繪制按鈕文本,在何處使用這個(gè)方法呢,為了區(qū)別paint_Text()方法,我們將代碼放置在Helper函數(shù)部分(看源代碼大家就一目了然是什么意思了)還有一處大家一定會(huì)感興趣,就是文本的陰影效果是如何實(shí)現(xiàn)的呢?我們看看接下來的代碼:
// 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();
}
相信不用我多解釋,大家就知道實(shí)現(xiàn)的原理是什么了吧,好了我們繼續(xù)。
繪制圖像更是一個(gè)直接的過程,但是在使用下面的方法的時(shí)候,我遇到了一些問題,當(dāng)他繪制一個(gè)有我們資源編輯器產(chǎn)生的為圖的時(shí)候確實(shí)沒有什么問題,但是當(dāng)我們繪制一個(gè)由第三方程序產(chǎn)生的24位位圖的時(shí)候,失敗了。它必須使用另一種DrawImage方法,現(xiàn)在我們知道接下來需要怎么修改我們的方法了。
g.DrawImage(image,rect.Left,rect.Top)
// WORKAROUND
g.DrawImage(image,rect, 0, 0 ,image.Width,image.Height, GraphicsUnit.Pixel);
繪制邊框的代碼當(dāng)然也不難,像前面一樣創(chuàng)建一個(gè)梯度畫刷對(duì)象,然后繪制的時(shí)候把這個(gè)對(duì)象當(dāng)作參數(shù)傳遞進(jìn)去,看下面的代碼,其實(shí)很簡單。
//
// 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);
//.
Events:
數(shù)據(jù)成員、一些方法都簡單的陳述了,接下來看看我們的事件處理機(jī)制。按鈕的一個(gè)大的方面就是事件和捕獲與處理。我們走了個(gè)大的捷徑,那就是重載按鈕事件的方法,這些方法通過按鈕屬性直接修改按鈕的狀態(tài)。一旦狀態(tài)改變,他們就會(huì)讓控件無效,然后刷新機(jī)制重新繪制我們的Button。下面是事件方法列表和簡單說明。
Event Methods |
Button state |
|
Set BtnState to Pushed and Capturing mouse to true 設(shè)置Button狀態(tài)為按下,并且將捕獲鼠標(biāo)屬性設(shè)為true |
|
Set BtnState to 設(shè)置Button狀態(tài)為普通,并且將捕獲鼠標(biāo)屬性設(shè)為false |
|
Set BtnState to normal if we CapturingMouse = true 設(shè)置Button狀態(tài)為普通,如果捕獲鼠標(biāo)狀態(tài)屬性為true |
|
If CapturingMouse = true and mouse coordinates are within button region, set BtnState to Pushed, otherwise set BtnState to 簡單的說就是根據(jù)十分捕獲鼠標(biāo)設(shè)置Button狀態(tài) |
|
The button either became enabled or disabled. If button became enabled, set BtnState to Button是否可用改變時(shí)的方法 |
|
Set btnState to 失去焦點(diǎn),把Button狀態(tài)屬性變?yōu)槠胀ǖ姆椒?/SPAN> |
下面的代碼是一個(gè)簡單的事件處理。這里值得注意的是Capture屬性。
/// 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();
}
按鈕是否獲取焦點(diǎn)問題,大家知道如果這個(gè)焦點(diǎn)一直被設(shè)置在Button上,那其它當(dāng)實(shí)際焦點(diǎn)改變時(shí),我們的事件處理機(jī)制就不能按照我們的要求正常工作了。給出原文供大家參考:
The below code block shows an example of the event code. Events generally are composed of little code. One tidbit of information I should cover is the Capture property of the control. By setting this to true, the button does not lose input focus when the pointer is outside the button region. This is important because if the mouse button is held down and the user moves the mouse pointer in and out of the button region, the state of the button needs to change accordingly. If the Capture property is not set, the control will stop capturing input events when the pointer leaves the button region.
由此展開的課題:
這只是程序最原始的一個(gè)版本,所有代碼都在一個(gè)源文件中,以后我們可以擴(kuò)展這個(gè)程序,比如預(yù)先定義好好看的樣式,從一個(gè)XML文檔加載我們主題信息,希望大家能好好利用這個(gè)程序,也希望這個(gè)程序真的能給大家?guī)韼椭?/SPAN>
這里有一些額外的說明:
CodeProject這篇文章的源碼是很不錯(cuò)的,值得大家研究研究,代碼其實(shí)很簡單,有這樣的思路才是重點(diǎn),這樣的控件我們也完全能開發(fā)出來。
大家可以好好研究 HelperMethods,其實(shí)這些才是該控件比較精華的代碼。
但是我也發(fā)現(xiàn)了一個(gè)問題,該控件對(duì)設(shè)計(jì)期的支持不夠完善,具體說來就是你改變了某一個(gè)屬性,它并沒有立即在設(shè)計(jì)期反映出來,我研究了一下,發(fā)現(xiàn)它的屬性設(shè)置完成了都沒有這句話,前面我翻譯的一篇文章已經(jīng)強(qiáng)調(diào)了,要想讓你的控件及時(shí)反映出屬性的變化,做出相應(yīng)的反映this.Invalidate()這句話是重點(diǎn),希望大家不能忽視,我已將修改好的源代碼打包,還有一個(gè)Demo程序,這樣就比較完美了。
怎么把這個(gè)不錯(cuò)的控件添加到你的ToolBox中,相信不用我再教一次了,定位到這個(gè)dll就行了;還有如何定制自己的空間圖標(biāo)等等小的方面,參看我的這篇文章吧:http://www.cnblogs.com/jht/archive/