Delphi制作數(shù)據(jù)感知控件之浮想聯(lián)翩
發(fā)布時間:2008-08-09 閱讀數(shù): 次 來源:網(wǎng)樂原科技
知識點(diǎn)
本文共有6個關(guān)于控件的知識點(diǎn):
1、基本控件的制作;2、感知屬性的添加;3、關(guān)聯(lián)控件的銷毀處理;
4、事件方法的賦值; 5、屬性頁的制作; 6、組件包設(shè)計思想
關(guān)于制作感知控件的文章有不少,但涉及的內(nèi)容大都比較單一,讀者只能依照文章的陳述按部就班地操作,無法很好的理解控件的制作機(jī)制。本文試圖通過講解一個控件的制作流程來著重闡述制作感知控件的思路和編程思想,讓讀者真正了解VCL控件的制作機(jī)制,而非僅僅達(dá)到了解此控件的制作方法為目的,希望能使讀者在閱讀以后達(dá)到“一葉落而知天下秋”的效果,哈,夸張了。
本文以制作一個類似于DBEdit的控件為例。首先使用組件向?qū)腡Edit下繼承下來,命名TMyDataBaseEdit,單元名為MyDataBaseEdi.pas,安裝在一個新的包文件中,起名為MyDataEditStd60.Dpk。
包的命名沒有規(guī)則,但是我們建議遵守包的命名約定:包的命名與包的版本相關(guān),包的名稱前面幾個字符通常表示作者或公司名,也可以是控件的一個描述詞,后面緊跟的Std表示運(yùn)行期包,Dsgn表示設(shè)計期包,然后是版本號。關(guān)于包的設(shè)計方式我們將隨后詳細(xì)說明。
數(shù)據(jù)感知
準(zhǔn)備就緒,我們開始編輯控件的功能,首先添加數(shù)據(jù)源感知屬性 .DataSource,實(shí)現(xiàn)此屬性只需Pubished域添加一句話:
property DataSource: TDataSource read GetDataSource write SetDataSource;按下shift +ctrl+ C組合鍵(complete class at cursor)完成屬性的自動結(jié)構(gòu)化功能,private域自動添加兩個函數(shù):
procedure SetDataSource(const Value: TDataSource);
function GetDataSource: TDataSource;
這樣,安裝組件到面板,可以看到該組件已經(jīng)擁有了.DataSource屬性,并實(shí)現(xiàn)感知TDataSource控件功能。怎么樣,簡單吧,呵呵。其實(shí)我們由此可以得到:控件屬性的感知只不過是將它的一個屬性聲明為將要感知的控件類而已,如果感知Image控件,則:
property MyImage:TImage read GetImage write SetImage;
但是,假如我們要將一個控件作為這個控件的子屬性,即將這兩個控件的代碼合并,則不能單單需要上述那一句話了:
首先,去掉添加控件的注冊函數(shù);
其次,將其從TComponent繼承改為從TPersistent繼承下來。大家可試試看。
事件賦值
不過,該控件并不能真正與數(shù)據(jù)庫相連,只是一個樣子而已,要真正實(shí)現(xiàn)功能,我們需要添加代碼,這時候我們用到了一個重要的類TfieldDataLink。
它是控件內(nèi)部的數(shù)據(jù)聯(lián)接對象,從TDataLink繼承下來,它的作用是與TDataSource組件相互通信,連接單個字段進(jìn)行數(shù)據(jù)提取。我們將要處理這個對象的OnDataChange事件,這樣,當(dāng)字段或記錄有所改變時就會得到通知,進(jìn)行相應(yīng)的數(shù)據(jù)處理。Ok,我們聲明對象并創(chuàng)建:
private域聲明 FDataLink:TFieldDataLink;構(gòu)造函數(shù)中創(chuàng)建
constructor TMyDataBaseEdit.Create(AOwner: TComponent);
begin
inherited;
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
end;
DataChange是我們private域聲明的一個過程:procedure DataChange(Sender: TObject);這里面實(shí)現(xiàn)了我們這個控件的實(shí)際功能,并和FDataLink.OnDataChange事件相連。創(chuàng)建成功之后我們實(shí)現(xiàn)GetDataSource、SetDataSource函數(shù)過程:
function TMyDataBaseEdit.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TMyDataBaseEdit.SetDataSource(const Value: TDataSource);
begin
if not (FDataLink.DataSourceFixed and (csLoading in ComponentState)) then
begin
FDataLink.DataSource := Value;
end;
if Value <> nil then
begin
Value.FreeNotification(Self);
end;
end;
關(guān)聯(lián)控件的銷毀
實(shí)現(xiàn)了上述代碼,這樣數(shù)據(jù)才真正的與控件關(guān)聯(lián)起來,要個性化處理數(shù)據(jù)就要添加DataChange過程的代碼了。
那么Value.FreeNotification(Self)代碼是什么意思呢?請大家想一想:我們的組件需要和DataSource控件和DataSet控件相組合才能實(shí)現(xiàn)數(shù)據(jù)庫數(shù)據(jù)的讀寫,那么當(dāng)我們刪除其中一個時,如果其他兩個控件不知道,那么是不是會出現(xiàn)異常呢?答案是肯定的。
那么我們怎樣才能做到通知其他組件呢?Yes, Value.FreeNotification(Self)就是做這個工作的!FreeNotification(self)將會把我們的組件置入其通知對象列表中,被撤消時,它會依次調(diào)用通知對象列表中所有對象的Notification方法,我們只需要在組件中重載它就行:
protected
{ Protected declarations }
procedure Notification(AComponent: TComponent;Operation: TOperation);override;
代碼實(shí)現(xiàn)如下:
procedure TMyDataBaseEdit.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (FDataLink <> nil) and
(AComponent = DataSource) then DataSource := nil;
end;
這樣,當(dāng)關(guān)聯(lián)的參考控件被刪除時,控件就會得到消息,來處理事件,防止異常的發(fā)生,否則將會導(dǎo)致Delphi開發(fā)環(huán)境的不穩(wěn)定甚至死機(jī),切記!
同時,不要忘了重載Destroy函數(shù):
destructor TMyDataBaseEdit.Destroy;
begin
inherited;
FDataLink.Free;
FDataLink := nil;
end;
確保使用完之后銷毀FDataLink的實(shí)例,釋放空間。無論是寫組件還是寫程序我們都要嚴(yán)格注意聲明的對象,一定要在使用完之后釋放!
添加DataField屬性
我們使用此控件的功能目的是想連接好數(shù)據(jù)庫組件后,通過選擇字段名,將其字段值顯示在Edit.Text中。那么我們還缺一個屬性.DataField。
published域聲明:
property DataField: string read GetFieldName write SetFieldName;
實(shí)現(xiàn)如下:
function TMyDataBaseEdit.GetFieldName: string;
begin
Result := FDataLink.FieldName;
end;
procedure TMyDataBaseEdit.SetFieldName(const Value: string);
begin
FDataLink.FieldName := Value;
end;
OK,這樣數(shù)據(jù)庫中的字段就將列在下拉框中了。
下面我們填寫我們的數(shù)據(jù)處理過程:
procedure TMyDataBaseEdit.DataChange(Sender: TObject);
begin
if FDataLink.Field <> nil then
begin
Text := FDataLink.Field.Text;
end;
end;
哈,現(xiàn)在基本上可以運(yùn)行起來了。現(xiàn)在,我們將Table,DataSource連接起來,來看看當(dāng)前記錄發(fā)生改變時,MyDataBaseEdit控件的Edit框中顯示所選字段的字段值
添加屬性頁(About)
現(xiàn)在基本功能已經(jīng)具備了,下面我們給它加上一個屬性頁,我們這里只是做一個About對話框演示,開發(fā)者可根據(jù)自己的實(shí)際功能要求自行定制。
我們實(shí)現(xiàn)的效果是要求:在屬性編輯瀏覽器中出現(xiàn)一項About屬性,右側(cè)顯示:(About)… 點(diǎn)擊“…”按鈕彈出一個對話框。
下面我們開始新建一個Package,按照命名規(guī)則起名:MyDataEditDsgn60.Dpk,然后新建一個Form,作為About對話框,我們可以根據(jù)需要自行設(shè)計界面。我們新建一個類TAboutEditor使其從TPropertyEditor下繼承過來:
TAboutEdit = class(TPropertyEditor)
FFrmAbout: TfrmAbout;
function GetAttributes: TPropertyAttributes;override;
function GetValue: string;override;
procedure Edit;override;
end;
注意:將全局的Form變量刪掉,重新在TAboutEdit類中聲明一個新的私有變量。
覆蓋Edit函數(shù):
procedure TAboutEdit.Edit;
begin
FFrmAbout := TfrmAbout.Create(Application);
FFrmAbout.ShowModal;
FFrmAbout.Free
end;
用來創(chuàng)建銷毀窗體對象。
覆蓋GetAttributes函數(shù):
function TAboutEdit.GetAttributes: TPropertyAttributes;
begin
Result := [paDialog,paReadOnly];
end;
告訴IDE將以何種工作方式進(jìn)行顯示,關(guān)于TPropertyAttributes可以參考幫助看看,這里不再贅述。我們這里是以只讀對話框的方式顯示的,Object Inspector將在About屬性旁邊出現(xiàn)一個省略號按鈕。當(dāng)用戶單擊這個按鈕,就調(diào)用Edit方法。
覆蓋GetValue函數(shù),是為了省略號按鈕旁出現(xiàn)“(About)”的字樣,并且只讀。
function TAboutEdit.GetValue: string;
begin
Result := '(About)'
end;
好了,工作基本完成,如果你還想進(jìn)一步控制,還可以在研究TPropertyEditor類的代碼。這里就不細(xì)說了。
也許你產(chǎn)生疑問了:這樣能行了嗎?控件怎么知道這個Package的屬性方法,沒關(guān)聯(lián)呀。對了,要想把編輯器和控件關(guān)聯(lián)起來還要2項工作:
1、 控件增加一個string 類型的”About”屬性
2、 在編輯器的單元里注冊屬性編輯器
第一步,很簡單,大家早就熟悉增加一個屬性的方法了:
private
FAbout: string
Published
Property About: string read FAbout write FAbout;
第二步,也只是在屬性編輯器單元增加一個注冊的全局過程函數(shù):
procedure Register
beging
//只有一句話:
RegisterPropertyEditor(TypeInfo(string),TMyDataBaseEdit,'About',TAboutEdit);
end;
現(xiàn)在,我來解釋一下RegisterPropertyEditor函數(shù):它用來注冊一個屬性編輯器,將控件中的一個屬性和編輯器關(guān)聯(lián)起來。
l 第一個參數(shù):TypeInfo(string),參數(shù)原型:PropertyType:PTypeInfo它是一個指針,指向要編輯的屬性的運(yùn)行期類型信息,我們通過TypeInfo()獲取。
l 第二個參數(shù):TMyDataBaseEdit,參數(shù)原型:Component: Calss,用于指定這個屬性編輯器所作用的組件類。
l 第三個參數(shù)是一個字符串,用于指定被作用屬性的名稱。
l 第四個參數(shù)用于指定屬性編輯器的類型。
假如第二個參數(shù)設(shè)為nil,第三個參數(shù)設(shè)為空串,會如何呢?嘿嘿,所有string類型的屬性全部變成About框了,哈哈。
大家注意,我們在這里只是簡單的做了一個About對話框,目的是為了能夠讓大家快速清晰地了解屬性編輯器的設(shè)計原理,我們也可以把它做成一個復(fù)雜的有返回值的對話框,這樣我們就可以真正用對話框來編輯控件的屬性了。這就要看各位的實(shí)際需要了,但萬便不離其宗,呵呵。
有興趣的話大家還可以研究一下Delphi的Source\Property Editors目錄下的StringsEdit單元。(關(guān)于屬性編輯器更詳細(xì)的各種類型、定義可以參考《Delphi5開發(fā)人員指南》第三部分“關(guān)于組件的開發(fā)”或仔細(xì)研究DesignEditors單元)
包的設(shè)計思想
在進(jìn)行編輯器設(shè)計的時候我們新建了一個Package組件包。組件包,大家應(yīng)該都比較了解它了,它類似于Dll,不過只是在Delphi和CBuilder環(huán)境中通用。很好的利用此包,可以使我們的程序模塊清晰,能最大限度的代碼重用,使程序的體積盡量減小,而且可以在這兩種語言環(huán)境中互用。這里就不多說了,否則跑題太遠(yuǎn),有興趣的話大家可以跟我聯(lián)系,共同探討。
現(xiàn)在,我來說一下為什么要和控件的包分開,單獨(dú)新建一個Package組件包。
首先,分塊劃分好管理,這不用多說了,一個運(yùn)行期包,一個設(shè)計期包,使應(yīng)用程序的體積變小,省得所有關(guān)于的單元全部鏈接到執(zhí)行程序中去(Delphi5及前版本),即使你只在程序中靜態(tài)地進(jìn)行鏈接,混合運(yùn)行時和設(shè)計時的代碼也將使你的代碼膨脹。因?yàn)槟愕脑O(shè)計時代碼在運(yùn)行時不會被執(zhí)行,但是編譯器并不知道,所以把它也一起鏈接進(jìn)去了。
其實(shí)重要的一個原因是,Delphi6版本以來,VCl結(jié)構(gòu)對Delphi5及前版本有所調(diào)整,Borland用DesignIntf替換了DsgnIntf,而且屬性編輯器也被放進(jìn)DesignEditors、DesignMenus、DesignWindows和其它的一些設(shè)計文件里。特別是DesignEditors使用了其它的一個名叫Proxies的IDE文件。而Proxies被編譯進(jìn)了DesignIDE.bpl文件中。DesignIDE.bpl已經(jīng)不再是一個可以分發(fā)的文件,我們只能在開發(fā)環(huán)境中使用,不用說,這時候編譯應(yīng)用程序時就出現(xiàn)“找不到文件”的問題了。這時,我們將運(yùn)行期包和設(shè)計期包分開,將設(shè)計期包加入designide.dcp文件,分別編譯包文件,就能保證正確無誤,編譯通過!
所以我們遵守一些約定,將使我們的代碼結(jié)構(gòu)達(dá)到最優(yōu)化,一般設(shè)置如下:
設(shè)計時包應(yīng)該包括:
1、 所有的注冊聲明(最好放在一個單獨(dú)的單元中);
2、 所有的屬性編輯器;
3、 所有的組件編輯器;
4、 將需要DesignIDE支持的包。
注:組件編輯器是通過在組件上彈出快捷菜單,象Table等組件。它的制作比較類似,是從TComponentEditor下繼承來的。這里不再多講了。
運(yùn)行時包應(yīng)該包括:
1、 組件本身。
2、 組件可能在運(yùn)行時調(diào)用的窗體(屬性編輯器用到的)。
Package部分由于版本不同而對不同的版本要進(jìn)行不同的設(shè)計,Delphi 6.0的編輯器版本是VER140,Delphi 5.0的編輯器版本是VER130,如果我們想適應(yīng)不同的版本需要使用 {$IFDEF VER140}。。。{$ELSE}。。。{$ENDIF};在Uses域進(jìn)行控制,這點(diǎn)大家稍加注意。