property設計上的一個問題 |
答題得分者是:syntax
|
P.D.
版主 發表:603 回覆:4038 積分:3874 註冊:2006-10-31 發送簡訊給我 |
|
syntax
尊榮會員 發表:26 回覆:1139 積分:1258 註冊:2002-04-23 發送簡訊給我 |
property iamcaption: String read getCap write SetCap;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
將這行放在 publish,然後按下 Ctrl Shift C 會自動產生
getCap 與 SetCap 在 private ,當然妳也可以自己 key in 這樣就是了,不用特別處理,只要妳宣告的是 String , Delphi 會幫妳處理剩下的,除非妳要特別什麼功能,不然不用自己寫 然後在 getCap 中將妳要的 Cap 傳出來
在 SetCap 設回去 function getCap:String;
begin Result := the_cap_you_want.Caption; end; procedure SetCap(const Value: String);
begin the_cap_you_want.Caption := Value; end; 這樣妳就可以在 objectinspactor 上修改
不過如果妳要向那樣加以 group 的話,那必須特別再處理,也不難,只要用另一個 class 來宣告與修改那些 caption 然後在主 class 內的property 改成
property groupcap: yourclass;
而該 class 是
yourclass = class function getA......
function getB......
property SetA......
property SetB...... property a:string read getA write SetA;
property b:string read getB write SetB;
......
......
...... as many as you want end; 同樣的剩下 Delphi 會幫妳處理
|
P.D.
版主 發表:603 回覆:4038 積分:3874 註冊:2006-10-31 發送簡訊給我 |
謝謝 syntax 指導, 我大致已做出樣子, 但現有另一個問題, 就是我改了properity的內容, 並無法使元件上的button.caption同步異動, 以下是部份截錄, 不知那裡有疏漏, 請指教(.... 符號代表類似的寫法)
ps:問題發生在改 properity 無法觸發 setCaption 事件
謝謝!
unit NAVIGATOR; interface uses SysUtils, Classes, DBctrls, Dialogs, Db, DBtables, Messages, Windows; var fFirstCap: string; fPriorCap: string; ...... type TCaptionSet = Class(TPersistent) private function GetFirstCap: string; function GetPriorCap: string; ..... procedure setFirstCap(Value: string); procedure setPriorCap(Value: string); .... constructor Create; published property First: string read getFirstCap write setFirstCap; property Prior: string read getPriorCap write setPriorCap; property Next: string .... end; TmNavigator = class (TDBNavigator) private fBtnCaptions: TCaptionSet; function GetCaption: TCaptionSet; procedure SetCaption(const Value: TCaptionSet); ..... public .... Constructor Create(Aowner:TComponent);override; destructor Destroy; override; published .... property CaptionSet: TCaptionSet read GetCaption write SetCaption; end; procedure Register; implementation procedure Register; .... Constructor TmNavigator.Create(Aowner:TComponent); var i:TNavigateBtn; begin inherited Create(Aowner); fFontSize:= 9; fBtnCaptions:= TCaptionSet.Create; Buttons[nbFirst].Caption := fBtnCaptions.First; Buttons[nbPrior].Caption := fBtnCaptions.Prior; ..... end; .... function TmNavigator.GetCaption; begin fBtnCaptions.First := fFirstCap; fBtnCaptions.Prior := fPriorCap; ..... result:= fBtnCaptions; end; procedure TmNavigator.SetCaption(const Value: TCaptionSet); begin Buttons[nbFirst].Caption := Value.First; Buttons[nbPrior].Caption := Value.Prior; ..... end; { TCaptionSet } constructor TCaptionSet.Create; begin fFirstCap := '首筆'; fPriorCap := '上筆'; .... end; function TCaptionSet.GetFirstCap: string; begin result:= fFirstCap; end; function TCaptionSet.GetPriorCap: string; begin result:= fPriorCap; end; ..... procedure TCaptionSet.setFirstCap(Value: string); begin fFirstCap:= Value; end; procedure TCaptionSet.setPriorCap(Value: string); begin fPriorCap:= Value; end; end. 這是做出來的樣式 |
syntax
尊榮會員 發表:26 回覆:1139 積分:1258 註冊:2002-04-23 發送簡訊給我 |
妳做得都沒錯,已經是正確的 proterty 原形
只是妳的程式設計邏輯出問題
你設計了一個 property 但是該 property 會去修改 ???
以下是妳的程式流程 1.讀取 CaptionSet 發現是 Class 所以帶入 Class property Editor 因此有個加號產生,同時可以展開
2.展開後顯示 First 發現要經由 getFirstCap 來讀取,所以程式呼叫 getFirstCap
3.getFirstCap 傳回其值,根據妳的程式 result = fFirstCap,而 fFirstCap 是一個全域的變數,是你在 Create 時有設定初值為"首筆"
4.當妳要設定 First 時,呼叫的是 setFristCap ,會將數值經由 Value 傳入 setFirstCap
5.setFirstCap 會將 Value 設定到 fFirstCap 這就是妳的程式流程,以上略過 SetCaption 與 GetCaption,因為那是不必要的,妳一件事做了兩次,不過那不是主要問題 到這裡,妳知道問題在哪裡了嗎? 問題在於,妳創造了一個全域變數,同時設計了存取那個變數的 property
這樣跟妳想修改的 button.caption 有什麼關係? 所以整理一下妳的設計缺點 1.妳沒有確實改到妳要的東西
2.每一個妳放到 Form 上的 Navigator 都會對那個全域變數作存取,如果 Nav A 設 "1";Nav B 設 "2" ; Nav C 設 "3",最後 fFirstCap 是哪一個?
喔~是的 fFirstCap 將會是 "3",如果今天你可以同步更新,妳將會問,為什麼 Nav 間的 property 會互相影響?
因為根本就是改到同一個東西
3.妳同一件做了兩次
property CaptionSet: TCaptionSet read GetCaption write SetCaption; 是不需要再另寫程式來存取
直接使用 property CaptionSet: TCaptionSet read fBtnCaptions write fBtnCaptions; 即可
這個比較沒有關係,不影響程式的正確性,邏輯也正確,只是不需要做兩次,同時還必須冒兩個風險
--a.妳可能使用看似正確但結果將不會是你想要的語法,在這裡,差一點就差很多,一個是位址的傳遞,一個是數值的傳遞,會差很多
--b.不過你選擇的作法,可以避過 a. 的問題,但是要是你有 1000000 個直要設定,那 fBtnCaptions.First := fFirstCap 這種東西,妳就必須有 1000000 行,這種會造成你維護與修改的難度與時間上的浪費 以下給妳參考 1.class 的選用,通常直接用 class 就可以了,除非妳需要其他能力,不然就這樣用,這裡我們可能會需要資訊流的能力,所以加上(TPersistent),如需元件的能力就加上(TComponent) 吧
2.如何設計?
---a.採用 TString ,這樣可以如 Hint 的方式透過一個編輯視窗來統一,一起設定
---b.不果妳要求的是要有個收納功能,這樣若是使用 TString ,勢必重新設計一個 Editor 來達到妳的要求,這樣難度會比較高
---c.如果妳不介意,其實所有的 cap 也不算多,雖然也不少,直接寫在 Nav 內,會比較簡單(應該是簡單許多),只是在事件檢視器內會多那8行,且不能收納
---d.使用之前所說的採用 Delphi 的優勢,盡量使用已經有的功能
a. b. c. 與 d. 3 種方法各有優缺,沒有說哪種最好,只有妳喜歡與妳需要哪種 以下以 d. 來說,此時我們必須設計一個 Meta class 來達到我們的要求,所以該 class 必需要知道是那個 Nav 擁有他,這樣才有辦法,來處理
而原本我們是希望可以直接在 Meta class 就完全設定好,但是我們發現,並沒有一個直接的方法可以存取那些 Caption,因為 Botton 是在 Nav 的 protected 區段
只有其子代可以存取,所以我們有兩個選擇:(1).將 botton 的可見度改到 public (2).將實際設定的方法寫在 Nav 內,Meta Class 只是一個橋樑與Group整理用 因為 (1) 會破壞程式原來的 OOP 原則,也就是會改到可見度的原規劃,這種選擇應該是最後的選擇
所以我們以 (2) 來實做,以下是範例 TNavCapGroup = class(TPersistent)
//** 用來做 Group 的 Meta Class
private
{ Private declarations }
FOwner: TComponent;
function GetFirstCap: String;
procedure SetFirstCap(const Value: String); protected
{ protected declarations } public
{ Public declarations }
constructor Create(AOwner: TComponent); virtual;
//** 用來設定 FOwner published
{ published declarations }
property FirstCap: String read getFirstCap write setFirstCap;
//** ..... 其餘妳自己來囉 end; TDBNavigator_Ex = class(TDBNavigator)
private
{ Private declarations }
FtheNavCap: TNavCapGroup;
function GetCaptions(const BtnType: TNavigateBtn): String;
procedure SetCaptions(const BtnType: TNavigateBtn;const Value: String);
//** 實際做設定的程式碼 protected
{ protected declarations } public
{ Public declarations }
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
妳也可以改在 create 與 destory 做處理,只要時間正確,不要有未 create 就去存取,或是 destory 但尚未完成時還去存取 published
{ published declarations }
property BtnCaptions: TNavCapGroup read FtheNavCap write FtheNavCap; end; { TDBNavigator_Ex } procedure TDBNavigator_Ex.AfterConstruction;
begin inherited;
FtheNavCap := TNavCapGroup.Create(Self); end; procedure TDBNavigator_Ex.BeforeDestruction;
begin FreeAndNil(FtheNavCap);
inherited; end; function TDBNavigator_Ex.GetCaptions(const BtnType: TNavigateBtn): String;
begin Result := Buttons[BtnType].Caption; end; procedure TDBNavigator_Ex.SetCaptions(const BtnType: TNavigateBtn;const Value: String);
begin Buttons[BtnType].Caption := Value; end; { TNavCapGroup } constructor TNavCapGroup.Create(AOwner: TComponent);
begin if AOwner <> nil then
if AOwner.ClassType = TDBNavigator_Ex then
FOwner := AOwner
else
raise Exception.Create('只有 TDBNavigator_Ex 能做為擁有者,因為其才有對應的程式碼')
else raise Exception.Create('擁有者,未知,無法運作'); end; function TNavCapGroup.GetFirstCap: String;
begin TDBNavigator_Ex(FOwner).GetCaptions(nbFirst); end; procedure TNavCapGroup.SetFirstCap(const Value: String);
begin TDBNavigator_Ex(FOwner).SetCaptions(nbFirst,Value); end; end. 這樣就可以做到你要的成果囉,只是這樣還有幾件事要做 1.妳的修改不會被儲存,所以妳必須自己寫入資料流,不然就白做囉
2.當修改後,妳必須重新設定 Btn 的大小來調整,不然妳的自會被截斷,同時當 Nav 大小修改時,也必須重新計算是否要可以繼續縮小,如果不行,就限制其最小的大小,目的也是讓妳輸入的字可以完整顯示
要注意,如果同時有圖形顯示,看是你要取消圖形的顯示,或是聽圖形與字同時顯示,將會影響計算完的大小 這兩點做好後,就是一個完整的功能啦
|
P.D.
版主 發表:603 回覆:4038 積分:3874 註冊:2006-10-31 發送簡訊給我 |
|
syntax
尊榮會員 發表:26 回覆:1139 積分:1258 註冊:2002-04-23 發送簡訊給我 |
|
P.D.
版主 發表:603 回覆:4038 積分:3874 註冊:2006-10-31 發送簡訊給我 |
十分感激, 由於不知道class要如何可以存資料, 我改了下面的做法, 可以讓設定存起來
如圖
type TCaptionSet = Class(TPersistent) private FOwner: TComponent; fFirst: string; FPrior: string; fNext: string; fInsert: string; fDelete: string; fLast: string; fEdit: string; fPost: string; fCancel: string; fRefresh: string; procedure setFirstCap(const Value: string); ..... public constructor Create(AOwner: TComponent); virtual; published property First: string read fFirst write setFirstCap; .... TmNavigator = class (TDBNavigator) private fFontSize:integer; fShowType:ShowTypeName; fBtnCaptions: TCaptionSet; procedure SetCaptions(const BtnType: TNavigateBtn; const Value: String); public procedure SetBounds(ALeft,ATop,AWidth,AHeight:integer);override; ..... Constructor Create(Aowner:TComponent); override; destructor Destroy; override; published property CaptionSet: TCaptionSet read fBtnCaptions write fBtnCaptions; .... procedure Register; implementation const BtnCapArray: Array [TNavigateBtn] of String = ('首筆','上筆','下筆','末筆','新增','刪除','修改','寫入','取消','更新'); .... Constructor TmNavigator.Create(Aowner:TComponent); var i: TNavigateBtn; begin inherited Create(Aowner); fBtnCaptions:= TCaptionSet.Create(self); fFontSize:= 9; for i:=Low(Buttons) to High(Buttons) do begin Buttons[i].Glyph.Assign(nil); Buttons[i].Font.Size:= fFontSize; Buttons[i].Caption:= BtnCapArray[i]; end; end; destructor TmNavigator.Destroy; begin inherited; FreeandNil(fBtnCaptions); end; procedure TmNavigator.SetCaptions(const BtnType: TNavigateBtn; const Value: String); begin Buttons[BtnType].Caption:= Value; end; { TCaptionSet } constructor TCaptionSet.Create(AOwner: TComponent); begin if AOwner <> nil then if AOwner.ClassType = TmNavigator then FOwner:= AOwner else raise Exception.Create('只有 TmNavigator 能做為擁有者') else raise Exception.Create('擁有者--未知,無法運作'); fFirst := '首筆'; fPrior := '上筆'; fNext := '下筆'; fLast := '末筆'; fInsert := '新增'; fDelete := '刪除'; fEdit := '修改'; fPost := '寫入'; fCancel := '取消'; fRefresh:= '更新'; end; .... procedure TCaptionSet.setFirstCap(const Value: string); begin TmNavigator(FOwner).SetCaptions(nbFirst, Value); fFirst:= Value; end;雖然有點土法煉鋼, 不過結果是可以達成我想要的方式, 期待觀摩syntax的做法! |
syntax
尊榮會員 發表:26 回覆:1139 積分:1258 註冊:2002-04-23 發送簡訊給我 |
我是這樣做的,程式碼如下:
注意:
1.我有使用一個資源檔,其資源名稱是以 DBN_CHT_為開頭 結合BtnTypeName 陣列中名稱而成為完整資源名稱
2.預設文字在上,圖形在下,以減少計算顯示完整文字的時間消耗
3. GetFirstCap , SetFirstCap 經由改成泛用形,已經不是專給 First 設定用了,不過完成後才發現,又是一個懶得改的地方,可以改成 GetBtnCap , SetBtnCap ,這樣就不會混淆囉 有待補強的地方:
1.尚未給予所有的 Btn 初值,恩,偷懶沒做
2.應該設計可以選擇使用,文字、圖形,或同時使用的 property ,所以可以看到,文字與圖形同時出現,上一行是文字,下方是圖形 那個有紅色的按鈕,是執行時的圖,上方式設計時,左方是 Object Inspector
unit DBCtrls_Ex; //** 建立日期 : 20031208 //** 修改日期 : 20040204,20050323 //** 設計者 : Syntax //** 名稱 : VCL-Data-Controls Extension //** 功能 : //** 元件自訂加強能力 {$R 'Data Controls.RES'} interface uses DBCtrls, Classes, Messages; type TNavCapGroup = class(TPersistent) private { Private declarations } FOwner: TComponent; function GetFirstCap(const BtnType: TNavigateBtn): String; procedure SetFirstCap(const BtnType: TNavigateBtn;const Value: String); protected { protected declarations } public { Public declarations } constructor Create(AOwner: TComponent); virtual; published { published declarations } //** 因為是 String 型別, Delphi 已有處理方式,只要宣告在 pbulished ,就會被自動儲存囉~ 真好,不用自己再處理 defineproperty 等類似的咚咚 property c0_First: String index nbFirst read GetFirstCap write SetFirstCap; property c1_Prior: String index nbPrior read GetFirstCap write SetFirstCap; property c2_Next: String index nbNext read GetFirstCap write SetFirstCap; property c3_Last: String index nbLast read GetFirstCap write SetFirstCap; property c4_Insert: String index nbInsert read GetFirstCap write SetFirstCap; property c5_Delete: String index nbDelete read GetFirstCap write SetFirstCap; property c6_Edit: String index nbEdit read GetFirstCap write SetFirstCap; property c7_Post: String index nbPost read GetFirstCap write SetFirstCap; property c8_Cancel: String index nbCancel read GetFirstCap write SetFirstCap; property c9_Refresh: String index nbRefresh read GetFirstCap write SetFirstCap; //** 這樣做可以減少需要 Key in 的數量 ^_^,利用 Deplhi 本來就有寫好的程式碼,加上 property 的特性 //** 等於,快樂的 key in 時間,整個程式幾乎就在 Copy & Past 中渡過 ,超快,至於那個 c0 ~ c9 //** 是為了在 object inspector 中能夠依我想要的方式排列,注意,程式碼中 property 的寫作順序 //** 就是其創立與讀取的順序,不過 object inspector 就是會重新排列,為了好看,所以加入 cx 字頭 end; TDBNavigator_Ex = class(TDBNavigator) private { Private declarations } FtheNavCap: TNavCapGroup; procedure ReDefineBTG; procedure ReDefineHints; //** Redefinexxx 是之前做好玩的功能,只是將圖形換一換,提示改成中文 function GetCaptions(const BtnType: TNavigateBtn): String; procedure SetCaptions(const BtnType: TNavigateBtn;const Value: String); procedure WMSize(var Message: TWMSize); message WM_SIZE; //** 用來避免,縮的太小,造成字被切斷或遮住,如果不介意那樣,那這個程序可以刪掉 protected { protected declarations } public { Public declarations } procedure AfterConstruction; override; procedure BeforeDestruction; override; //** 可以用 create and destory 來取代 published { published declarations } property BtnCaptions: TNavCapGroup read FtheNavCap write FtheNavCap; //** 這樣做可以直接讀取元件,但是要小心,千萬不可以下BthCaptions.Free 喔 ~ 不然..... //** 這就是當 property 使用 class 時的缺點,有時不小心會 Free 到不該 Free 的地方,容易造成錯誤與盲點 //** 不過改成另一種寫法後可以避免,但是卻會造成另一個問題,而該問題也是同樣的性質 //** 所以哪種方式都一樣,使用時都有要小心的地方 end; procedure Register; implementation uses SysUtils, Buttons, Forms, Controls; var BtnTypeName: array[TNavigateBtn] of AnsiString = ('FIRST', 'PRIOR', 'NEXT', 'LAST', 'INSERT', 'DELETE', 'EDIT', 'POST', 'CANCEL', 'REFRESH'); procedure Register; begin RegisterComponents('Samples',[TDBNavigator_Ex]); end; { TDBNavigator_Ex } procedure TDBNavigator_Ex.AfterConstruction; begin inherited; FtheNavCap := TNavCapGroup.Create(Self); Height := 38; ReDefineBTG; ReDefineHints; ShowHint := True; end; procedure TDBNavigator_Ex.BeforeDestruction; begin FreeAndNil(FtheNavCap); inherited; end; function TDBNavigator_Ex.GetCaptions(const BtnType: TNavigateBtn): String; begin Result := Buttons[BtnType].Caption; end; procedure TDBNavigator_Ex.ReDefineBTG; var I: TNavigateBtn; ResName: string; begin //** Load Graph for I := Low(Buttons) to High(Buttons) do begin FmtStr(ResName, 'DBN_CHT_%s', [BtnTypeName[I]]); Buttons[I].Glyph.LoadFromResourceName(HInstance, ResName); Buttons[I].NumGlyphs := 4; Buttons[I].Layout := blGlyphBottom; end; end; procedure TDBNavigator_Ex.ReDefineHints; begin Hints.Text := '首筆 資料' + #13#10 + '前一筆 資料' + #13#10 + '次一筆 資料' + #13#10 + '尾筆 資料' + #13#10 + '新增 資料' + #13#10 + '刪除 資料' + #13#10 + '編輯 資料' + #13#10 + '確定/儲存 資料' + #13#10 + '放棄修改 資料' + #13#10 + '資料顯示 更新'; end; procedure TDBNavigator_Ex.SetCaptions(const BtnType: TNavigateBtn;const Value: String); var CT: TMEssage; A: TControl; begin Buttons[BtnType].Caption := Value; CT.LParamLo := Width; CT.LParamHi := Height; Perform(WM_SIZE,0,CT.LParam); //** Nav 整體的 maintain 是由 messege of WM_SIZE --> SetSize __> CalM..._-> SetSize --> Return 來達成 //** 已經有的東西,拿來用就好,直接傳個 Msg ,所有 Btn 就會自動排好,不是很棒嗎? if csDesigning in ComponentState then UpDate; //** 至於這行,是要給 Design time 時通知 Delphi 該 UpDate 該物件囉,不然會面不會統一,至於執行時,就不用了 //** 因為 Msg 已經做好了,而一件事不用作兩次 end; procedure TDBNavigator_Ex.WMSize(var Message: TWMSize); var Count,TextW,Temp,W,L: Integer; I: TNavigateBtn; begin if Buttons[nbFirst] <> nil then begin Count := 0; TextW := 0; for I := Low(Buttons) to High(Buttons) do begin if Buttons[I].Visible then begin Inc(Count); Temp := Canvas.TextWidth(Buttons[I].Caption + ' '); if TextW < Temp then TextW := Temp; end; end; Temp := TextW * Count; end; //** 計算最長字串所需寬度 if Message.Width < Temp then Width := Temp else inherited; //** 因為其他屬性與變數都是不可見的,所以只能用 Width 來修正大小,但是卻會造成重複呼叫,這裡不用擔心會造成無限回圈 //** 因為 Delphi 在 Width直沒有變化的狀況下,即使妳去設定他,也不會做任何事 //** 不過能然會造成 WM_SIZE 被呼叫 2 次,我不喜歡那一次多做的事,所以加個條件來處理,剛好設定 width 與 inherited 可以處理成如上 //** 若是設定 width 則會引發另一個 Msg 則這個 Msg 的 inherited 就可以不用呼叫,省了一個 //** 不過還是可以做的更好,在加個變數與檢查,就可以再減少一次計算最長字串所需寬度,這裡我偷懶囉 end; { TNavCapGroup } constructor TNavCapGroup.Create(AOwner: TComponent); begin if AOwner <> nil then if AOwner.ClassType = TDBNavigator_Ex then FOwner := AOwner else raise Exception.Create('只有 TDBNavigator_Ex 能做為擁有者,因為其才有對應的程式碼') else raise Exception.Create('擁有者,未知,無法運作'); end; function TNavCapGroup.GetFirstCap(const BtnType: TNavigateBtn): String; begin Result := TDBNavigator_Ex(FOwner).GetCaptions(BtnType); end; procedure TNavCapGroup.SetFirstCap(const BtnType: TNavigateBtn;const Value: String); begin TDBNavigator_Ex(FOwner).SetCaptions(BtnType,Value); end; end.發表人 - syntax 於 2005/03/23 21:33:37 發表人 - syntax 於 2005/03/23 21:41:07 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |