C#發現之旅第五講 圖形開發基礎篇

來源:互聯網
上載者:User
C#發現之旅第五講 圖形開發基礎篇

袁永福 2008-5-15

系列課程說明

    為了讓大家更深入的瞭解和使用C#,我們將開始這一系列的主題為“C#發現之旅”的技術講座。考慮到各位大多是進行WEB資料庫開發的,而所謂發現就是發現我們所不熟悉的領域,因此本系列講座內容將是C#在WEB資料庫開發以外的應用。目前規劃的主要內容是圖形開發和XML開發,並計劃編排了多個課程。在未來的C#發現之旅中,我們按照由淺入深,循序漸進的步驟,一起探索和發現C#的其他未知的領域,更深入的理解和掌握使用C#進行軟體開發,拓寬我們的視野,增強我們的軟體開發綜合能力。

本系列課程配套的示範代碼為 http://files.cnblogs.com/xdesigner/cs_discovery.zip 。其中的EllipseButtonLib.zip 就是本課程的示範代碼。

本系列課程發行的文章有
C#發現之旅第一講 C#-XML開發
C#發現之旅第二講 C#-XSLT開發
C#發現之旅第三講 使用C#開發基於XSLT的代碼產生器
C#發現之旅第四講 Windows圖形開發入門
C#發現之旅第五講 圖形開發基礎篇
C#發現之旅第六講 C#圖形開發中級篇
C#發現之旅第七講 C#圖形開發進階篇
C#發現之旅第八講 ASP.NET圖形開發帶超連結的餅圖
C#發現之旅第九講 ASP.NET驗證碼技術
C#發現之旅第十講 文件物件模型

課程說明

    經過上次Windows圖形開發基本原理的課程,大家對Windows圖形開發有著一些感性的認識,但還可能對此不甚瞭解,還有一些迷茫,在本次課程中,我們將用C#從零開始開發一個比較簡單的橢圓形按鈕的圖形軟體,和大家一起開始探索C#圖形開發。

功能需求

    在本次快速軟體開發中,首先是確定軟體功能需求。

    現有一個客戶,需要一個軟體,其功能要求如下

  1. 實現一個橢圓形的按鈕。可置中顯示一段單行文本。
  2. 滑鼠離開按鈕和進入這個按鈕時,按鈕邊框和背景色需要變化。
  3. 滑鼠點擊按鈕會觸發一個 Click 事件。

    最後產生的軟體的使用者介面

軟體設計

根據功能需求,本軟體設計如下

  1. 橢圓形按鈕是從UserControl 派生的一種自訂控制項。
  2. 控制項內部重寫OnPaint事件來繪製按鈕介面。
  3. 重寫OnMouseMove,OnMouseEnter,OnMouseLeave事件來實現按鈕的動態效果。
  4. 重寫OnClick事件來觸發 Click 事件。

軟體開發過程

    經過簡單的設計,我們開始來開發這個軟體了。

建立C# WinForm.NET工程

    開啟VS.NET2003整合式開發環境。建立立一個C#WinForm.NET程式。客戶最終需要一個組件,但此處為了調試方便,開始使用WinForm.NET應用程式工程模式,開發完畢後可以設定它為DLL工程模式提交給客戶。
    要進行圖形開發,C#工程必須引用 System.Drawing.dll,在新增WinForm.NET過程時,會自動添加該引用,而新增其他類型的工程時可能不會預設添加該引用,此時需要手動添加該引用。圖形編程需要頻繁引用System.Drawing名稱空間中的類型,因此在代碼的開頭需要添加 using System.Drawing ; 不過很多時候VS.NET會自動添加這個代碼,若不自動添加則需要手動添加。

新增控制項

    新增一個名稱為EllipseButton 的使用者控制項。

    首先是定義控制項的一些屬性,主要有邊框色,按鈕背景色,滑鼠懸浮時邊框色和按鈕背景色。

    定義一個滑鼠移至上方標誌變數。 bool bolMouseHoverFlag = false ;

