Articles   Members Online: 3
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How to put components into a cell on a TDBGrid Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
28-Aug-02
Category
Database-VCL
Language
Delphi 2.x
Views
209
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: Tomas Rutkauskas

How to put components into a cell on a TDBGrid

Answer:

This article and the accompanying code shows how to put just about any component 
into a cell on a grid. By component I mean anything from a simple combobox to a 
more complicated dialog box. The techniques described below to anything that is 
termed a visual component. If you can put it into a form you can probably put it 
into a grid.

There are no new ideas here, in fact, the basic technique simply mimics what the 
DBGrid does internally. The idea is to float a control over the grid. Inside DBGrid 
is a TDBEdit that moves around the grid. It's that TDBEdit that you key you data 
into. The rest of the unfocused cells are really just pictures. What you will learn 
here, is how to float any type of visual control/component around the grid.

1. TDBLookupComboBox:

You need a form with a DBGrid in it. So start an new project and drop a DBGrid into 
the main form.

Next drop in a TTable and set it's Alias to DBDEMOS, TableName to GRIDDATA.DB and 
set the Active property to True. Drop in a DataSource and set it's DataSet property 
to point to Table1. Go back to the grid and point it's DataSource property to 
DataSource1. The data from GRIDDATA.DB should appear in your grid.

The first control we are going to put into the grid is a TDBLookupComboBox so we 
need a second table for the lookup. Drop a second TTable into the form. Set it's 
Alias also to DBDEMOS, TableName to CUSTOMER.DB and Active to True. Drop in a 
second data source and set its DataSet to Table2.

Now go get a TDBLookupComboBox from the Data Controls pallet and drop it any where 
on the form, it doesn't matter where since it will usually be invisible or floating 
over the grid. Set the LookupComboBox's properties as follows.

DataSource: DataSource1
DataField: CustNo
LookupSource: DataSource2
LookupField: CustNo
LookupDisplay: CustNo  {you can change it to Company later but keep it custno for 
now)

So far it's been nothing but boring point and click. Now let's do some coding.


The first thing you need to do is make sure that DBLookupComboBox you put into the 
form is invisible when you run the app. So select Form1 into Object Inspector goto 
the Events tab and double click on the onCreate event. You should now have the 
shell for the onCreate event displayed on your screen.

1   procedure TForm1.FormCreate(Sender: TObject);
2   begin
3   
4   end;


Set the LookupComboBox's visible property to False as follows.

5   procedure TForm1.FormCreate(Sender: TObject);
6   begin
7     DBLookupCombo1.Visible := False;
8   end;


Those of you who are paying attention are probably asking why I didn't just set 
this in the Object Inspector for the component. Actually, you could have. 
Personally, I like to initialize properties that change at run time in the code. I 
set static properties that don't change as the program runs in the object 
inspector. I think it makes the code easier to read.

Now we to be able to move this control around the grid. Specifically we want it to 
automatically appear as you either cursor or click into the column labeled 
DBLookupCombo. This involves defining two events for the grid, OnDrawDataCell and 
OnColExit. First lets do OnDrawDataCell. Double click on the grid's OnDrawDataCell 
event in the Object Inspector and fill in the code as follows.
9   
10  procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
11    Field: TField; State: TGridDrawState);
12  begin
13    if (gdFocused in State) then
14    begin
15      if (Field.FieldName = DBLookupCombo1.DataField) then
16      begin
17        DBLookupCombo1.Left := Rect.Left + DBGrid1.Left;
18        DBLookupCombo1.Top := Rect.Top + DBGrid1.top;
19        DBLookupCombo1.Width := Rect.Right - Rect.Left;
20        { DBLookupCombo1.Height := Rect.Bottom - Rect.Top; }
21        DBLookupCombo1.Visible := True;
22      end;
23    end;
24  end;


The reasons for the excessive use begin/end will become clear later in the demo. 
The code is saying that if the State parameter is gdFocused then this particular 
cell is the one highlighted in the grid. Further if it's the highlighted cell and 
the cell has the same field name as the lookup combo's datafield then we need to 
move the LookupCombo over that cell and make it visible. Notice that the position 
is determined relative to the form not to just the grid. So, for example, the left 
side of LookupCombo needs to be the offset of the grid ( DBGrid1.Left) into the 
form plus the offset of the cell into the grid (Rect.Left).

