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等,如果操作平凡也可以提高速度的。