繪製控制項使用者介面

    重寫控制項的OnPaint方法,繪製橢圓形按鈕,其代碼在示範程式中可以看到。在開發自訂的控制項時,可以相應控制項的Paint事件,也可以重寫OnPaint方法,這裡為了代碼結構簡單,此處重寫了OnPaint方法,在重寫該方法時一定要調用基類的 base.OnPaint 方法。

    在重寫的OnPaint 方法中,具有一個類型為 PaintEventArgs 的參數,該參數有若干個成員,其中最重要的就是Graphics成員和ClipRectangle成員,Graphics成員是圖形繪製對象,可以看作一個空白的畫布,可以任意繪製圖形;ClipRectangle成員就是繪製地區剪下矩形。

    在C#圖形開發中,Graphics類型是最重要的類型,它表示一個畫布對象,任何圖形操作都是輸出到這個畫布上。這個類型提供了很多屬性和方法,可以設定某些圖形輸出品質,還提供了一系列的以Draw開頭的方法來繪製圖形,以Fill開頭的方法來填充圖形。此外還提供方法和屬性進行座標轉換。

    ClipRectangle表示剪下矩形,一般情況下,控制項重新繪製內容時是不需要重寫所有的內容,而是繪製一部分內容,該參數就指明控制項中那個部分是需要重新繪製的,該地區以外的介面是不需要繪製,因此該參數是最佳化圖形介面軟體效能的基礎,在這裡,由於橢圓形按鈕繪製的內容少,介面結構簡單,因此不需要最佳化,不需要使用ClipRectangle參數。

    我們重寫的OnPaint函數代碼如下

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint (e);
    // 建立橢圓路徑
    using( System.Drawing.Drawing2D.GraphicsPath path =
                new System.Drawing.Drawing2D.GraphicsPath())
    {
        path.AddEllipse( 0 , 0 , this.ClientSize.Width -1 , this.ClientSize.Height -1 );
        // 填充背景色
        using( SolidBrush b = new SolidBrush(
                    bolMouseHoverFlag ? this.HoverBackColor : this.ButtonBackColor ))
        {
            e.Graphics.FillPath( b , path );
        }
        // 繪製邊框
        using( Pen p = new Pen(
                    bolMouseHoverFlag ? this.HoverBorderColor : this.BorderColor , 2 ))
        {
            e.Graphics.DrawPath( p , path );
        }
    }
    if( this.Caption != null )
    {
        // 繪製文本
        using( StringFormat f = new StringFormat())
        {
            // 水平置中對齊
            f.Alignment = System.Drawing.StringAlignment.Center ;
            // 垂直置中對齊
            f.LineAlignment = System.Drawing.StringAlignment.Center ;
            // 設定為單行文本
            f.FormatFlags = System.Drawing.StringFormatFlags.NoWrap ;
            // 繪製文本
            using( SolidBrush b = new SolidBrush( this.ForeColor ))
            {
                e.Graphics.DrawString(
                    this.Caption ,
                    this.Font ,
                    b ,
                    new System.Drawing.RectangleF(
                    0 ,
                    0 ,
                    this.ClientSize.Width ,
                    this.ClientSize.Height ) ,
                    f );
            }
        }
    }
}//protected override void OnPaint(PaintEventArgs e)

    在這個方法中,我們首先建立了一個 GraphicsPath 對象,這個對象表示一個路徑,所謂路徑就是若干個直線和曲線的組合。我們可以向路徑對象中添加各種直線段或曲線。在這裡我們調用它的 AddEllipse 方法向路徑中添加了一個橢圓曲線,這是一個封閉曲線。AddEllipse 方法的參數表示一個橢圓的外切矩形。在這裡外切矩形就是控制項的用戶端區域。

    所謂客戶區就是控制項內部可以自訂繪製圖形的地區。某些Windows控制項具有邊框,比如文本輸入框,邊框上面是不能繪製圖形的,因此若控制項有邊框則它的客戶區大小不等於控制項大小,此時需要使用控制項的 ClientSize 屬性獲得控制項客戶區大小,當然若控制項沒有邊框,則它的客戶區大小等於控制項大小,為了編程方便,建議大家以後繪製控制項內容時都使用 ClientSize 屬性獲得可繪製地區的大小。

    建立了一個橢圓路徑後,我們可以使用繪製橢圓形了,首先是建立一個 SolidBrush 對象,然後調用圖形繪製對象的FillPath方法來填充路徑。然後建立 Pen 對象,使用Graphics的DrawPath方法來繪製路徑。這裡要注意順序不能搞反。若先繪製邊框然後填充橢圓,則會導致後面的操作覆蓋掉前面的操作成果。

    圖形編程有一個很明顯的特點,那就是各種圖形操作是要注意順序的,因為後一個圖形操作很容易覆蓋掉前面的圖形操作結果,這造成了圖形開發中調試困難,很多時候需要對代碼進行非常仔細的靜態檢查。

    很多圖形編程對象,例如SolidBrush,Pen,GraphisPath等等,都實現了System.IDisposable介面,其內部都使用了非託管資源,在不使用的時候要銷毀這些對象,因此在代碼中使用了 using 文法結構來處理這些對象。

    這裡我們使用滑鼠移至上方標誌變數 bolMouseHoverFlag ,使得滑鼠移至上方和不懸停時按鈕的背景色和邊框色有所不同。

    繪製出橢圓地區後,我們就可以繪製按鈕文本。首先建立一個 StringFormat 對象,這個對象用於控制繪製文本時的樣式。我們設定文字格式設定為水平置中對齊,垂直置中對齊樣式,而且還不能換行,只能顯示單行文本。

    我們根據文本顏色建立一個SolidBrush對象,然後繪製文本,然後調用圖形繪製對象的 DrawString 方法來繪製字串。這個函數第一個參數是常值內容,第二個是字型,第三個就是繪製文本使用的畫刷對象,第四個就是包含文本顯示地區的矩形地區,第5個就是文字格式設定控制。

    完成了OnPaint方法後,我們就獲得了一個具有橢圓形外觀的使用者控制項,我們編譯器,然後進入一個表單設計器,在工具箱的“我的使用者控制項”欄目,上面可以看到已經有一個 EllipseButton 項目,按下這個項目就可以在表單上放置一個橢圓形的按鈕了,你可以在屬性列表中設定它的文本。然後運行程式,可以看到啟動並執行表單上顯示了一個橢圓形的按鈕,但這個按鈕就像圖片一樣,毫無生機,我們還需要改進這個控制項來實現動態效果。

