用VS.NET制作功能強(qiáng)大的位圖按鈕控件

2010-08-28 10:50:01來源:西部e網(wǎng)作者:

原文(英文)http://www.codeproject.com/cs/miscctrl/XCtrls.asp

 

系統(tǒng):WindowsXP   P42.2G   內(nèi)存:512MB

環(huán)境:Visual Studio.NET 2003 語言:C#                                                                         源碼下載

 

在開始我們的講解之前先看一下我們位圖Button控件的效果(很漂亮吧。┤绻銓(duì)這個(gè)控件感興趣的話,就跟著我們的腳步學(xué)習(xí)如何做出這樣的一個(gè)控件吧!

XCtrls1.jpg 

簡介:

       創(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

BackColor

background color of the button

按鈕的背景

BorderColor

the color of the thin one pixel width border surrounding the button

圍繞著按鈕的一個(gè)像素寬的邊框的顏色

Font

font used to render the text

按鈕文本呈現(xiàn)的字體

ForeColor

color of button text

按鈕文本的顏色

ImageAlign

specifies the alignment of the image

指定圖像的對(duì)齊方式

ImageBorderColor

If ImageBorderEnabled is true, then this property contains the color of the rendered image border. In addition, the StretchImage property must be false

如果該屬性被設(shè)置為true,這個(gè)屬性就使得圖像邊框?yàn)樵O(shè)置多顏色,但是使用此屬性時(shí),StretchImage必須設(shè)為false

ImageBorderEnabled

true if to render an image border, otherwise false

是否呈現(xiàn)圖像的邊框

ImageDropShadow

true, if to render a shadow around the image border

是否呈現(xiàn)圖像的陰影

ImageFocused

image used to render when button has focus and is in a normal state

按鈕獲得焦點(diǎn)并處于普通狀態(tài)時(shí)呈現(xiàn)的圖像

ImageInactive

image used when button is disabled. Note, if a image is not defined, a gray scale version of the normal image is used in substitution

按鈕不可用時(shí)呈現(xiàn)的圖像,如果沒有被設(shè)置,將呈現(xiàn)灰化的普通狀態(tài)的圖像

ImageMouseOver

image used when the mouse is over the button, but the button is not pressed

鼠標(biāo)移到按鈕時(shí)顯示的圖片

ImageNormal

image used when the button is it its normal state. Note, this image must be set for an image button

普通狀態(tài)的按鈕呈現(xiàn)圖片,必須被設(shè)置

ImagePressed

image used when button is pressed

按鈕按下呈現(xiàn)的圖片

InnerBorderColor

color of the inner border while button is in its normal state

普通狀態(tài)按鈕內(nèi)邊框的顏色

InnerBorderColor_Focus

color of the inner border when the button has focus

按鈕獲取焦點(diǎn)時(shí)內(nèi)邊框的顏色

InnerBorderColor_MouseOver

color of the inner border when the mouse is over a button

鼠標(biāo)移到按鈕上時(shí)內(nèi)邊框的顏色

OffsetPressedContent

If this is set to true and the button is pressed, the contents of the button is shifted.

如果屬性設(shè)置為true,按鈕按下時(shí)替換的內(nèi)容

Padding

It holds the pixel padding amount between each of the button contents. This is the space between the image, text, and border.

圖像、文本和邊框之間的距離(像素)

StretchImage

If true, it indicates to stretch the current image across the button

如果設(shè)置為true,拉伸圖像.

Text

the text to be displayed in the button

按鈕文本

TextAlignment

defines the alignment of the text

按鈕文本對(duì)齊方式

TextDropShadow

If true, the text casts a shadow

如果設(shè)置為true,文本有陰影屬性

 

所有的這些屬性都被加到屬性標(biāo)簽頁了,下面是個(gè)截圖

XCtrls2.jpg
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)邊框

/// <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);
}


繪制背景應(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ù)合顏色的代碼示例:


   

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()方法繪制按鈕文本,在何處使用這個(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)在我們知道接下來需要怎么修改我們的方法了。

      

// FAILED
g.DrawImage(image,rect.Left,rect.Top)
// WORKAROUND
g.DrawImage(image,rect, 00 ,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

OnMouseDown

Set BtnState to Pushed and Capturing mouse to true

設(shè)置Button狀態(tài)為按下,并且將捕獲鼠標(biāo)屬性設(shè)為true

OnMouseUp

Set BtnState to Normal and set CapturingMouse to false

設(shè)置Button狀態(tài)為普通,并且將捕獲鼠標(biāo)屬性設(shè)為false

OnMouseLeave

Set BtnState to normal if we CapturingMouse = true

設(shè)置Button狀態(tài)為普通,如果捕獲鼠標(biāo)狀態(tài)屬性為true

OnMouseMove

If CapturingMouse = true and mouse coordinates are within button region, set BtnState to Pushed, otherwise set BtnState to Normal. If CapturingMouse = false, then set BtnState to MouseOver

簡單的說就是根據(jù)十分捕獲鼠標(biāo)設(shè)置Button狀態(tài)

OnEnabledChanged

The button either became enabled or disabled. If button became enabled, set BtnState to Normal else set BtnState to Inactive

Button是否可用改變時(shí)的方法

OnLostFocus

Set btnState to Normal

失去焦點(diǎn),把Button狀態(tài)屬性變?yōu)槠胀ǖ姆椒?/SPAN>

下面的代碼是一個(gè)簡單的事件處理。這里值得注意的是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();
}


   按鈕是否獲取焦點(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/2005/08/10/211650.html

關(guān)鍵詞:VS.NET