Also notice that the Height of the LookupCombo has been commented out above. The 
reason is that the LookupCombo has a minimum height. You just can't make it any 
smaller. That minimum height is larger than the height of the cell. If you 
un-commented the height line above. Your code would change it and then Delphi would 
immediately change it right back. It causes an annoying screen flash so don't fight 
it. Let the LookupCombo be a little larger than the cell. It looks a little funny 
but it works.

Now just for fun run the program. Correct all you missing semi-colons etc. Once its 
running try moving the cursor around the grid. Pretty cool, hu? Not! We're only 
part of the way there. We need to hide the LookupCombo when we leave the column. So 
define the grid's onColExit. It should look like this:

25  procedure TForm1.DBGrid1ColExit(Sender: TObject);
26  begin
27    if DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField then
28      DBLookupCombo1.Visible := false;
29  end;


This uses the TDBGrids SelectedField property to match up the FieldName associated 
with the cell with that of the LookupCombo. The code says, "If the cell you are 
leaving was in the DBLookupCombo column then make it invisible". Now run it again. 
Was that worth the effort or what?

Now things look right but we're still missing one thing. Try typing a new customer 
number into one of the LookupCombo. The problem is that the keystrokes are going to 
the grid, not to the LookupCombo. To fix this we need to define a onKeyPress event 
for the grid. It goes like this:
30  
31  procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
32  begin
33    if (key <> chr(9)) then
34    begin
35      if (DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField) then
36      begin
37        DBLookupCombo1.SetFocus;
38        SendMessage(DBLookupCombo1.Handle, WM_Char, word(Key), 0);
39      end;
40    end;
41  end;


This code is saying that if the key pressed is not a tab key (Chr(9)) and the 
current field in the grid is the LookupCombo then set the focus to the LookupCombo 
and then pass the keystroke over to the LookupCombo. OK so I had to use a WIN API 
function. You don't really need to know how it works just that it works.

But let me explain a bit anyway. To make Window's SendMessage function work you 
must give it the handle of the component you want to send the message to. Use the 
component's Handle property. Next it wants to know what the message is. In this 
case it's Window's message WM_CHAR which says I'm sending the LookupCombo a 
character. Finally, you need to tell it which character, so word(Key). That's a 
typecast to type word of the events Key parameter. Clear as mud, right? All you 
really need to know is to replace the DBLookupCombo1 in the call to the name of the 
component your putting into the grid. If you want more info on SendMessage do a 
search in Delphi's on-line help.

Now run it again and try typing. It works! Play with it a bit and see how the tab 
key gets you out of "edit mode" back into "move the cell cursor around mode".

Now go back to the Object Inspector for the DBLookupCombo component and change the 
LookupDIsplay property to Company. Run it. Imagine the possibilities.


2. TDBComboBox:

I'm not going to discuss installing the second component, a DBComboBox, because I 
don't really have anything new to say. It's really the same as #1. Here's the 
incrementally developed code for your review.

