Delphi --技巧探索:
{ No. 1 }
創建模式窗體的句子:
class procedure TMyForm.RunForm(AObj1, AObj2: TObject);
var
vForm: TMyForm;
begin
vForm := TMyForm.Create(Application);
with vForm do
Try
InitForm(AObj1, AObj2);
ShowModal;
Finally
Free;
end;
end;
//*說明:
通過class聲明的函數,類似與VC中的靜態函數;使用語句:TMyForm.RunForm(vObj1, vObj2);
其他具體的,參考:Delphi 幫助中的,class 類說明。
強調這個慣用法,就是為了:
1、如果此窗體在多處被使用,那麼可以保證統一都調用此段代碼;
2、如果功能上有所修改,比如:根據ShowModal的返回值不同進行處理,那麼只修改此函數就行了。
3、程序封裝性好,易於維護和工作交接。*//
{ No. 2 }//Tag 的使用
窗體工具欄按鈕事件的響應
procedure TMyForm.RunOperate(ATag: Integer);
begin
Case ATag of
1: MyButton.Color := clRed;
2: MyButton.Color := clGreen;
3: MyButton.Color := clBlack;
end;
end;
procedure TMyForm.ToolBtnClick(Sender: TObject);
begin
RunOperate(TControl(Sender).Tag);
end;
如果你在某下拉菜單中,也需要執行類似功能則
procedure TMyForm.MenuItemClick(Sender: TObject);
begin
RunOperate(TMenuItem(Sender).Tag);
end;
//*說明:
1、結構清晰
2、相關的信息集中,比較容易查錯、修改和維護
3、提高程序的適應、擴展能力;比如現在要求不在工具欄按鈕中實現,而要求在不同按鈕中實現,則修改容易。
建議:每個分類後面只跟一行或不多的幾行代碼,如果代碼比較多,使用過程函數替代。
比較有意思的是,我經常如下寫:
Case btnMyButton.Visible of
{ 顯示 } True: ...
{不顯示} False: ...
end; *//
{ No. 3 }//事件指針做參數
//對於列表等的讀取使用事件指針的方式
type
TDataSetEvent = procedure (DataSet: TDataSet; AIndex, ACount: Integer) of Object;
//從 TADOQuery派生而來的類
procedure TMyADOQuery.EnumRecord(AWhereStr: String; APro: TDataSetEvent);
begin
Close;
SQL.Clear;
SQL.Add('Select * From Table1');
if AWhereStr <> '' then
SQL.Add('Where ' + AWhereStr);
Open;
While Not Eof do
begin
if Assigned(APro) then APro(Self, RecNo, RecordCount);
Next;
end;
Close;
end;
//*說明:
此方法來自與Window中,枚舉當前所有子窗體的API函數,EnumChildWindow
1、原則:盡量將數據讀取與數據顯示、數據處理等分離;如:MVC等都是此目的。
2、程序擴展性增強,如果您原來希望在列表中顯示或處理某列信息,後來改為用ComboBox,則在修改程序時,不在閱讀數據讀取部分,只需要修改信息顯示等即可。又比如,現在要求您在讀取記錄時,用進度條顯示讀取進度等。
*//
{ No. 4 }//常量數組
{ 在 No.2 中,實現了如下的內容
procedure TMyForm.RunOperate(ATag: Integer);
begin
Case ATag of
1: MyButton.Color := clRed;
2: MyButton.Color := clGreen;
3: MyButton.Color := clBlack;
end;
end;
}
//那麼用數組方式實現,則就比較理想了
procedure TMyForm.RunOperate(ATag: Integer);
const
MyButtonColorMax := 3;
MyButtonColor: array [1..MyButtonColorMax] of TColor = (clRed, clGreen, clBlack);
begin
Case ATag of
1..MyButtonColorMax: MyButton.Color := MyButtonColor[ATag];
101:....
end;
end;
//*說明:
對於數組方式使用,只要注意數組的上限或下限使用常量來實現,然後在以後使用中都盡量使用此常量進行數組循環讀取就行了。
*//
{ No. 5 }消息機制 減少類公共函數
//如何讓一個窗體中,盡量減少公共函數的定義;
{ 比如:要實現一個當前窗體控件的屬性列表窗體,當需要刷新屬性窗體;改變某屬性值;添加新的屬性等;會有很多需要交互的信息。如果我們使用類公共函數,則需要定義很多的公共函數。同時,如果需要進行窗體類型轉換,轉換為目標窗體類型才可以使用公共函數。所以,會遇到兩個單元需要互相包含的情況 }
//解決方案:
TfrmMyForm = class(TForm)
FfrmProperty: TForm;
end;
...
FfrmProperty := TfrmProperty.MyCreate(Application, Self);
...
//當需要刷新屬性窗體時
FfrmProperty.Perform(WD_REFRESHPROPERTYLIST, 0, 0);
TfrmProperty = class(TForm)
private
FMyForm: TForm;
procedure WDREFRESHPROPERTYLIST(var Message: TMessage); message WD_REFRESHPROPERTYLIST;
public
constructor MyCreate(Owner: TComponent; AForm: TForm);
end;
constructor TfrmProperty.MyCreate(Owner: TComponent; AForm: TForm);
begin
inherited Create(Owner);
FMyForm := AForm;
end;
//* 對於使用消息的方式,可以減少窗體公共函數的定義。同時,提高程序的可擴充性。如果,使用他的窗體替代時,則可以比較輕松的轉換,因為如果最多也就是您的窗體,對當前的消息沒有進行處理而已 *)//
{ No. 6 }使用注冊列表管理可能擴充的模塊
//項目:要求你對一個數據集支持多種輸出顯示方式
...例子,以後給出
//* 說明:
1、“多種輸出方式”,說明輸出方式在今後的應用中可能會經常擴充,因此要在程序設計時考慮到輸出方式的易擴充性。
2、參考VCL中,控件注冊(RegisterComponents)的機制,可以發現VCL中大量的使用到了注冊機制;其中比較經典的就是控件屬性編輯器的注冊了。
*//
{ No. 7 }使用預定義控制程序版本
//如果您做的是一個二次開發平台的程序,則必須涉及到產品版本控制和項目版本控制問題
//通常使用預定義的方式控制
//語句比較簡單了就是:
{$DEFINE JOYYUAN97}
{$IFDEF JOYYUAN97} {ELSE} {ENDIF}
{$UNDEF JOYYUAN97}
*說明:
1、將預定義劃分在多個單獨的文件中。
2、在每個單元的最前頭但在Unit 後,使用{$I ...} 將文件包含(Include)進當前單元
3、根據預定義情況控制當前單元所能包含的單元文件
4、盡量單獨劃分一個針對項目的預定義文件在包含所有預定義文件後,包含此文件,則在此文件中,可以針對項目的需要,將取消部分預定義{$UNDEF JOYYUAN97}
*//
{ No. 8 } 使用函數指針,減少單元項目包含
//我經常的認為減少單元的包含,是做公共單元的第一步,所以在如何盡量減少單元包含
//也就是如何減少程序單元的耦合性上,應多下工夫。
{ 情景描述:
TMyFormManager: 窗體管理類
TMyForm:數據窗體基礎類
TMyFormAccess:窗體信息保存和讀取類。將窗體信息保存到數據庫或其他什麼類型的結構中
分析:
1、窗體基礎類(TMyForm) 和 窗體管理類(TMyFormManager)需要在一個單元 uManagers中實現。
2、窗體具體實現類(TMyImageForm)單元 fMyImange 需要包含單元uManagers,進行窗體繼承,和窗體管理。
3、窗體數據讀取類(TMyForMaccess)單元 uMyAccess 需要包含單元uManagers和單元fMyImange
問題:
如果我希望實現窗體保存,那麼應該在窗體的某個按鈕事件中實現。則涉及到窗體單元需要包含窗體數據訪問類單元,而如果放在窗體基礎類中,則單元uManager又必須包含單元uMyAccess。
當數據訪問,即數據存儲格式會根據要求而改變並要求可擴充時,則單元包含必定是一個隱患。
解決辦法:使用函數指針變量。
1、在單元uManagers中定義一個,保存數據信息的函數指針變量。
2、在應用程序初始化的時候給這個函數指針變量賦值。
3、在需要保存窗體信息時,判斷如果指針不為空,則執行函數保存窗體信息。
{ No. 9 } 常量,認識常量,使用常量
有很多書都都介紹了常量定義的重要性,我也會經常想到,但是看看VCL源碼才知道,自己忽略了,別人對常量的使用情況。
1、我們經常使用的消息的定義就是:聲明一個常量,然後在適當的時候使用之。
通常定義和使用:
const
WD_MyMessage = WM_User + 101;
type
TMyForm = class(TForm)
...
procedure WDMyMessage(var message: TMessage); message WD_MyMessage; {響應消息位置}
end;
但是,如果您將{響應消息位置}語句改寫為:
procedure WDMyMessage(var message: TMessage); message WM_User + 101;
同樣,編譯可以成功,使用也正常。所以,常量定義在Window系統處理和接口中應用非常普遍。
2、在Delphi中,我們定義了顏色變量,clRed, clGreen等,也都是定義的常量,便於以後的使用。通過這個觀察我發現,常量的定義應該是在項目中,可部分復用的,所以,可以定義一個標准常量單元,以便在個項目中,復用定義的常量。
{ No. 10 }一個Delphi中,常用到的數組
對TIdentMapEntryd類型的數組定義和使用,Delphi中,有比較完善的實現。
TIdentMapEntry = record
Value: Integer;
Name: String;
end;
1、數組定義:array[0..ArrMax] of TIdentMapEntry
可參考:Controls單元中:
Cursors: array[0..21] of TIdentMapEntry = (
...
);
2、兩個互相求值得函數: IntToIdent(由Value求Name)和 IdentToInt(由Name求Value);
具體應用可以參考:IdentToCursor 和 CursorToIdent。
3、應用:a、直接應用此樹組定義方式和數組操縱函數;b、學習函數中,對數組訪問和操縱的方式。c、學習標准的信息訪問函數定義: function IntToIdent(Int: Longint; var Ident: string; const Map: array of TIdentMapEntry): Boolean; 具體返回的信息由參數方式返回回來,至於訪問是否有效,則通過函數的布爾返回值加以判斷。
{ No. 11 } 由特例到普通的發現
我通過對 Cursors 的定義和操作函數的跟蹤發現:
1、如 { No. 10 }中介紹的,將Cursors的定義和一般操作通用化。
2、提供 Int 和 Ident互轉化的函數。
3、提供數組列表信息循讀取的函數: GetCursorValues;其中,使用了 { No. 3 } 中介紹的“事件指針 做參數”讀取列表信息的方法。
{ No. 6 } 的補充:
例子:
procedure RegisterComponents(const Page: string;
ComponentClasses: array of TComponentClass);
begin
if Assigned(RegisterComponentsProc) then
RegisterComponentsProc(Page, ComponentClasses)
else
raise EComponentError.CreateRes(@SRegisterError);
end;
解讀:
1、使用注冊的方式,記錄可使用的控件的類型等。
3、對於 RegisterComponentsProc 使用了{ No. 8 } 中“使用函數指針,減少單元項目包含”的方法,便於將來程序的擴充,版本的升級等。
{ No. 11 }只定義一個公共函數
//項目描述:現在要實現一個CAD畫圖或Visio系統,要求有好的擴展性和易維護性;
//並且要求耦合性低,便於,將來系統的部分或擴展後的系統封裝後,直接在今後的項目中使用
設計:
1、設計一個圖形對象抽象類,在此類中,定義一個抽象函數 CadPerform,函數的參數參照function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
2、在圖形管理類中,實現一個圖形對象列表的管理,列表中保存的是抽象對象的指針。
3、對於要對具體類對象進行操縱控制時,只需通過條用CanPerform函數,然後根據當前操作的類別傳入 Msg, 並傳入相應的參數信息。
實現: TCad 為由抽象類繼承下來的第一層控件類
function TCad.CadPerform(Msg: Cardinal; WParam, LParam: Longint): Longint;
begin
Case Msg of
My_Message1: Result := MyMessage1(WParam, LParam);
My_Message2: Result := MyMessage2(WParam, LParam);
end;
end;
對於,TPoint繼承自 TCad, CadPerform函數實現如下。
function TPoint.CadPerform(Msg: Cardinal; WParam, LParam: Longint): Longint;
begin
Case Msg of
My_Message1: Result := MyMessage1(WParam, LParam); //屏蔽了TCad中此操作類型的處理
My_Message3: Result := MyMessage3(WParam, LParam);
else Result := inherited CadPerform(Msg, WParam, LParam);
end;
end;
*說明:
因為,我們對圖形對象的操作會非常頻繁,所以我們通過定義一個公共開放的接口函數來實現,類的高封裝性和程序的易維護性、好擴展等性能。
*//
{ No. 12 }
以下是我編程時的要求:(部分信息沒有語言限制)
//以下的解決方案,幾乎都可以在上面的方法中,找到
1、減少程序的復雜度。a、減少函數個數,使用Case、Tag方式,學習實現Perform定義方式;b、減少單元嵌套關系,使用消息傳遞方式,減少窗體單元的互相包含。
2、減少
{ No. 13 }使用廣播,實現管理類對管理列表對象的通知
//對於{ No. 12 } 項目描述中,當畫圖的窗體控件屬性或狀態改變時,經常會需要通知所有的圖形對象,進行相應的改變。
//則如果只定義一個廣播函數,就可以實現父子通知的話,也會提高程序的可重用性、擴展性、易維護性等,使類結構清晰。
//比如:1、在Visio和MapInfo中,如果當前窗體的比例尺(縮放比例)改變時,需要用新的比例尺重畫當前所有的顯示圖形對象。2、當當前窗體默認窗體字體改變後,對於默認使用窗體字體顯示文字信息的圖形對象,他們的文字字體也應該相應的改變。
//解決方案,參考TWinControl中,屬性或狀態改變時,通知所有子Controls的處理機制:
procedure TWinControl.NotifyControls(Msg: Word);
var
Message: TMessage;
begin
Message.Msg := Msg;
Message.WParam := 0;
Message.LParam := 0;
Message.Result := 0;
Broadcast(Message);//廣播當前的變更消息
end;
其中:
procedure TWinControl.Broadcast(var Message);
var
I: Integer;
begin
for I := 0 to ControlCount - 1 do
begin
Controls[I].WindowProc(TMessage(Message));
//改為:with TMessage(Message) do Cads[I].CadPerform(msg, WParam, LParam);
if TMessage(Message).Result <> 0 then Exit;
end;
end;
但是,我們處理圖形對象時,可能會直接調用 Cads 的CanPerform公共函數即可
{ No. 14 }需要時,動態創建你的對象
比如:http://www.Delphibbs.com/keylife/iblog_show.ASP?xid=824 中的
//*******方案二 當需要的時候在創建屬性窗體
uses
...
fProperty;
type
TfrmMyMap = class
...
procedure OnfrmMyMapDestroy(Sender: TObject);
procedure OnMapGeoSelected(AGeo: TGeometry);
private
FfrmProperty: TfrmProperty;
procedure ShowPropertyForm(aVisible: Boolean);
public
end;
procedure TfrmMyMap.ShowPropertyForm(aVisible: Boolean);
begin
if Not Assigned(FfrmProperty) then FfrmProperty := TfrmProperty.Create(Application);
FfrmProperty.Visible := aVisible;
end;
procedure TfrmMyMap.OnfrmMyMapDestroy(Sender: TObject);
begin
if Assigned(FfrmProperty) then FfrmProperty.Free;
end;
procedure TfrmMyMap.OnMapGeoSelected(AGeo: TGeometry);
begin
if Assigned(FfrmProperty) then FfrmProperty.MyRefresh(AGeo);
end;
這裡說明了:
1、需要時,動態創建你的對象 FfrmProperty
2、當前對象釋放時,判斷你的對象的合法性,然後釋放動態創建的對象。
{ No. 15 }創建接口還是創建結構
//項目描述:我開發一個表格控件時,如果我將單元格設置為一個Com,則如果表格現實的信息過多的話,則裝載速度無法保證,甚至於有死機的可能。我之所以用Com是為了將來每個單元格的處理和信息都可以在控件外擴展。
我的解決辦法是:對於每個從Cell派生來的控件創建一個實例,通過動態創建若干個結構對象Record來記錄個單元格的信息,如果需要對單元格進行操作,則將結構對象指針賦值給Cell組件,測試結果很令人滿意。
所以,如果需要使用某個Com大量實例的話,盡量管理和維護一個實例,而對於其中的數據可以實行動態創建管理,速度上會有很好的效果。
另外,盡量聲明一個 pMyInterface = ^IMyInterface 借口指針,參數傳遞或使用時,直接使用接口指針,這樣可以減少調用計數函數_AddInft等,如果操作平凡也可以提高速度的。