具體而微的繪圖程式-c++ Borland 入門

  在本章中我將爲你示範如何在C++Builder中撰寫一個完整的繪圖程式。藉由這個程式的撰寫,你會更加了解C++Builder的 Canvas 繪圖精神,而在撰寫這個程式的同時,我們也可將相關的技術做一個整體的檢閱。此繪圖程式的執行結果如下:
  
具體而微的繪圖程式-c++ Borland 入門

  點擊查看大圖
  
  在此程式中我會以循序漸進的方式一步一步地帶領你完成整個程式,基本上這個程式和C++Builder內附的範例程式有幾分類似,但我必須要說明的是:在 C++Builder中所附的範例程式是直接由原先在Delphi內以 Object Pascal 所撰寫的範例程式修改而成,所以有部份程式的寫法大爲違反C++ 式物件導向精神,在邁入C++Builder 的新世紀之後,我們當然希望寫出的程式是『系出名門,血統純正』的C++ 式的物件導向程式。而這就是我在本章中希望帶領你完成的程式。
  XX-01 關於滑鼠事件(Mouse Event)
  撰寫繪圖程式,首先要了解滑鼠事件,在Windows中定義了許多的滑鼠訊息(Message),而這些滑鼠訊息在BCB中就成爲滑鼠事件了,爲了要處理滑鼠事件,我們必須要選寫滑鼠事件處理程式:
  在Windows中定義的滑鼠訊息列表
  WM_CAPTURECHANGED
  WM_LBUTTONDBLCLK
  WM_LBUTTONDOWN
  WM_LBUTTONUP
  WM_MBUTTONDBLCLK
  WM_MBUTTONDOWN
  WM_MBUTTONUP
  WM_MOUSEACTIVATE
  WM_MOUSEMOVE
  WM_NCHITTEST
  WM_NCLBUTTONDBLCLK
  WM_NCLBUTTONDOWN
  WM_NCLBUTTONUP
  WM_NCMBUTTONDBLCLK
  WM_NCMBUTTONDOWN
  WM_NCMBUTTONUP
  WM_NCMOUSEMOVE
  WM_NCRBUTTONDBLCLK
  WM_NCRBUTTONDOWN
  WM_NCRBUTTONUP
  WM_RBUTTONDBLCLK
  WM_RBUTTONDOWN
  WM_RBUTTONUP
  表XX-01 Windows內滑鼠相關 Message。
  雖然在Windows作業系統中定義了非常多的訊息,但是在C++Builder 中已經把龐大的訊息系統作適度的簡化了,並且不再以訊息的方式存在,而改以事件 (Event)的處理方式,在本章的繪圖程式中,我們只要處理以下的幾個事件即可:
  OnMouseDown 滑鼠鍵按下事件
  OnMouseMove 滑鼠移動事件
  OnMouseUp 滑鼠鍵放開事件
  OnClick 任何滑鼠的點取
  在此,你可以很明顯地發現,在C++Builder的事件中並未將左右滑鼠鍵分別定義,而是以合並處理的方式,因此在收到以上滑鼠事件時,若你要分辨左右滑鼠事件時,必須在事件處理程式中判定左右鍵。
  具備了基本的滑鼠事件認知後,我們開始進行後續的程式探索吧!
  爲了讓你實際了解程式的細節,我希望將程式撰寫的步驟細節交代楚,在往下進行之前,我們先建立一個新的專案檔,並將其命名爲 DrawMain,同時將Form的Color性質設爲黑色(clBlack),以便直接在上面畫圖。
  XX-02滑鼠事件的處理
  當C++ Builder應用程式偵測到物件滑鼠事件時,它會檢查你是否定義該物件相對應的滑鼠事件處理程式,然後呼叫該函數,將相關參數傳給它。以OnMouseDown事件爲例,它的事件處理程式模版如下:
  void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  }
  它總共接收了以下幾個參數:
  Sender 引發該事件的軟體元件。
  Button 表示滑鼠的按鍵。它的值可爲mbLeft(左鍵),mbRight(右鍵),mbMiddle(中間鍵)。
  Shift 用以表示事件發生的同時Alt,Shift及Ctrl叁鍵的狀態。
  X,Y 用以表示事件發生時之座標位置。
  在大多數的情況下,滑鼠事件的(X,Y)座標值是我們最爲感愛好的項目,不過,有時候我們也需要靠Button鍵來判定滑鼠的按鍵,或是需要利用Shift來取得非凡鍵的狀態,而做一些額外的程式處理。
  XX-02-01 OnMouseDown事件的處理
  首先我們先以一個最基本的畫線程式來說明OnMouseDown事件的處理,當使用者按下滑鼠時,我們希望將筆移至事件發生時的坐標,因此我們可將程式寫成如下:
  void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  Canvas->MoveTo(X,Y);
  }
  XX-02-03 OnMouseUp事件的處理
  同樣地,我們可以再爲這個Form加上OnMouseUp的事件處理函式,在收到OnMouseUp事件時,由滑鼠點下的坐標,畫一條直線至現在的坐標。
  
  
  
  void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  Canvas->LineTo(X,Y);
  }
  在寫完了以上兩個事件處理函式之後,我們就可以在Form上面作畫了,你可以用滑鼠在Form上面拖戈出一條條直線。其執行結果大致如圖XX-01:
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-01
  XX-02-02 OnMouseMove事件的處理
  在加上了OnMouseDown及OnMouseUp處理函式之後,我們只能畫出一條條直線,若是我們想要以滑鼠畫出不規則線段時,就必須再處理OnMouseMove事件,利用OnMouseMove事件,我們可以追縱到滑鼠移動的位置,簡單的OnMouseMove事件處理函式如下:
  void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,
  int Y)
  {
  Canvas->LineTo(X,Y);
  }
  此程式的意義即在於將滑鼠所經過的每個點,以線條連接起來,在加上OnMouseMove 事件處理函式之後,它的執行結果會變成圖XX-02:
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-02
  XX-02-03 滑鼠的處理的加強
  前面的程式對於滑鼠的移動處理有部份考慮的不夠周詳,因爲它在滑鼠移動時不分青紅皂白就將線畫在螢慕上,造成螢幕上的線條混亂,這並不是正規的處理方法,正確的處理方法應該如下:
  (1) 滑鼠鍵按下時,將記錄滑鼠按下的旗標設爲True.同時將該點記錄下來,謂之原點。
  滑鼠移動時,判定滑鼠按下的旗標是否設爲 True,若爲 True,則移動至原點,並畫一條由原點至目前所在點的線。同時更新原點位置至目前所在之點。
  滑鼠放開時,將記錄滑鼠按下的旗標設爲False。
  以下就是關於叁個滑鼠事件的處理程式碼。
  // 滑鼠按下的事件處理函式
  // 1. 將旗標設爲True
  // 2. 記錄原點位置
  void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  m_bDraw = TRUE;
  m_nOrgX=X;
  m_nOrgY=Y;
  }
  // 滑鼠移動的事件處理函式
  // 1. 判定旗標是否爲True。若是則進行以下動作。
  // 2. 移動至原點。
  // 3. 畫一條由原點至目前所在點的線條。
  // 4. 更改原點位置。
  void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift,
  int X, int Y)
  {
  if (m_bDraw)
  {
  Canvas->MoveTo(m_nOrgX,m_nOrgY);
  Canvas->LineTo(X,Y);
  m_nOrgX = X;
  m_nOrgY = Y;
  }
  }
  // 滑鼠放開的事件處理函式
  // 1. 判定旗標是否爲True。若是則進行以下動作。
  // 1. 將旗標設爲 False。
  // 2. 畫線並記錄原點位置(非必要)。
  void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  if (m_bDraw)
  {
  m_bDraw=FALSE;
  Canvas->MoveTo(m_nOrgX,m_nOrgY);
  Canvas->LineTo(X,Y);
  m_nOrgX = X;
  m_nOrgY = Y;
  }
  }
  將滑鼠事件處理函式做以上的修改之後,我們就完成了一個基本的塗鴉程式的雛形了。我將此表格的背景設爲黑色,筆的狀態設爲2單位寬度的紅色筆,就得到以下的輸出結果:
  
具體而微的繪圖程式-c++ Borland 入門

  XX-03 繪圖物件的定義
  至目前爲止,我們已經完成了一個簡單的塗鴉程式,接下來,我希望將程式擴充爲一個一般的繪圖程式,它必須具備基本的畫線、畫圓、畫方等功能。爲了要實作出這些功能,我們必須先定義我們的繪圖物件。
  XX-03-01 繪圖物件之始CShape
  class CShape
  
  
   {
  PRotected:
  TCanvas* m_pCanvas;
  TColor m_Color;
  int m_nWidth;
  public:
  CShape(TCanvas* pCanvas) {m_pCanvas = pCanvas;}
  virtual ~CShape() {}
  virtual void OnMouseMove(int,int)=0;
  virtual void OnMouseDown(int,int)=0;
  virtual void OnMouseUp(int,int)=0;
  };
  我們首先定義一個CShape類別,它是所有繪圖物件之始,也因此它定義了一個繪圖物件的基本行爲。在此繪圖程式中我希望它可以處理叁個不同的滑鼠事件並加以處理之,所以我在CShape中定義了叁個相對應的成員函式,而且它們都是純虛擬函式,表示所有繼續自CShape的類別都必須改寫此叁個成員函式。 (關於物件導向的關念請參閱 <必要的C++ 基礎章節> 或是相關書籍,在此盡作簡短的解釋)。這叁個函式名稱稱如下:
  virtual void OnMouseMove(int,int)=0;
  virtual void OnMouseDown(int,int)=0;
  virtual void OnMouseUp(int,int)=0;
  另外我們再定義一般性的繪圖物件都會用到的基本特性,如顔色及線條寬度,再加上繪圖時所需要的 Canvas,如此就組成了CShape的類別定義:
  TCanvas* m_pCanvas; // 繪圖所需的Canvas
  TColor m_Color; // 顔色
  int m_nWidth; // 寬度
  至於CShape的解構函式爲何也設成virtual呢?這關系到繼續物件的毀滅方法。若是基礎類別的解構函式沒有定義成虛擬函式時,會造成特定情況下,子類別的解構函式沒有被呼叫到的情形:
  如:
  CLine *pLine = new Line;
  CShape* pShape=pLIne;
  delete pShape;
  上述的例子因爲CLine爲CShape的子類別,因此可以直接將pShape指標指向pLine,然而在後面delete pShape時,若是pShape的解構函式不爲虛擬函式,會造成pLine的解構函式不被呼叫到。這是一般C++ 程式設計時很輕易犯的錯誤。
  我們可以將以上的經驗法則歸納成一個原則,即是:只要該類別有可能被繼續,就必須將其解構函式設爲虛擬函式。如此就有了以下的定義了:
  CShape(TCanvas* pCanvas) {m_pCanvas = pCanvas;}
  virtual ~CShape() {}
  CShape的建構函式必須傳入Canvas以便繪圖,而解構函式則不做任何事,只將其定義爲虛擬函式。
  XX-03-02 CLine類別定義及實作 畫直線的類別
  class CLine : public CShape
  {
  public:
  POINT m_ptMove;
  POINT m_ptOrigin;
  public:
  CLine(TCanvas* pCanvas):CShape(pCanvas) {}
  virtual ~CLine() {}
  virtual void OnMouseMove(int,int);
  virtual void OnMouseDown(int,int);
  virtual void OnMouseUp(int,int);
  };
  我們將CLine定義爲一個畫直線的類別,而我們希望在畫直線時可以在拖弋滑鼠時將原先的線條擦去,並畫出新的線,因此我們必須宣告兩個點來記載滑鼠按下的點及上次的點以便擦去原來的線條。
  以下就是CLine對於叁個滑鼠事件的處理函式:
  // 滑鼠按下的事件處理函式
  // 1. 設定原點及上個啓始點爲目前所在點。
  // 2. 移動至目前所在點。
  void CLine::OnMouseDown(int x,int y)
  {
  m_ptOrigin.x = m_ptMove.x = x;
  m_ptOrigin.y = m_ptMove.y = y;
  m_pCanvas->MoveTo(x,y);
  }
  // 滑鼠移動事件處理函式
  // 1.將畫筆模式設爲XOR模式,以便擦去上一條線。
  // 2.擦去原來的線(以XOR模式再畫一次就會擦去了)
  // 3.在目前的位置畫出一條新線。
  // 4.更新坐標並改變畫筆模式。
  void CLine::OnMouseMove(int x,int y)
  {
  m_pCanvas->Pen->Mode = pmXor;
  m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
  m_pCanvas->LineTo(m_ptMove.x,m_ptMove.y);
  m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
  m_pCanvas->LineTo(x,y);
  m_ptMove.x = x;
  m_ptMove.y = y;
  m_pCanvas->Pen->Mode = pmCopy;
  }
  // 滑鼠放開事件處理函式
  // 1.畫出原點至目前點的直線。
  void CLine::OnMouseUp(int x,int y)
  {
  
  
   m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
  m_pCanvas->LineTo(x,y);
  }
  這就是畫直線類別的定義及實作內容。
  XX-03-03 CPolyline類別定義及實作 畫隨意線的類別
  class CPolyline : public CShape
  {
  public:
  POINT m_ptOrigin;
  public:
  CPolyline(TCanvas* pCanvas):CShape(pCanvas) {}
  virtual ~CPolyline() {}
  virtual void OnMouseMove(int,int);
  virtual void OnMouseDown(int,int);
  virtual void OnMouseUp(int,int);
  };
  CPolyline類別其實和我們前面所寫的塗鴉程式的行爲模式極爲類似,所以我就簡單帶過好了。
  void CPolyline::OnMouseDown(int x,int y)
  {
  m_ptOrigin.x = x;
  m_ptOrigin.y = y;
  m_pCanvas->MoveTo(x,y);
  }
  void CPolyline::OnMouseMove(int x,int y)
  {
  m_pCanvas->LineTo(x,y);
  }
  void CPolyline::OnMouseUp(int x,int y)
  {
  m_pCanvas->LineTo(x,y);
  }
  XX-03-04 CPolygon類別定義及實作 畫多邊形的類別
  class CPolygon : public CPolyline
  {
  public:
  CPolygon(TCanvas* pCanvas):CPolyline(pCanvas){}
  virtual ~CPolygon() {}
  virtual void OnMouseUp(int,int);
  };
  CPolygon是CPolyline的子類別,其差別僅在於它會將首尾兩點連接,使其成爲一個多邊形,因此我們就直接由CPolyline繼續而來,只改寫其OnMouseUp成員函式即可。
  void CPolygon::OnMouseUp(int x,int y)
  {
  m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
  m_pCanvas->LineTo(x,y);
  }
  XX-03-05 CRectangle類別定義及實作 畫矩形的類別
  class CRectangle : public CShape
  {
  public:
  POINT m_ptMove;
  POINT m_ptOrigin;
  public:
  CRectangle(TCanvas* pCanvas):CShape(pCanvas) {}
  virtual ~CRectangle() {}
  virtual void OnMouseMove(int,int);
  virtual void OnMouseDown(int,int);
  virtual void OnMouseUp(int,int);
  };
  畫矩形類別其實和畫線類別有些類似,它們同樣必須記載上次滑鼠移動的點,並擦掉原來的圖形畫出新的圖形,所以我只針對其相異的部份加以說明之:
  // 滑鼠移動事件處理函式
  // 原理和CLine類似,只不過改成畫矩形。
  void CRectangle::OnMouseMove(int x,int y)
  {
  m_pCanvas->Pen->Mode = pmXor;
  m_pCanvas->Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.y);
  m_ptMove.x = x;
  m_ptMove.y = y;
  m_pCanvas->Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.y);
  m_pCanvas->Pen->Mode = pmCopy;
  }
  XX-03-06 CRoundRect類別定義及實作 畫圓矩形的類別
  class CRoundRect : public CShape
  {
  public:
  POINT m_ptMove;
  POINT m_ptOrigin;
  public:
  CRoundRect(TCanvas* pCanvas):CShape(pCanvas) {}
  virtual ~CRoundRect() {}
  virtual void OnMouseMove(int,int);
  virtual void OnMouseDown(int,int);
  virtual void OnMouseUp(int,int);
  };
  CRoundRect的實作幾乎和Crectangle相同,只不過它們呼叫不同的API罷了,CRoundRect是以Canvas->RoundRect來畫出圖形的。
  void CRoundRect::OnMouseMove(int x,int y)
  
  
   {
  m_pCanvas->Pen->Mode = pmXor;
  m_pCanvas->RoundRect(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.y,4,4);
  m_ptMove.x = x;
  m_ptMove.y = y;
  m_pCanvas->RoundRect(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.y,4,4);
  m_pCanvas->Pen->Mode = pmCopy;
  }
  XX-03-07 CEllipse 類別定義及實作 畫圓形的類別
  畫圓形的處理和畫矩形也大致相同,因爲在Windows中是以包圍矩形來定義一個圓形,因此和CRoundRect相同的,我們只要改寫成畫圓函式即可。其馀我就不多說了。
  class CEllipse : public CShape
  {
  public:
  POINT m_ptMove;
  POINT m_ptOrigin;
  public:
  CEllipse(TCanvas* pCanvas):CShape(pCanvas) {}
  virtual ~CEllipse() {}
  virtual void OnMouseMove(int,int);
  virtual void OnMouseDown(int,int);
  virtual void OnMouseUp(int,int);
  };
  XX-03-08小結
  以上就是此繪圖程式中所使用的各個物件的定義,此乃血統純正的C++ 寫法的程式,不像C++Builder官方的範例是由Delphi的範例修改而來,布滿了Object Pascal的味道。
  若你對C++ 尚不太熟悉的話,請你一定要細細領略以上的精神。因爲它是C++ 式的物件導向程式最基本且精要的精神所在,當你了解了以上的精神,你就可謂把握了C++ 的封裝、繼續、及動態連結這叁把權仗的基本精神。
  至於C++ 老手,以上的定義都是很自然就可以接受的。也許有人會質疑以上的物件定義並未考慮到物件的永續性 (Object Persistence)。沒錯,不過這並不是我疏忽了,而是在本章的程式中圖形的存取是以Timage來存取,因此所有向量式的物件都已轉化成點陣圖了,自然不需考慮到物件的儲存問題。
  在後續章節,我還會再針對物件的永續性來做一討論。現在我們先就TImage的點陣圖存取方式爲平台討論之。
  最後,在完成了物件的定義之後,我們再將程式根據物件導向的方式再加以改寫之。因爲我目前尚未加入選擇物件的方法,所以我只能用預設物件型態的方式來展示程式的結果。
  // 表格建構函式,設定m_bDraw旗標初值
  __fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
  {
  m_bDraw = FALSE;
  }
  // Form的OnCreate事件處理函式。Form建立時引發。
  // 1.設定筆的顔色及寬度。
  // 2.産生一個CLine繪圖物件。
  // 注:你可以自行修改CLine成CPolyline、CPolygon、CRect等值。
  void __fastcall TForm1::FormCreate(TObject *Sender)
  {
  Canvas->Pen->Color = clRed;
  Canvas->Pen->Width = 2;
  m_pObj = new CLine(Canvas);
  }
  // Form的OnClose事件處理函式。Form關閉時引發。
  // 1.殺掉繪圖物件。
  void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
  {
  delete m_pObj;
  }
  // 更改後的OnMouseDown物件處理函式。
  // 1.將m_bDraw旗標設爲 TRUE。
  // 2.呼叫繪圖物件的OnMouseDown函式。
  void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
  TShiftState Shift, int X, int Y)
  {
  m_bDraw = TRUE;
  m_pObj->OnMouseDown(X,Y);
  }
  // 更改後的OnMouseMove物件處理函式。
  // 1.判定m_bDraw旗標是否爲 TRUE。
  // 2.若是則呼叫繪圖物件的OnMouseMove函式。
  void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift,
  int X, int Y)
  {
  if (m_bDraw)
  m_pObj->OnMouseMove(X,Y);
  }
  // 更改後的OnMouseUp物件處理函式。
  // 1.將m_bDraw旗標設爲 FALSE。
  // 2.若是則呼叫繪圖物件的OnMouseUp函式。
  void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
  
  
   TShiftState Shift, int X, int Y)
  {
  m_bDraw = FALSE;
  m_pObj->OnMouseUp(X,Y);
  }
  瞧!這就是更改後的程式,是不是變得格外簡潔呢?除此之外,它最大的優點在於,無論我們日後加入了多少繪圖物件,你都不需再修改以上程式中關於繪圖物件的處理部份,只要再自行增加一個物件宣告即可。和原先Borland公司産品內附由Object Pascal修改而來的範例,它的C++ 血統純正多了。而且若是日後你想要將其修改成爲一個物件式的繪圖系統,也只需要很簡單的修改而已。
  好吧!讓我們先檢閱現在的成果。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-04 CLine繪圖物件範例。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-05 CPolyline繪圖物件範例(將程式改成new CPolyline)
  XX-04 工具列(ToolBar)的使用
  到目前爲止我們已經將所有繪圖物件定義完成,因此理論上你的程式應該可以畫出各種不同的繪圖物件了。但是前面我提到,目前我們尚未將繪圖物件的選擇功能實作出來,因此我們是以直接修改程式的方式來繪制不同的圖形。這是爲了說明方便的權宜之計。
  在一般的繪圖程式中都是以工具列的方式來實作出繪圖功能的切換功能,如Windows 95內的小畫家即是一典型例子。因此接下來我就爲你說明在C++ Builder中實作出工具列的方法。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-06小畫家使用的工具列
  在C++ Builder中實作工具列的方式和其他的程式如Visual C++,Borland C++ 不同。後兩者都是直接使用Windows 95內建的工具列型別來達到此功能。然而在C++ Builder中因爲有一種更爲簡單且直接的方式來做到,因此就不采用上述作法 (當然C++ Builder也可以用Windows 95內建的ToolBar型別,只是用法較爲複雜。)。
  那麽在C++ Builder中是如何來實作出工具列呢?說穿了其實很簡單:那就是利用TPanel和TSpeedButton。
  CPanel是一個多功能的容器元件,因此我們可以用它來做爲工具列的平台,使用CPanel是因爲它是少數幾個可做爲容器元件的元件,所以它會自動調整置於其上的軟體元件的位置,因此很適合做爲放置工具列的平台。
  注:在C++ Builder的程式模式中大量使用TPanel來做爲容器元件。它除了可以做爲ToolBar的平台外,另外如狀態列 (StatusBar)也可以用它來完成,而且它也可以用來做爲畫面分割的工具,來達成在MFC中類似分割視窗(Splitter Window)效果。
  TSpeedButton快速按鈕元件在功能上本來就和工具列有幾分類似,現在我們可以將相同屬性的快速按鈕元件整合在一個TPanel中即可完成我們所要的工具列了。
  最後我再將工具列的作法按部就班詳述之:
  (1) 在表格上加入TPanel元件。
  設定TPanel的Align性質爲 alTop。因爲我們希望工具列置於表格上方,所以將它設定爲浮貼於表格的上方。如此一來當表格大小改變時,工具列的寬度爲跟著改變,而高度則維持原先的高度。
  將TSpeedButton加入TPanel原件上。
  你可以在表格中加入多個上述的工具列,它們會依序自動排列於表格的上方,因此你不需費心去處理這些額外的動作。
  XX-04-01 TSpeedButton 元件解析及設定
  ToolBar的幾個基本要素是:
  (1) 代表該功能的圖形。
  (2) 可依狀況切換其狀態。
  當使用者點取該功能時,必須執行該功能。
  我們來看看TSpeedButton如何達到以上的要求。
  首先,TSpeedButton具備Glyph性質,可以指定其圖形,所以第一個要求不成問題,再來TSpeedButton具備以下叁種狀態:
  一般按鈕的功能。
  可以除能/致能。
  具備群組特性。(也就是說同一群組的TSpeedButton會互相影響,因此可輕易做出互斥的功能,以繪圖程式爲例,一次只能使用一種工具,因此當使用者選取工具時,除了被選取的工具之外,其他的工具應該都呈浮起狀態)
  由上可知,TSpeedButton確實是實作ToolBar的適當人選。
  XX-04-02將SpeedButton加入ToolBar
  依照我們先前的需求,我們需要一個可以切換繪圖工具的工具列,因此我們就照前面所說的方式來完成它。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-07工具列
  圖XX-07就是我所加入的SpeedButton,除此之外還有幾個程序必須完成
  爲SpeedButton命名。取一個有意義的名字。
  依需要設定其高度及位置。
  設定圖形。
  設定SpeedButton狀態初值。
  設定群組特性。
  
  
   ◎ 設定事件處理函式。
  SpeedButton的命名原則和一般變數的命名原則相同,簡單明了就好。以本程式爲例,我們就可以LineButton、RectangleButton等名字命名之。命名時只要改變SpeedButton的Name性質即可。
  至於設定圖形,只要先選取該SpeedButton,然後至物件檢視器點取Glyph性質,然後將欲選取的點陣圖Load進來,即可完成設定圖形的程序了。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-08 Glyph圖形之選取。
  設定狀態初值:由於我們希望本程式執行的初始值是使用CLine元件,因此我將LineButton的Down屬性爲True,其馀則爲 False。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-09選取後的狀態
  設定群組特性:群組特性是SpeedButton用以實作出ToolBar的重要功能之一。我們可以將一群SpeedButton設爲同一群組,如此一來在此一群組的SpeedButton就具備了互斥特性,也就是說在任何一個SpeedButton按下時,會導致其他的SpeedButton浮起。此爲實作ToolBar的必備條件,而利用SpeedButton可以輕易達成此目的。
  設定群組特性其實很簡單,只要把該群組的SpeedButton的GroupIndex性質設爲相同數字即可,在此例中我們希望將所有繪圖工具列按鈕設爲同一群組,因此我把該相關工具的GroupIndex性質都設爲1。
  另外,相信你已經發現在上面的工具列中,除了前面所談到的繪圖工具之外,我還多加了兩個額外的SpeedButton ,它們是用來設定筆及筆刷的工具。在此例中,我們是以它來叫出另外兩個設定筆及筆刷的工具列,因此它必須具備所謂Toggle On/Off的開關功能。也就是類似CheckBox的功能。
  SpeedButton也可輕易達到以上的要求,只要設定該SpeedButton的AllowAllUp屬性爲True即可以做到此功能。
  最後我們必須將設定筆及筆刷的兩個SpeedButton,PenButton及BrushButton的GroupIndex分別設爲 2、3,如此才不會和繪圖工具的SpeedButton的群組特性相幹擾。
  XX-05工具列的事件處理函式
  完成的工具列的設定之後,接著我們要設定工具列的處理函式,由於此工具列是用來切換繪圖工具的,因此我們只要處理SpeedButton的OnClick事件,再分別根據不同的事件做處理即可。
  在此例中,OnClick的事件處理函式其很簡單,只要刪除原來使用的繪圖工具物件,再重新啓始一個新的繪圖工具即可。
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::LineButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CLine(Canvas);
  }
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::PolylineButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CPolyline(Canvas);
  }
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::PolygonButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CPolygon(Canvas);
  }
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::RectangleButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CRectangle(Canvas);
  }
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::EllipseButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CEllipse(Canvas);
  }
  //---------------------------------------------------------------------
  void __fastcall TGraphEx::RoundRectButtonClick(TObject *Sender)
  {
  delete m_pObj;
  m_pObj = new CRoundRect(Canvas);
  }
  //---------------------------------------------------------------------
  
  
  
  在完成了以上的設定之後,此程式就具備了利用繪圖工具列來切換繪圖工具的功能。
  
具體而微的繪圖程式-c++ Borland 入門

  圖XX-10具備繪圖工具列的繪圖程式範例。