程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#發現之旅第五講 圖形開發基礎篇

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

編輯:關於C#

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

本文配套源碼,其中的 EllipseButtonLib.zip 就是本課程的演示代碼。

課程說明

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

功能需求

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

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

實現一個橢圓形的按鈕。可居中顯示一段單行文本。

鼠標離開按鈕和進入這 個按鈕時,按鈕邊框和背景色需要變化。

鼠標點擊按鈕會觸發一個 Click 事件。

最後生成的軟件的用戶界面如圖所示

軟件設計

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

橢圓形按鈕是從UserControl 派生的一種自定義控件。

控件內部重寫OnPaint事件來繪制按鈕界面。

重寫OnMouseMove, OnMouseEnter,OnMouseLeave事件來實現按鈕的動態效果。

重寫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#圖形開發的探 索。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved