熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Delphi編程 >> 正文

通過實例看VCL組件開發全過程

2013-11-11 21:04:11  來源: Delphi編程 

  在這篇文章中我們將建立一個和時間有關的組件這個組件通過設置它的不同狀態有以下基本功能顯示系統的當前時間(包括設置鬧鐘)跑表倒計時這是一個簡單的例子然而我們將在這個例子中盡可能多的用到delphi在組件開發中的多種特性你可以通過以下列舉出的本文涉及特性有選擇的閱讀
  
  組件和組件包
  
  組件的屬性類別
  
  組件的屬性編輯器
  
  組件編輯器
  
  一組件和組件包以及一些你應該知道的文件類型
  組件和組件包的關系就如同普通工程中unit和工程文件的關系一樣通常你所安裝的組件都是以組件包的形式發布的一個組件包中可以有很多個組件在組件開發中組件包就是項目的工程文件為了開始開發我們的組件(我們把他叫做TClock)並將它包括在我們自己的組件包(ClockPackage)中我們選擇Fileànewàother在彈出的窗口中的New頁選擇Package新建一個組件包得到一個組件包窗口查看這個組件包的原文件(dpk)得到以下代碼
  
  package ClockPackage;
  
  {$R *res}
  
  {$ALIGN }
  
  {$ASSERTIONS ON}
  
  ……
  
  ……
  
  {$DESCRIPTION Our Clock Pack}
  
  {$IMPLICITBUILD OFF}
  
  requires
  
  rtl;
  
  end
  
  這個文件其實就是組件開發中的工程文件requires關鍵字指示了組件包所需組件包的列表隨著向組件包中加入組件(類似於單元文件)你還會看到contains關鍵字指示了組件包所包含的組件你可以通過組件包窗口中的add和remove按紐來添加新的組件和刪除已有的組件另外這個代碼中所包含的大量的編譯器開關大多都可以在組件包窗體上的Options中設置這裡需要補充說明的是組件包的種重要屬性(都在Options中)Designtime OnlyRuntime OnlyDesigntime and runtime(這個詞的意思有英語基礎的朋友應該都知道吧)對於大多數的組件包我們只要選擇最後一個就可以了然而有些組件包設計為只運行時(這樣你用這套組件開發的程序不能脫離組件而單獨運行組件包也不能被安裝)有些組件包被設計為只設計時(這將在後文有更詳細的說明)
  
  了解了組件和組件包我們對組件開發中可能出現的一些你沒有見過的文件做一些說明dpk文件既組件包的原代碼bpl文件組件包編譯後的結果在沒有發布dpk的情況下可以通過bpl來安裝組件包到delphi(ProjectàOptionsàPackagesàadd)pas在這裡就是組件包中組件的原代碼了dcu為pas編譯後的結果在你選擇將組件包含進組件包時(contains關鍵字)你可以選擇發布原代碼或是不發布(dcu文件)dcp如果你將組件作為運行時組件連接器將使用該文件
  
  二開始開發組件
  了解了上面的知識後我們就可以開始開發組件了!在組件窗體中單擊add選擇NewComponent頁在第一個組合框中選擇我們的組件將要繼承自哪個類(通常新的組件是通過繼承已有的組件來開發的)由於這個組件的主要作用是要顯示時間跑表倒計時種的文字信息所以我們選擇繼承自TCustomLabel(由於我們並不需要Tlabel的全部功能我們選擇了能夠隱藏Tlabel屬性並有選擇的發布它的屬性的TcustomLabel類)接下來為我們的新組件取一個名字Tclock然後指定我們想把組件安裝到哪一個頁中這裡我們自己鍵入一個ClockAndTime頁這將出現在RegisterComponents過程中(後面會詳細說明)選擇好文件保存的路徑後(最好把它和組件dpk包放在同一目錄)確認這是組件包窗體中的contains下已經多了我們剛才建立的組件的文件雙擊它開始編寫代碼
  
  在代碼中我們需要注意在interface部分的一個新的過程procedure Register;(注意delphi規定Register的R必須大寫這是一個保留字)這個過程是作為每一個組件所必須有的它完成組件的注冊包括組件本身以及如屬性編輯器等多種組件特性的注冊)
  
  procedure Register;
  
  begin
  
  RegisterComponents(ClockAndTime [TClock]);
  
  //這個過程注冊組件本身注意到前面定義的ClockAndTime頁了嗎?
  
  //這裡在後面還會出現一些新的過程包括注冊組件的屬性類別等等
  
  end;
  
  在下一篇中我們將給出這個組件的全部原代碼

  組件的代碼由於假設你已經熟悉delphi開發(它和一般開發沒什麼不同)我們就直接貼出來並加上適當的注釋
  
  unit Clock;
  
  interface
  
  uses
  
  SysUtils Classes Controls StdCtrlsExtCtrls;
  
  type
  
  TState=(StClockStRunClockStBackClock);//定義枚舉類表示控件的種狀態時鐘跑表倒計時鐘
  
  TClock = class(TCustomLabel)
  
  private
  
  fState:TState;
  
  fTimer:TTimer;//為什麼使用這個組件作為我們組件的私有成員就不用說了吧
  
  RCD:array[] of integer;//跑表中的各個數位
  
  fBeginTime:string;//到計時時的開始時鐘之所以沒用TTime類型是為了在後面演示屬性編輯器
  
  fWakeTime:string;//鬧鐘時間出於和上面同樣的理由
  
  fAllowWake:boolean;//是否開啟鬧鐘功能
  
  fOnWakeUp:TNotifyEvent;//為了使組件更加完美我們允許組件用戶能夠響應鬧鐘到來時的時件
  
  fOnTimeUp:TNotifyEvent;//同上能夠響應倒計時種完成時的事件我們將發布這兩個事件
  
  function GetActive:boolean;//控制Timer是否工作以控制種狀態的鐘是否工作
  
  procedure SetActive(Value:boolean);
  
  procedure SetState(Value:TState);
  
  procedure SetBeginTime(Value:string);
  
  procedure SetWakeTime(Value:string);
  
  protected
  
  procedure WalkClock(sender:TObject);//作為時鐘時走種的事件
  
  procedure RunClock(sender:TObject); //跑表
  
  procedure BackClock(sender:TObject);//倒計時
  
  public
  
  constructor Create(AOwner:TComponent);override;//完成一些初始化工作
  
  procedure ReSetRunClock; //跑表和倒計時都需要一個復位方法給組件使用者調用
  
  procedure ReSetBackClock;
  
  published
  
  property State:TState read fState write SetState default StClock;//默認為時鐘狀態
  
  property Active:boolean read GetActive write SetActive;//控制種狀態的鐘是否工作
  
  property BeginTime:string read fBeginTime write SetBeginTime;
  
  property WakeTime:string read fWakeTime write SetWakeTime;
  
  property AllowWake:boolean read fAllowWake write fAllowWake;
  
  property OnWakeUp:TNotifyEvent read fOnWakeUp write fOnWakeUp;
  
  property OnTimeUp:TNotifyEvent read fOnTimeUp write fOnTimeUp;
  
  //最後我們再發布一些被TCustomLabel所隱藏而我們又需要的屬性
  
  property Align;
  
  property Alignment;
  
  property Color;
  
  property Font;
  
  property ParentColor;
  
  property ParentFont;
  
  property ParentShowHint;
  
  property PopupMenu;
  
  property ShowHint;
  
  property Visible;
  
  property Transparent;
  
  property OnClick;
  
  end;
  
  procedure Register;
  
  implementation
  
  procedure Register;
  
  begin
  
  RegisterComponents(ClockAndTime [TClock]);
  
  end;
  
  { TClock }
  
  constructor TClockCreate(AOwner: TComponent);
  
  begin
  
  inherited Create(AOwner);
  
  //設置默認值
  
  fTimer:=TTimerCreate(self);
  
  //將它屬於我們的組件這樣便不用編寫析構函數而可以自動在釋放本組件時釋放Timer
  
  Active:=false;
  
  AllowWake:=false;
  
  State:=StClock;
  
  BeginTime:=::;
  
  WakeTime:=::;
  
  end;
  
  function TClockGetActive: boolean;
  
  begin
  
  result:=fTimerEnabled;
  
  end;
  
  procedure TClockSetActive(Value: boolean);
  
  begin
  
  fTimerEnabled:=Value;
  
  end;
  
  procedure TClockSetState(Value: TState);
  
  var
  
  i:integer;
  
  begin
  
  case Value of
  
  StClock:
  
  begin
  
  Active:=false;
  
  fTimerInterval:=;
  
  fTimerOnTimer:=WalkClock;
  
  Active:=true;
  
  end;
  
  StRunClock://由於Time類型不好處理微秒操作我們只有手工模仿這個操作代碼會稍微煩瑣
  
  begin
  
  Active:=false;
  
  for i:= to do RCD[i]:=;
  
  Caption:=IntToStr(RCD[])+IntToStr(RCD[])+:+IntToStr(RCD[])+IntToStr(RCD[])+:+IntToStr(RCD[]);
  
  Caption:=Caption+IntToStr(RCD[])+:+IntToStr(RCD[])+IntToStr(RCD[]);
  
  fTimerInterval:=;
  
  //經過測試這個秒表的效果很好然而這只是一個技術上的演示
  
  //實際上這麼頻繁(/秒)的不斷執行RunClock會使CPU的占用一直達到%
  
  //這並不是一個好注意事實上要想在跑表中顯示微秒級別並做到合理的占用CPU
  
  //這需要更加靈活和復雜的編程
  
  fTimerOnTimer:=RunClock;
  
  end;
  
  StBackClock:
  
  begin
  
  Active:=false;
  
  Caption:=BeginTime;
  
  fTimerInterval:=;
  
  fTimerOnTimer:=BackClock;
  
  end;
  
  end;
  
  fState:=Value;
  
  end;
  
  procedure TClockSetBeginTime(Value: string);
  
  begin
  
  try
  
  StrToTime(Value);
  
  fBeginTime:=Value;
  
  if State=StBackClock then
  
  begin
  
  Active:=false;
  
  Caption:=Value;
  
  end;
  
  except
  
  on Exception do
  
  begin
  
  fBeginTime:=::;
  
  if State=StBackClock then Caption:=::;
  
  end;
  
  end;
  
  end;
  
  procedure TClockSetWakeTime(Value: string);
  
  begin
  
  try
  
  StrToTime(Value);
  
  fWakeTime:=Value;
  
  except
  
  on Exception do
  
  begin
  
  fWakeTime:=::;
  
  end;
  
  end;
  
  end;
  
  procedure TClockWalkClock(sender: TObject);
  
  begin
  
  Caption:=TimeToStr(Time);
  
  if AllowWake and (StrToTime(Caption)=StrToTime(WakeTime)) then
  
  begin
  
  Beep;//蜂鳴器
  
  if Assigned(fOnWakeUp) then
  
  fOnWakeUp(self);
  
  end;
  
  end;
  
  procedure TClockRunClock(sender: TObject);
  
  begin
  
  RCD[]:=RCD[]+;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then begin RCD[]:=RCD[]+;RCD[]:=; end;
  
  if RCD[]= then RCD[]:=; //我們的跑表最多可計個小時;
  
  Caption:=IntToStr(RCD[])+IntToStr(RCD[])+:+IntToStr(RCD[])+IntToStr(RCD[])+:+IntToStr(RCD[]);
  
  Caption:=Caption+IntToStr(RCD[])+:+IntToStr(RCD[])+IntToStr(RCD[]);
  
  end;
  
  procedure TClockBackClock(sender: TObject);//可以在一天之類的時間倒計時
  
  begin
  
  if StrToTime(Caption)<>StrToTime(::) then
  
  Caption:=TimeToStr(StrToTime(Caption))
  
  else
  
  begin
  
  Active:=false;
  
  Beep;
  
  if Assigned(fOnTimeUp) then
  
  fOnTimeUp(self);
  
  end;
  
  end;
  
  procedure TClockReSetBackClock;
  
  var
  
  i:integer;

  三添加組件圖標注冊組件的屬性類別
  在前面的文章中我們已經完成了組件的基本功能的開發但是遺憾的是一但你安裝了組件包你會發現組件顯示在delphi組件頁中的圖標並不能清楚的說明我們組件的功能(由於我們的組件繼承自TcustomLabel圖標是一個默認的delphiVCL的圖標如果組件繼承自其它已經出現在組件面板中的組件圖標還會和已有組件一樣!)顯然一個好的組件特別是一個要發布的商業化組件需要一個有自己特色的目標下面我們便來完成這一工作
  
  打開delphi自帶的Image Editor(ToolsàImage Editor)新建一個組件資源(fileànewàComponent Resource File (dcr))在彈出的窗口中右鍵單擊new新建一個bitmap位圖資源調整好位圖的大小(我們用*)和色深後確定雙擊建立好的位圖名字還是做圖(做圖工具的使用基本和windows自帶的畫圖程序差不多這裡略過)完成後我們需要為位圖文件另取一個名字(右鍵點擊bitmap)因為delphi強制要求這個位圖的名字要和組件的名字一樣並且要全部大寫這裡我們就取為TCLOCK最後保存這個資源文件到我們的組件包(dpk文件)目錄命名為ClockDcrdcr最後在Clock的代碼中的interface部分加入一個編譯器開關{$R ClockDcrdcr}然後重新編譯更新組件(還記得怎麼更新嗎?)這時的組件圖標已經變成我們剛才做的位圖了!
  
  接下來我們將為我們開發的組件的屬性進行分類並介紹一個組件開發中重要的特性屬性類別
  
  為了讓我們組件的一些和時鐘有關的屬性注冊成一個新的類別把它們和label的屬性分開開來讓組件用戶能夠更容易的發現組件的新特性我們繼承了屬性類別的基類TpropertyCategory(在delphi中這需要引用單元DsgnIntf不過應該特別注意在delphi中已經沒有了這個基類也沒有這個單元文件注冊新的屬性類別可以通過直接使用RegisterPropertyInCategory這種簡單的辦法完成在下面的代碼中會在相應的地方同時給出兩種方法並說明他們的不同)並覆蓋它的兩個類方法最後在Register過程中用RegisterPropertyInCategory(在delphi中在DsgnIntf單元在delphi中在DesignIntf單元注意delphi的一些單元並沒有被安裝包括我們這裡指出的這兩個單元和將要在後文中指出的單元這些單元屬於delphi的open tools api是用來方便我們特別是組件開發者用來擴展delphi如果你的delphi沒有這些單元請將delphi安裝目錄下的source文件夾裡ToolsAPI文件夾中的pas文件拷貝到lib目錄下在你第一個需要用到這些單元的程序編譯時delphi會自動編譯這些單元)方法注冊屬性類別我們把以下的部分代碼補充進我們開發的組件的原代碼中
  
  uses
  
  DesignIntf;//delphi//delphi用DsgnIntf
  
  ///////////這部分代碼如果是delphi就不需要了///////////////
  
  type
  
  TClockGategory=class(TpropertyCategory)//建立一個新的屬性類別
  
  Class function Name:string;override;//屬性類別的名稱
  
  Class function Description:string;override;//屬性類別的描述
  
  End;
  
  ……
  
  Class function TClockGategory Name:string;
  
  Begin
  
  Result:=ClockPro;
  
  End;
  
  Class function TClockGategory Description:string;
  
  Begin
  
  Result:=Our Component Clock Description;
  
  End;
  
  ////////////////////////////////////////////////////////////////////////////////////
  
  接下來我們要做的就是修改register過程
  
  procedure Register;
  
  begin
  
  RegisterComponents(ClockAndTime [TClock]);
  
  ////////////這是delphi的代碼/////////////////////////////
  
  RegisterPropertyInCategory(ClockProTClockState);
  
  RegisterPropertyInCategory(ClockProTClockActive);
  
  RegisterPropertyInCategory(ClockProTClockBeginTime);
  
  RegisterPropertyInCategory(ClockProTClockWakeTime);
  
  RegisterPropertyInCategory(ClockProTClockAllowWake);
  
  RegisterPropertyInCategory(ClockProTClockOnWakeUp);
  
  RegisterPropertyInCategory(ClockProTClockOnTimeUp);
  
  //////////////////////////////////////////////////////////
  
  ///////////////這是delphi的代碼/////////////////////////
  
  {
  
  RegisterPropertyInCategory(TClockGategoryTClockState);
  
  RegisterPropertyInCategory(TClockGategoryTClockActive);
  
  RegisterPropertyInCategory(TClockGategoryTClockBeginTime);
  
  RegisterPropertyInCategory(TClockGategoryTClockWakeTime);
  
  RegisterPropertyInCategory(TClockGategoryTClockAllowWake);
  
  RegisterPropertyInCategory(TClockGategoryTClockOnWakeUp);
  
  RegisterPropertyInCategory(TClockGategoryTClockOnTimeUp);
  
  }
  
  ////////////////////////////////////////////////////////
  
  end;
  
  重新編譯後做一個測試程序這時只要組件使用者右鍵單擊Object Inspector選擇ArrangeàBy Category就可以看到屬性已經被清楚的分類了
  
  然而應該清楚的是屬性類別絕對不能被濫用因為過多的使用該技術會使組件使用者為了找到某一個屬性變的更加麻煩和摸不著頭腦
  
  在接下來的文章裡我們將繼續研究兩個很有用的組件特性

  四組件屬性編輯器和組件編輯器
  通過上面的努力我們的組件似乎已經比較完美了可我們也忽略了一些重要的細節和一些有趣的事情這一篇我們將研究兩個很有用的組件特性
  
  在之前開發組件核心功能時我們曾設置了兩個屬性BeginTime和WakeTime他們都是字符串型的屬性然而他們所要表示的卻是時間類型這樣就很有可能使組件使用者錯誤的編輯屬性並導致轉化字符串到時間時出錯(當然這裡只是為了文章的講解我們故意把它設置為了字符串類型)雖然通過浏覽原代碼你知道我們也做了一些代碼級別的防出錯處理使當輸入錯誤時屬性自動變成::然而這對組件使用者來講仍然顯的很不友好所以我們需要為這兩個屬性定制編輯器我們的編輯器將彈出一個窗口裡面有一個TdateTimePicker用來選擇時間在delphi中有許多這樣的例子例如大家都知道的lines屬性當你單擊它右放的省略號時為自動彈出一個文本編輯器來編輯lines這大大降低了組件使用者范錯誤的可能性
  
  在定制完屬性編輯器以後我們將為組件本身加入一寫有趣的元素——組件編輯器這也是在delphi中經常出現的例如有些組件當你雙擊它時它並不會進入代碼編寫狀態而是彈出它自己的編輯器雖然我們的組件似乎並不需要這種特性但為了演示它我們也將它考慮近來我們給我們的組件編寫了一個版權信息和一個關於對話框當組件使用者雙擊它時彈出關於信息(當然這僅僅是種演示)上面提到的兩種特性由於它們只是會在設計時起作用所以你完全可以在新的組件包中編寫並注冊它們並將這個組件包設置為Designtime Only為了方便起見我們就直接把它們和組件的單元編寫在一起注意以下出現的一些類和方法都需要引用單元DesignEditors(delphi)或DsgnIntf(delphi與前面說的一樣它們都屬於delphi的open tools api所以如果你沒有這寫單元請按照前文的方法安裝它們
  
  首先來編寫屬性編輯器由於BeginTime和WakeTime是字符串類型所以我們必須從默認的字符串屬性編輯器類TstringProperty繼承並覆蓋它的一寫方法(這裡只介紹幾個重要的方法事實上所有的屬性編輯器都從TpropertyEditor繼承而來然而我們不用直接繼承這個基類)其中一個重要的方法是GetAttributes他將返回一些代表編輯器功能的值這些值將會在代碼的注釋中說明(如果你的屬性編輯器還需要一個下拉列表你還需要另外一個重要的方法GetValues具體請查看delphi幫助)另外為了使屬性編輯器為彈出的對話框我們需要覆蓋Edit方法為了可以以可視化的方式設計對話框我們可以建立一個普通工程在設計好後將窗體的類聲明復制到我們的組件單元並將窗體的dfm文件拷貝到我們的組件包目錄並在代碼中加入編譯器開關{$R *dfm}以下是窗體的類聲明這個窗體沒有任何的代碼需要編寫
  
  TTimeEditFrm = class(TForm)
  
  DateTimePicker: TDateTimePicker;
  
  Button: TButton;
  
  Button: TButton;
  
  private
  
  { Private declarations }
  
  public
  
  { Public declarations }
  
  end;
  
  以下是屬性編輯器的代碼
  
  TClockProperty=class(TStringProperty)
  
  public
  
  function GetAttributes:TPropertyAttributes;override;
  
  procedure Edit;override;
  
  end;
  
  實現部分
  
  procedure TClockPropertyEdit;
  
  var
  
  TimeEditFrm:TTimeEditFrm;
  
  begin
  
  TimeEditFrm:=TTimeEditFrmCreate(Application);
  
  try
  
  TimeEditFrmDateTimePickerTime:=StrToTime(GetValue);
  
  if TimeEditFrmShowModal=mrOK then
  
  SetValue(TimeToStr(TimeEditFrmDateTimePickerTime));
  
  //GetValue和SetValue是TStringProperty的基類方法他直接讀取和設置字符串的值
  
  finally
  
  TimeEditFrmFree;
  
  end;
  
  end;
  
  function TClockPropertyGetAttributes: TPropertyAttributes;
  
  begin
  
  result:=[paDialogpaMultiselect];
  
  //paDialog表示屬性編輯器將顯示一個對話框paMulitiselect允許多個組件選擇屬性
  
  //除此之外如果你想讓屬性編輯器顯示下拉列表你還需要paValueList具體請查看幫助
  
  end;
  
  最後我們用RegisterPropertyEditor方法注冊屬性編輯器:
  
  procedure Register;
  
  begin
  
  ……
  
  RegisterPropertyEditor(TypeInfo(string)TClockBeginTimeTClockProperty);
  
  RegisterPropertyEditor(TypeInfo(string)TClockWakeTimeTClockProperty);
  
  end;
  
  重新編譯更新組件後我們就可以測試了
  接下來我們來實現組件編輯器
  
  組件編輯器需要繼承TcomponentEditor並覆蓋一些重要的方法GetVerbCount返回設計時組件右鍵自定義菜單的數目GetVerb為每一個自定義菜單添加文字ExecuteVerb為每一個菜單項添加事件Edit為組件的缺省操作指定事件(即在設計時雙擊組件)以下是代碼
  
  TClockEditor=class(TComponentEditor)
  
  public
  
  function GetVerbCount:integer;override;
  
  function GetVerb(index:integer):string;override;
  
  procedure ExecuteVerb(index:integer);override;
  
  procedure Edit;override;
  
  end;
  
  實現部分
  
  procedure TClockEditorEdit;
  
  begin
  
  ExecuteVerb(); //默認顯示關於
  
  end;
  
  procedure TClockEditorExecuteVerb(index: integer);
  
  begin
  
  case index of
  
  //第一個顯示名字的菜單什麼都不做顯示
  
  :showmessage(hkbarton@);
  
  end;
  
  end;
  
  function TClockEditorGetVerb(index: integer): string;
  
  begin
  
  case index of
  
  :result:=hkbarton;
  
  :result:=About Clock;
  
  end;
  
  end;
  
  function TClockEditorGetVerbCount: integer;
  
  begin
  
  result:=;//我們顯示兩條菜單一個我的名字一個關於
  
  end;
  
  同樣最後我們注冊組件編輯器
  
  procedure Register;
  
  begin
  
  ……
  
  RegisterComponentEditor(TClockTClockEditor);
  
  end;


From:http://tw.wingwit.com/Article/program/Delphi/201311/8400.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.