響應事件,實現動態效果

    開啟這個按鈕控制項的代碼,開始添加代碼來實現滑鼠移至上方的動態效果。首先編寫一個 CheckMouseHover 函數,該函數用於判斷滑鼠是否懸停到按鈕上面,由於按鈕是橢圓形,控制項上有部分內容不屬於按鈕地區,因此即使滑鼠在控制項上面,也要判斷滑鼠游標是否在橢圓形地區中。CheckMouseHover函數代碼如下

/// <summary>
/// 檢測釋放發生滑鼠移至上方狀態發生改變,若發生改變則重寫繪製控制項
/// </summary>
/// <param name="x">測試點X座標</param>
/// <param name="y">測試點Y座標</param>
/// <returns>測試點是否在橢圓地區中</returns>
private bool CheckMouseHover( int x , int y )
{
    using( System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
    {
        path.AddEllipse( 0 , 0 , this.ClientSize.Width -1 , this.ClientSize.Height -1 );
        bool flag = path.IsVisible( x , y );
        if( flag != bolMouseHoverFlag )
        {
            bolMouseHoverFlag = flag ;
            // 控制項整體無效,準備重新繪製,但不立即繪製使用者介面.
            this.Invalidate();
            //this.Refresh(); // 強制立即繪製使用者介面.
        }
        return flag ;
    }
}

    我們建立一個路徑對象,向該路徑添加橢圓地區,然後調用路徑的 IsVisible 函數判斷指定點是否包含在這個路徑中,若不包含在路徑中,則該點不在橢圓形按鈕上面。若這次判斷的結果和上次判斷的結果不相同,則設定滑鼠移至上方狀態變數,然後重新繪製按鈕。

    代碼中重新繪製控制項具有兩種選擇,一個是調用控制項的 Invalidate 方法,另外可調用 Refresh 方法。兩者都能重新繪製使用者介面,但是有差別的。Invalidate方法是聲明控制項使用者介面一部分或全部無效,但不會導致立即重新繪製使用者介面,而是延遲一段時間後才真正的重新繪製使用者介面,可以看作是一種非同步作業;而Refresh則是立即重新繪製使用者介面,繪製完畢後才結束Refresh方法,是一種同步操作。

    在一般情況下Invalidate函數導致的延遲時間很短暫,人類無法察覺,此時應當調用Invalidate方法;但在少數情況下使用Invalidate會導致明顯的可察覺的延遲,則需要使用 Refresh 方法。Invalidate導致的延遲時間的長短和Windows底層訊息驅動機制有關,這裡看出比較精細的圖形編程和Windows底層是有關聯的,Invalidate方法是Win32API函數InvalidateRect的.NET封裝,而Refresh方法是Win32API函數UpdateWindow的封裝。查閱MSND中關於這兩個API函數的說明就可以理解為什麼會出現這種情況。

    微軟提出.NET架構目的是讓開發人員脫離Windows底層API來進行快速軟體開發,這個目標在ASP.NET中得到的相當好的實現,因此常規的Web資料庫開發中是不會用到Win32API的。但在圖形開發中,.NET架構仍然很大程度的依賴Win32API函數,.NET圖形相關類庫中有很多部分是Win32API的封裝,這方面和VC的MFC架構有點類似,VC的MFC個人認為是傻大黑粗,功能是強大,可是使用很不方便,而.NET架構中包含了一個充滿靈性的MFC,使用方便,功能也不弱,但仍然是基於Win32API的。因此要很深入的學習.NET圖形編程,就要求對Win32API有所瞭解,這也加大了.NET圖形編程的學習難度。當然比較簡單的.NET圖形編程是不需要瞭解Win32API的。

    在這裡也反映出圖形開發中對使用者體驗的一些特殊要求。圖形軟體需要在電腦螢幕上繪製圖形,而人類由於其生理特點,各種感覺器官和運動器官的速度是不同的,大腦思維反應最遲鈍,手操作鍵盤和滑鼠速度一般,而人眼的反映速度是很快的,能感知螢幕上幾十毫秒內發生的變化,由於人眼具有很高的反應速度,因此對圖形軟體的圖形繪製代碼運行速度有很高的要求。

    重寫控制項的OnMouseMove 方法,處理滑鼠移動事件,該事件處理中,只是簡單的調用CheckMouseHover 成員,參數就使用滑鼠游標位置。

    控制項提供了一系列的以OnMouse開頭的方法都是處理滑鼠事件的,該方法有一個類型為 MouseEventArgs 的參數,該參數具有一些屬性,列出了發生滑鼠事件時的滑鼠按鍵狀態,滑鼠滾輪計數和滑鼠游標在控制項客戶區中的位置。

    控制項還重寫 OnMouseLeave 方法,處理滑鼠離開控制項客戶區的事件,取消控制項的滑鼠移至上方狀態。

觸發Click事件

    客戶要求滑鼠按下這個橢圓形按鈕需要觸發一個事件,我們選擇了控制項本身具有的Click事件作為按鈕點擊事件,於是我們重寫了OnClick函數,該函數代碼為

/// <summary>
/// 處理按一下滑鼠事件
/// </summary>
/// <param name="e"></param>
protected override void OnClick(EventArgs e)
{
    //base.OnClick (e);
    Point p = System.Windows.Forms.Control.MousePosition ;
    p = base.PointToClient( p );
    if( CheckMouseHover( p.X , p.Y ))
    {
        base.OnClick( e );
    }
}

    由於按鈕是橢圓形的,當使用者滑鼠點擊控制項時,要判斷點擊點是否在橢圓形地區中,從而要判斷是否需要觸發Click事件。因此我們重寫 OnClick 方法來處理控制項的 Click 事件。

    OnClick方法的參數沒有指明滑鼠游標位置,因此我們自己計算滑鼠游標在客戶區中的位置,我們使用Control類型的MousePosition靜態屬性,獲得滑鼠游標在電腦螢幕中的位置,然後使用控制項的PointToClient函數將這個座標從電腦螢幕座標轉換為控制項客戶區座標,然後調用CheckMouseHover函數判斷這個座標是否在橢圓形地區中,若滑鼠在橢圓形地區中,則調用base.OnClick方法,觸發Click事件。

測試控制項

    重新編譯器,建立一個表單,開啟表單設計器,在工具箱的我的使用者控制項頁面中可以看到有一個EllipiseButton的使用者控制項,若沒有則滑鼠右擊工具箱,選擇功能表項目“添加/移除項目”。在對話方塊中點擊瀏覽選擇剛剛編譯產生的EXE或DLL檔案,然後選中EllipiseButton即可在工具箱上新增EllipseButton項目。選中橢圓形按鈕,設定屬性列表為顯示控制項事件,雙擊添加控制項的Click事件,在該事件中顯示一個訊息框,然後編譯運行即可看到一個具有動態效果的橢圓形按鈕。如此這個按鈕控制項編寫完畢。

    我們設定工程類型為DLL樣式,重新編譯,得到一個DLL檔案,這個DLL檔案就可以提交給客戶使用了。

小結

    在本次課程中,我們使用了C#開發了一個很簡單的具有動態效果的橢圓形按鈕的小工具,示範了C#圖形開發的基本過程,使得大家能對C#圖形開發有一個初步的印象。從這個小程式可以看出,代碼是不多的,但所需的基本知識是比較多的,軟體的設計,開發和WEB資料庫開發有著很大的不同。最後我希望大家能在今天的程式的基礎上,實現一個三角型的按鈕控制項。

    在下一次課程中,我們繼續使用C#開發一個稍微複雜的圖形軟體,從而更深入的進行C#圖形開發的探索。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.