42  procedure TForm1.FormCreate(Sender: TObject);
43  begin
44    DBLookupCombo1.Visible := False;
45    DBComboBox1.Visible := False;
46  end;
47  
48  procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
49    Field: TField; State: TGridDrawState);
50  begin
51    if (gdFocused in State) then
52    begin
53      if (Field.FieldName = DBLookupCombo1.DataField) then
54      begin
55        DBLookupCombo1.Left := Rect.Left + DBGrid1.Left;
56        DBLookupCombo1.Top := Rect.Top + DBGrid1.top;
57        DBLookupCombo1.Width := Rect.Right - Rect.Left;
58        DBLookupCombo1.Visible := True;
59      end
60      else if (Field.FieldName = DBComboBox1.DataField) then
61      begin
62        DBComboBox1.Left := Rect.Left + DBGrid1.Left;
63        DBComboBox1.Top := Rect.Top + DBGrid1.top;
64        DBComboBox1.Width := Rect.Right - Rect.Left;
65        DBComboBox1.Visible := True;
66      end;
67    end;
68  end;
69  
70  procedure TForm1.DBGrid1ColExit(Sender: TObject);
71  begin
72    if DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField then
73      DBLookupCombo1.Visible := false
74    else if DBGrid1.SelectedField.FieldName = DBComboBox1.DataField then
75      DBComboBox1.Visible := false;
76  end;
77  
78  procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
79  begin
80    if (key <> chr(9)) then
81    begin
82      if (DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField) then
83      begin
84        DBLookupCombo1.SetFocus;
85        SendMessage(DBLookupCombo1.Handle, WM_Char, word(Key), 0);
86      end
87      else if (DBGrid1.SelectedField.FieldName = DBComboBox1.DataField) then
88      begin
89        DBComboBox1.SetFocus;
90        SendMessage(DBComboBox1.Handle, WM_Char, word(Key), 0);
91      end;
92    end;
93  end;



3. TDBCheckBox:

The DBCheckBox gets even more interesting. In this case it seems appropriate to 
leave something in the non-focused checkbox cells to indicate that there's a check 
box there. You can either draw the "stay behind" image of the checkbox or you can 
blast in a picture of the checkbox. I chose to do the latter. I created two BMP 
files one that's a picture of the box checked (TRUE.BMP) and one that's a picture 
of the box unchecked (FALSE.BMP). Put two TImage components on the form called 
ImageTrue and ImageFalse and attach the BMP files to there respective Picture 
properties. Oh yes you also need to put a DBCheckbox component on the form. Wire it 
to the CheckBox field in DataSource1 and set the Color property to clWindow. First 
edit the onCreate so it reads as follows:

94  procedure TForm1.FormCreate(Sender: TObject);
95  begin
96    DBLookupCombo1.Visible := False;
97    DBCheckBox1.Visible := False;
98    DBComboBox1.Visible := False;
99    ImageTrue.Visible := False;
100   ImageFalse.Visible := False;
101 end;
102 
103 {Now we need to modify the onDrawDataCell to do something with cells that do not 
104 have the focus. Here comes the code.}
105 
106 procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
107   Field: TField; State: TGridDrawState);
108 begin
109   if (gdFocused in State) then
110   begin
111     if (Field.FieldName = DBLookupCombo1.DataField) then
112     begin
113       {... see above}
114     end
115     else if (Field.FieldName = DBCheckBox1.DataField) then
116     begin
117       DBCheckBox1.Left := Rect.Left + DBGrid1.Left + 1;
118       DBCheckBox1.Top := Rect.Top + DBGrid1.top + 1;
119       DBCheckBox1.Width := Rect.Right - Rect.Left { - 1 };
120       DBCheckBox1.Height := Rect.Bottom - Rect.Top { - 1 };
121       DBCheckBox1.Visible := True;
122     end
123     else if (Field.FieldName = DBComboBox1.DataField) then
124     begin
125       {... see above}
126     end;
127   end
128   else {in this else area draw any stay-behind bitmaps}
129   begin
130     if (Field.FieldName = DBCheckBox1.DataField) then
131     begin
132       if TableGridDataCheckBox.AsBoolean then
133         DBGrid1.Canvas.Draw(Rect.Left, Rect.Top, ImageTrue.Picture.Bitmap)
134       else
135         DBGrid1.Canvas.Draw(Rect.Left, Rect.Top, ImageFalse.Picture.Bitmap)
136     end;
137   end;
138 end;
139 
140 {It's the very last part we're most interested in. If the state is not gdFocused 
141 and the column in CheckBox then this last bit executes. All it does is check the 
142 value of the data in the field and if it's true it shows the TRUE.BMP otherwise it 
143 shows the FALSE.BMP. I created the bit maps so they are indented so you can tell 
144 the difference between a focused and unfocused cell. Make onColExit look like this:}
145 
146 procedure TForm1.DBGrid1ColExit(Sender: TObject);
147 begin
148   if DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField then
149     DBLookupCombo1.Visible := false
150   else if DBGrid1.SelectedField.FieldName = DBCheckBox1.DataField then
151     DBCheckBox1.Visible := false
152   else if DBGrid1.SelectedField.FieldName = DBComboBox1.DataField then
153     DBComboBox1.Visible := false;
154 end;
155 
156 //Edit onKeyPress to:
157 
158 procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
159 begin
160   if (key <> chr(9)) then
161   begin
162     if (DBGrid1.SelectedField.FieldName = DBLookupCombo1.DataField) then
163     begin
164       DBLookupCombo1.SetFocus;
165       SendMessage(DBLookupCombo1.Handle, WM_Char, word(Key), 0);
166     end
167     else if (DBGrid1.SelectedField.FieldName = DBCheckBox1.DataField) then
168     begin
169       DBCheckBox1.SetFocus;
170       SendMessage(DBCheckBox1.Handle, WM_Char, word(Key), 0);
171     end
172     else if (DBGrid1.SelectedField.FieldName = DBComboBox1.DataField) then
173     begin
174       DBComboBox1.SetFocus;
175       SendMessage(DBComboBox1.Handle, WM_Char, word(Key), 0);
176     end;
177   end;
178 end;


Finally, here's the last trick. The caption of the checkbox needs to change as the 
user checks or unchecks the box. My first thought was to do this in the 
TDBCheckBox's onChange event, the only problem is that it doesn't have one. So I 
had to go back to the Windows API and send another message. 
"SendMessage(DBCheckBox1.Handle, BM_GetCheck, 0, 0)" which returns a 0 if the box 
is unchecked, otherwise it's checked.

179 procedure TForm1.DBCheckBox1Click(Sender: TObject);
180 begin
181   if SendMessage(DBCheckBox1.Handle, BM_GetCheck, 0, 0) = 0 then
182     DBCheckBox1.Caption := '  ' + 'False'
183   else
184     DBCheckBox1.Caption := '  ' + 'True'
185 end;


That's it. Hopefully you learned something. I've tried this technique with dialog 
boxes. It works and it's simple. Have fun with it. You don't really need to 
completely understand it as long as you know how to edit the code and replace the 
above component names with with the name of the component you want to drop into the 
grid.


4. Enhancements and error correction:

There are 2 stichy points about the Original grid demo. First, once a component in 
the grid has the focus it takes 2 Tab presses to move to the next grid cell. The 
other has to do with adding new records.

Problem one - Two Tab Presses Required

A component installed in the grid is actually floating over the top of the grid and 
not part of the grid it self. So when that component has the focus it takes two tab 
presses to move to the next cell. The first tab moves from the floating component 
to the Grid cell underneath and the second to move to the next grid cell. If this 
behavior bugs you heres how to fix it.

First in the form that contains grid add private variable called WasInFloater of 
type boolean, like so.

186 type
187   TForm1 = class(TForm)
188     {...}
189   private
190     { Private declarations }
191     WasInFloater: Boolean;
192     {...}
193   end;


Next create an onEnter event for the LookupCombo where WasInFloater is set to true. 
Then point the onEnter event for each component that goes into the grid at this 
same single onEnter event.

procedure TForm1.DBLookupCombo1Enter(Sender: TObject);
begin
  WasInFloater := True;
end;

Finally, and here's the tricky part, define the following onKeyUp event for the 
grid.
194 
195 procedure TForm1.DBGrid1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
196 begin
197   if (Key in [VK_TAB]) and WasInFloater then
198   begin
199     SendMessage(DBGrid1.Handle, WM_KeyDown, Key, 0);
200     WasInFloater := False;
201   end;
202 end;


What's happening here is that the grid's onKeyUp is sending it self a KeyDown when 
the focus just switched from one of the floating controls. This solution handles 
both tab and shift-tab.

Problem two - New record disappears when component gets focus

The second problem is that if you press add record on the navigator in the demo a new record is added but then when you click on one of the components installed in the grid the new record disappears. The reason for this is that there is a strange grid option called dgCancelOnExit which is True by default. Set it to False and the above problem goes away.

			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC