Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
Using Multi-Selected Rows in a VCL TDBGrid Control
And how to migrate that code to VCL for .NET
The TDBGrid is one of the more powerful data-aware VCL controls, and it contains many features - sometimes more than all Delphi developers are aware of.
Did you know, for example, that you could use the TDBGrid to select multiple rows? If so, did you know how to programmatically get access to the selected rows? (and in case you still answered yes, do you also know how to compile that code in a VCL for .NET application?).
In this article, I'll cover the multiple row selection feature of the TDBGrid component, showing you how we can get access to the selected rows (for example to delete the selected rows from the TDataSet - which is easy - or to copy one or more fields from the selected rows to another control, like a TListBox - which is a bit less easy).
I will also show how to migrate the code to a managed VCL for .NET project (which is what kept me bust for a while, when migrating a VCL project over from Win32 to .NET).
TDBGrid
OK, first things first: how do we enable the TDBGrid to select multiple rows? For that, I've used Delphi 2005 to build a little demo VCL for Win32 application with a TTable (pointing to the DBDemos alias and the Paradox Biolife.db table), one TDataSource and one TDBGrid component.
We have to tweak the TDBGrid Options property, and set the dgRowSelect as well as the dgMultiSelect option to True, and the dgEditing option to False (note that the dgEditing flag will be set to False automatically once you've set the dgRowSelect to True).
According to the Delphi help, the combination of dgMultiSelect and dgRowSelect will enable the use of the SelectedRows property of the TDBGrid component.
With the SelectedRows property, we can determine how many rows have been selected in the TDBGrid, using the SelectedRows.Count property.
We can also determine if the current record in the TDBGrid has been selected, using the SelectedRows.CurrentRowSelected property.
In code this can be done simply as follows:
procedure TForm1.btnCountClick(Sender: TObject); begin if DBGrid1.SelectedRows.CurrentRowSelected then ShowMessage(IntToStr(DBGrid1.SelectedRows.Count) + ' rows selected, including current row.') else ShowMessage(IntToStr(DBGrid1.SelectedRows.Count) + ' rows selected (but not the current row).') end;Which results in the following message in my example application:
Instead of counting the selected rows and determining if the current row is selected, we can also delete all selected rows with a single statement:, as follows:
procedure TForm1.btnDeleteClick(Sender: TObject); begin DBGrid1.SelectedRows.Delete end;Note that this method will not ask for confirmation, and will actually delete multiple records from the TDataSet that is connected to the TDBGrid. Very powerful, but also a bit dangerous perhaps.
VCL for Win32
When in doubt, use the source.
Or better: check the on-line helpfiles that were included with Delphi 7.
Unlike the Delphi 2005 helpfile, the Delphi 7 helpfile includes a little example that explains that we can cast a TBookmarkStr item to a pointer, and use the resulting pointer as input for the GotoBookmark method of the Table, as follows:
Table1.GotoBookmark(pointer(DBGrid1.SelectedRows[i]));This construct can be used to walk through the list of TBookmarkStr items in the SelectedRows property, and call the GotoBookmark method of the TDataSet, after which we can copy one or more field values from the TDataSet to a TListBox. Using the Biolife example, I want to copy the value of the Common_Name field of all selected records, which can be coded as follows now:
procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin ListBox1.Items.Clear; if DBGrid1.SelectedRows.Count > 0 then begin for i:=0 to DBGrid1.SelectedRows.Count-1 do begin Table1.GotoBookmark(pointer(DBGrid1.SelectedRows[i])); ListBox1.Items.Add(Table1Common_Name.AsString) end end end;The result can be seen below:
The real question I had left after this, is how well this pointer cast would migrate to the .NET world...
VCL for .NET
In the past few months, I've been migrating a number of VCL applications from Win32 to .NET (some as exercises, and some as real-world projects when the client had decided to strategically move all Win32 development to .NET, using VCL as a basis for conversion).
Unfortunately, the project we have to far doesn't compile to a VCL for .NET target, since the DBGrid1.SelectedRows[i] of type TBookmarkStr cannot be cast to a pointer.
Looking into the VCL source code, I see that a bookmark is actually an IntPtr, but that cast doesn't help either.
Further examination shows, however, that each VCL TDataSet has a bookmark which is of type TBookmarkStr as well.
Hmm... instead of calling the GotoBookmark method, wouldn't it be possible to simply assign one of the SelectedRows items to the Bookmark property of the TDataSet? It turns out that this is indeed the case, so the VCL for .NET solution is implemented as follows
procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin ListBox1.Items.Clear; if DBGrid1.SelectedRows.Count > 0 then begin for i:=0 to DBGrid1.SelectedRows.Count-1 do begin {$IFDEF WIN32} Table1.GotoBookmark(pointer(DBGrid1.SelectedRows[i])); {$ELSE} Table1.Bookmark := DBGrid1.SelectedRows[i]; // moves!! {$ENDIF} ListBox1.Items.Add(Table1Common_Name.AsString); end end end;Checking back in the Win32 environment even learned that the direct assignment to the Bookmark property also works in Win32 - even when compiled with Delphi 7. Make me wonder why the Delphi 7 helpfile showed a hard cast from the DBGrid.SelectedRows to a pointer, instead of assigning the DBGrid.SelectedRows directly to the Bookmark property of the TDataSet. Oh well, at least it works now, both in VCL for Win32 and for .NET, so I can continue with the final code as follows:
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
ListBox1.Items.Clear;
if DBGrid1.SelectedRows.Count > 0 then
begin
for i:=0 to DBGrid1.SelectedRows.Count-1 do
begin
Table1.Bookmark := DBGrid1.SelectedRows[i]; // moves!!
ListBox1.Items.Add(Table1Common_Name.AsString)
end
end
end;
It still doesn't answer the question how we can find out which records are selected based on a given record (one of the features the helpfile also lists for the SelectedRows property), other than walking through the selected records one by one.
Summary
I guess apart from the fact that we can assign DBGrid.SelectedRows items directly to a TDataSet's Bookmark property, I've also learned that the Delphi 2005 helpfile is sometimes less complete than the Delphi 7 helpfile, but also that you cannot always trust the Delphi helpfile to show the most optimal solution (unless there's some explicit reason why the hard cast is better than my direct assignment, but my converted applications have worked just fine for weeks now, and I haven't encountered any bad side-effects).