看了以上這么多的技巧和方法,想必大家未免會(huì)有一種沖動(dòng)吧-自己動(dòng)手做一個(gè)DBGrid,下面就介紹一種自制DBGrid的方法啦。 Delphi中的TDBGrid是一個(gè)使用頻率很高的VCL元件。TDBGrid有許多優(yōu)良的特性,例如它是數(shù)據(jù)綁定的,能夠定義功能強(qiáng)大的永久字段,事件豐富等,特別是使用非常簡(jiǎn)單。但是,與FoxPro、VB 、PB中的DBGrid相比就會(huì)發(fā)現(xiàn),TDBGrid也有明顯的缺陷:它的鍵盤操作方式非常怪異難用。雖然很多人都通過(guò)編程把回車鍵轉(zhuǎn)換成Tab鍵來(lái)改進(jìn)TDBGrid的輸入方式,但是仍然不能很好地解決問(wèn)題,這是為什么呢?本文將對(duì)造成這種缺陷的根本原因進(jìn)行分析,并在此基礎(chǔ)上制作一個(gè)輸入極其簡(jiǎn)便、界面風(fēng)格類似Excel的DBGridPro元件。 DBGrid的格子(Cell)有四種狀態(tài):輸入狀態(tài)(有輸入光標(biāo),可以輸入,記作狀態(tài)A1);下拉狀態(tài)(彈出了下拉列表,可以選擇,記作狀態(tài)A2);高亮度狀態(tài)(沒(méi)有輸入光標(biāo),可以輸入,記作狀態(tài)B);顯示狀態(tài)(不能輸入,記作狀態(tài)C)。DBGrid接受的控制鍵有回車,Tab,Esc,以及方向鍵。據(jù)此可以畫(huà)出每個(gè)Cell的狀態(tài)轉(zhuǎn)換圖: 不難看出,當(dāng)用戶移動(dòng)輸入焦點(diǎn)時(shí),對(duì)不同的移動(dòng)方向要用不同的操作方法,甚至可能必須使用多個(gè)不同的鍵或借助鼠標(biāo)來(lái)完成一個(gè)操作。當(dāng)有下拉列表和要斜向移動(dòng)的時(shí)候這種問(wèn)題尤為明顯。因此,輸入困難的根本原因是其狀態(tài)圖過(guò)于復(fù)雜和不一致。基于這種認(rèn)識(shí),我們可以對(duì)DBGrid作三點(diǎn)改造: 改造1:顯然B狀態(tài)是毫無(wú)意義的,應(yīng)該去掉。這意味著焦點(diǎn)每進(jìn)入一個(gè)新的Cell,就立即進(jìn)入編輯狀態(tài),而不要再按回車了。每個(gè)進(jìn)入狀態(tài)B的Cell都需要重新繪制,因此我們可以在繪制動(dòng)作中判斷是否有狀態(tài)為gdFocused的Cell,若有則設(shè)置EditorMode為真。值得注意的是,TDBGrid用來(lái)畫(huà)Cell的函數(shù)DefaultDrawColumnCell并不是虛函數(shù),因此不能通過(guò)繼承改變其行為,而只能使用其提供的事件OnDrawColumnCell來(lái)插入一些動(dòng)作。在DBGridPro中,這一點(diǎn)是通過(guò)實(shí)現(xiàn)顯示事件OnDrawColumnCell來(lái)實(shí)現(xiàn)的。但是這樣一來(lái),外部對(duì)象就不能使用該事件了,所以提供了一個(gè)OnOwnDrawColumnCell事件來(lái)替代它。見(jiàn)代碼中的Create和DefaultDrawColumnCell函數(shù)。 改造2:控制鍵應(yīng)該簡(jiǎn)化,盡量增加每個(gè)控制鍵的能力。在DBGridPro中,強(qiáng)化了方向鍵和回車鍵的功能:當(dāng)光標(biāo)在行末行首位置時(shí),按方向鍵就能跳格;回車能橫向移動(dòng)輸入焦點(diǎn),并且還能彈出下拉列表(見(jiàn)改造3)。在實(shí)現(xiàn)方法上,可以利用鍵盤事件API(keybd_event)來(lái)將控制鍵轉(zhuǎn)換成TDBGrid的控制鍵(如在編輯狀態(tài)中回車,則取消該事件并重新發(fā)出一個(gè)Tab鍵事件)。當(dāng)監(jiān)測(cè)到左右方向鍵時(shí),通過(guò)向編輯框發(fā)送EM_CHARFROMPOS消息判斷編輯框中的光標(biāo)位置,以決定是否應(yīng)該跳格。見(jiàn)代碼中的DoKeyUped函數(shù)。 改造3:簡(jiǎn)化下拉類型Cell的輸入方式。在DBGridPro中,用戶可以用回車來(lái)彈出下拉列表。這種方式看起來(lái)可能會(huì)造成的回車功能的混淆,但是只要處理得當(dāng),用戶會(huì)覺(jué)得非常方便:當(dāng)進(jìn)入下拉類型的Cell之后,如果用戶直接鍵入修改,則按回車進(jìn)入下一格;否則彈出下拉列表,選擇之后再按回車時(shí)關(guān)閉下拉列表并立即進(jìn)入下一格。見(jiàn)代碼中的DoKeyUped函數(shù)和DefaultDrawColumnCell函數(shù)。 一番改造之后,用戶輸入已經(jīng)非常方便了,但是又帶來(lái)了新的問(wèn)題:在TDBGrid中,用戶可以通過(guò)高亮度的Cell很快知道焦點(diǎn)在哪里,而DBGridPro中根本不會(huì)出現(xiàn)這種Cell,所以用戶可能很難發(fā)現(xiàn)輸入焦點(diǎn)!一種理想的方法是像Excel一樣在焦點(diǎn)位置處放一個(gè)黑框--這一點(diǎn)是可以實(shí)現(xiàn)的(如圖2)。 Windows中提供了一組API,用于在窗口上建立可接受鼠標(biāo)點(diǎn)擊事件的區(qū)域(Region)。多個(gè)Region可以以不同的方式組合起來(lái),從而得到"異型"窗口,包括空心窗口。DBGridPro就利用了這個(gè)功能。它在內(nèi)部建立了一個(gè)黑色的Panel,然后在上面設(shè)置空心的Region,并把它"套"在有輸入焦點(diǎn)的Cell上,這樣用戶就能看到一個(gè)醒目的邊框了。 好事多磨,現(xiàn)在又出現(xiàn)了新的問(wèn)題:當(dāng)Column位置或?qū)挾雀淖儠r(shí),其邊框必須同步變化。僅利用鼠標(biāo)事件顯然不能完全解決這個(gè)問(wèn)題,因?yàn)樵诔绦蛑幸部梢栽O(shè)置Column的寬度;用事件OnDrawColumnCell也不能解決(寬度改變時(shí)并不觸發(fā)該事件)。幸運(yùn)的是,TDBGrid中的輸入框?qū)嶋H上是一個(gè)浮動(dòng)在它上面的TDBGridInplaceEdit(繼承自TInplaceEdit),如果我們能監(jiān)測(cè)到TDBGridInplaceEdit在什么時(shí)候改變大小和位置,就可以讓邊框也跟著改變了。要實(shí)現(xiàn)這一點(diǎn),用一個(gè)從TDBGridInplaceEdit繼承的、處理了WM_WINDOWPOSCHANGED消息的子類來(lái)替換原來(lái)的TDBGridInplaceEdit將是最簡(jiǎn)單的辦法。通過(guò)查看源代碼發(fā)現(xiàn),輸入框由CreateEditor函數(shù)創(chuàng)建的,而這是個(gè)虛函數(shù)--這表明TDBGrid愿意讓子類來(lái)創(chuàng)建輸入框,只要它是從TInplaceEdit類型的。從設(shè)計(jì)模式的角度來(lái)看,這種設(shè)計(jì)方法被稱為"工廠方法"(Factory Method),它使一個(gè)類的實(shí)例化延遲到其子類??磥?lái)現(xiàn)在我們的目的就要達(dá)到了。 不幸的是,TDBGridInplaceEdit在DBGrids.pas中定義在implement中(這樣外部文件就無(wú)法看到其定義了),因此除非把它的代碼全部拷貝一遍,或者直接修改DBGrids.pas文件(顯然這前者不可取;后者又會(huì)帶來(lái)版本兼容性問(wèn)題),我們是不能從TDBGridInplaceEdit繼承的。難道就沒(méi)有好辦法了嗎?當(dāng)然還有:我們可以利用TDBGridInplaceEdit的可讀寫(xiě)屬性WindowProc來(lái)捕獲WM_WINDOWPOSCHANGED消息。WindowProc實(shí)際上是一個(gè)函數(shù)指針,它指向的函數(shù)用來(lái)處理發(fā)到該窗口元件的所有消息。于是,我們可以在CreateEditor中將創(chuàng)建出的TDBGridInplaceEdit的WndProc替換成我們自己實(shí)現(xiàn)的勾掛函數(shù)的指針,從而實(shí)現(xiàn)和類繼承相同的功能。這樣做的缺點(diǎn)是破壞了類的封裝性,因?yàn)槲覀儾坏貌辉贒BGridPro中處理屬于TDBGridInplaceEdit的工作。當(dāng)然,可能還有其他更好的方法,歡迎讀者提出建議。 至此,TDBGrid已經(jīng)被改造成一個(gè)操作方便、界面美觀的DBGridPro了,我們可以把它注冊(cè)成VCL元件使用。以下是它的源代碼: unit DBGridPro; interface uses Windows, Messages, SysUtils, Classes, Controls, Grids, DBGrids, ExtCtrls, richEdit, DBCtrls, DB; type TCurCell = Record {當(dāng)前焦點(diǎn)Cell的位置} X : integer; {有焦點(diǎn)Cell的ColumnIndex} Y : integer; {有焦點(diǎn)Cell所在的紀(jì)錄的紀(jì)錄號(hào)} tag : integer; {最近進(jìn)入該Cell后是否彈出了下拉列表} r : TRect; {沒(méi)有使用} end; type TDBGridPro = class(tcustomdbgrid) private hr,hc1 : HWND; {創(chuàng)建空心區(qū)域的Region Handle} FPan : TPanel; {顯示黑框用的Panel} hInplaceEditorWndProc : TWndMethod; {編輯框原來(lái)的WindowProc} {勾掛到編輯框的WindowProc} procedure InPlaceEditorWndProcHook(var msg : TMessage); procedure AddBox; {顯示邊框} {實(shí)現(xiàn)TCustomDBGrid的OnDrawColumnCell事件} procedure DoOwnDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); {處理鍵盤事件} procedure DoKeyUped(Sender: TObject; var Key: Word; Shift: TShiftState); protected curCell : TCurCell; {記錄當(dāng)前有焦點(diǎn)的Cell} FOwnDraw : boolean; {代替TCustomDBGrid.DefaultDrawing} FOnDraw : TDrawColumnCellEvent; {代替TCustomDBGrid.OnDrawColumnCell} function CreateEditor : TInplaceEdit; override; procedure KeyUp(var Key: Word; Shift: TShiftState); override; procedure DefaultDrawColumnCell(const Rect: TRect;DataCol: Integer; Column: TColumn; State: TGridDrawState); overload; public constructor Create(AOwner : TComponent); override; destructor Destroy; override; published property Align; property Anchors; property BiDiMode; property BorderStyle; property Color; property Columns stored False; //StoreColumns; property Constraints; property Ctl3D; property DataSource; property OwnDraw : boolean read FOwnDraw write FOwnDraw default false; property DragCursor; property DragKind; property DragMode; property Enabled; property FixedColor; property Font; property ImeMode; property ImeName; property Options; property ParentBiDiMode; property ParentColor; property ParentCtl3D; property ParentFont; property ParentShowHint; property PopupMenu; property ReadOnly; property ShowHint; property TabOrder; property TabStop; property TitleFont; property Visible; property OnCellClick; property OnColEnter; property OnColExit; property OnColumnMoved; property OnDrawDataCell; { obsolete } property OnOwnDrawColumnCell : TDrawColumnCellEvent read FOnDraw write FOnDraw; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEditButtonClick; property OnEndDock; property OnEndDrag; property OnEnter; property OnExit; property OnKeyup; property OnKeyPress; property OnKeyDown; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnStartDock; property OnStartDrag; property OnTitleClick; end; procedure Register; implementation procedure Register; begin RegisterComponents('Data Controls', [TDBGridPro]); end; { TDBGridPro } procedure TDBGridPro.AddBox; var p,p1 : TRect; begin GetWindowRect(InPlaceEditor.Handle,p); GetWindowRect(FPan.Handle,p1); if (p.Left=p1.Left) and (p.Top=p1.Top) and (p.Right=p1.Right) and (p.Bottom=p1.Bottom) then exit; if hr<>0 then DeleteObject(hr); if hc1<>0 then DeleteObject(hc1); {創(chuàng)建內(nèi)外兩個(gè)Region} hr := CreateRectRgn(0,0,p.Right-p.Left+4,p.Bottom-p.Top+4); hc1:= CreateRectRgn(2,2,p.Right-p.Left+2,p.Bottom-p.Top+2); {組合成空心Region} CombineRgn(hr,hc1,hr,RGN_XOR); SetWindowRgn(FPan.Handle,hr,true); FPan.Parent := InPlaceEditor.Parent; FPan.ParentWindow := InPlaceEditor.ParentWindow; FPan.Height := InPlaceEditor.Height+4; FPan.Left := InPlaceEditor.Left-2; FPan.Top :=InPlaceEditor.Top-2; FPan.Width := InPlaceEditor.Width+4; FPan.BringToFront; end; constructor TDBGridPro.Create(AOwner: TComponent); begin inherited; {創(chuàng)建作為邊框的Panel} FPan := TPanel.Create(nil); FPan.Parent := Self; FPan.Height := 0; FPan.Color := 0; FPan.Ctl3D := false; FPan.BevelInner := bvNone; FPan.BevelOuter := bvNone; FPan.Visible := true; DefaultDrawing := false; OnDrawColumnCell := DoOwnDrawColumnCell; OnOwnDrawColumnCell := nil; curCell.X := -1; curCell.Y := -1; curCell.tag := 0; hr := 0; hc1 := 0; end; function TDBGridPro.CreateEditor: TInplaceEdit; begin result := inherited CreateEditor; hInPlaceEditorWndProc := result.WindowProc; result.WindowProc := InPlaceEditorWndProcHook; end; procedure TDBGridPro.DefaultDrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin {如果要畫(huà)焦點(diǎn),就讓DBGrid進(jìn)入編輯狀態(tài)} if (gdFocused in State) then begin EditorMode := true; AddBox; {如果是進(jìn)入一個(gè)新的Cell,全選其中的字符} if (curCell.X <> DataCol) or (curCell.Y <> DataSource.DataSet.RecNo) then begin curCell.X := DataCol; curCell.Y := DataSource.DataSet.RecNo; curCell.tag := 0; GetWindowRect(InPlaceEditor.Handle,curCell.r); SendMessage(InPlaceEditor.Handle,EM_SETSEL,0,1000); end; end else {正常顯示狀態(tài)的Cell} TCustomDBGrid(Self).DefaultDrawColumnCell(Rect,DataCol,Column,State); end; destructor TDBGridPro.Destroy; begin FPan.Free; inherited; end; procedure TDBGridPro.DoKeyUped(Sender: TObject; var Key: Word; Shift: TShiftState); var cl : TColumn; begin cl := Columns[SelectedIndex]; case Key of VK_RETURN: begin {一個(gè)Column為下拉類型,如果: 1 該Column的按鈕類型為自動(dòng)類型 2 該Column的PickList非空,或者其對(duì)應(yīng)的字段是lookup類型} if (cl.ButtonStyle=cbsAuto) and ((cl.PickList.Count>0) or (cl.Field.FieldKind=fkLookup)) and (curCell.tag = 0) and not (ssShift in Shift) then begin {把回車轉(zhuǎn)換成Alt+向下彈出下拉列表} Key := 0; Shift := [ ]; keybd_event(VK_MENU,0,0,0); keybd_event(VK_DOWN,0,0,0); keybd_event(VK_DOWN,0,KEYEVENTF_KEYUP,0); keybd_event(VK_MENU,0,KEYEVENTF_KEYUP,0); curCell.tag := 1; exit; end; {否則轉(zhuǎn)換成Tab} Key := 0; keybd_event(VK_TAB,0,0,0); keybd_event(VK_TAB,0,KEYEVENTF_KEYUP,0); end; VK_RIGHT : begin {獲得編輯框中的文字長(zhǎng)度} i := GetWindowTextLength(InPlaceEditor.Handle); {獲得編輯框中的光標(biāo)位置} GetCaretPos(p); p.x := p.X + p.Y shr 16; j := SendMessage(InPlaceEditor.Handle,EM_CHARFROMPOS,0,p.X); if (i=j) then {行末位置} begin Key := 0; keybd_event(VK_TAB,0,0,0); keybd_event(VK_TAB,0,KEYEVENTF_KEYUP,0); end; end; VK_LEFT: begin GetCaretPos(p); p.x := p.X + p.Y shr 16; if SendMessage(InPlaceEditor.Handle,EM_CHARFROMPOS,0,p.X)=0 then begin {行首位置} Key := 0; keybd_event(VK_SHIFT,0,0,0); keybd_event(VK_TAB,0,0,0); keybd_event(VK_TAB,0,KEYEVENTF_KEYUP,0); keybd_event(VK_SHIFT,0,KEYEVENTF_KEYUP,0); end; end; else begin {記錄用戶是否作了修改} if (Columns[SelectedIndex].PickList.Count>0) and (curCell.tag = 0) then if SendMessage(InPlaceEditor.Handle,EM_GETMODIFY,0,0)=1 then curCell.tag := 1; end; end; end; procedure TDBGridPro.DoOwnDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if FOwnDraw=false then DefaultDrawColumnCell(Rect,DataCol,Column,State); if @OnOwnDrawColumnCell<>nil then OnOwnDrawColumnCell(Sender,Rect,DataCol, Column,State); end; procedure TDBGridPro.InPlaceEditorWndProcHook(var msg: TMessage); var m : integer; begin m := msg.Msg; {=inherited} hInplaceEditorWndProc(msg); {如果是改變位置和大小,重新加框} if m=WM_WINDOWPOSCHANGED then AddBox; end; procedure TDBGridPro.KeyUp(var Key: Word; Shift: TShiftState); begin inherited; DoKeyUped(Self,Key,Shift); end; end. {以上代碼在Windows2000,Delphi6上測(cè)試通過(guò)} (出處:www.delphibbs.com) |
|