I want to write an app that uses a Access database (*.mdb). I know how to connect to a mdb and how to use SQL statements. My problem is that I want to put the result of a query into a TListView.
Any info/link/book is welcomed
:)
Pull your result and then pass it to the following procedure (for example):
Query.First;
While not Query.EOF do
begin
StrObj := tStringList.create;
StrObj.Add(Query.FieldByname('id').asString);
ListView.AddItem(Query.FieldByName('Title').AsString,StrObj);
Query.Next;
end;
This will load your list view with nodes named by the fieldname title, and each node will contain a tstringlist containing whatever data you want to store for each node. Personally I would extend this and use a custom holder object rather than the tStringList, but this was just an example.
TListView is not a data-aware component, and there isn't (AFAIK) a VCL TDBListView - if you need a grid of data, you can either bind to a TDBGrid, or use a 3rd part TDBListView, which you can find with a quick google.
Using a ListView to represent a disconnected set of data is my favorite design pattern with Delphi database applications. The ListView control offers several different display formats of which vsReport is the one that looks like a table of data with rows and columns (the others are vsList, vsIcon, and vsLargeIcon).
To create "items" (which map to rows, at least when vsReport is the display style) you call AddItem() on the ListView's Items collection. AddItem will return an object of type TListItem. You can assigne a Caption to the item which becomes the description (in vsList, vsIcon, and vsLargeIcon styles) and the first column of the "table" in vsReport. The list item also has a TStringList property called SubItems. Each string added to SubItems will provide data for another column in vsReport mode (the SubItems are ignored in the other modes).
Finally, if you want to associate an integer primary key value back to your original database record you can do this using the TListItem's Data member. This is a pointer to an object associated with the item, but you can cast an integer and store it away there.
So, for instance:
DS := TSomeKindOfDataSet.Create();
try
//Set up and open DS.
while not DS.eof do begin
with ListView.Items.Add() do begin
//establish three columns for vsReport display
Caption := DS.FieldByName('DescriptiveField').AsString;
SubItems.Add(DS.FieldByname('AnotherColumn').AsString);
SubItems.Add(DS.FieldByname('YetAnotherColumn').AsString);
//Save the record's PK value
Data := Pointer(DS.FieldByname('PKColumn').AsInteger);
end;
DS.Next;
end;
finally
DS.Free;
end;
This gives you a list view containing the data from DS, along with the primary key value so that you can locate any record the user selects in the list view. DS is closed and disposed of while the user is working with the list view, so there's no ongoing demand on the database.
You can either add each record to a TListView. Just looping through the records and put the contents of the fields into the required control.
But Delphi provides data aware controls. That take care of the database connection. For most applications this is enough.
There are some implementations of VirtualTreeView that work with databases.
Here's one link, and here is VirtualTreeView web site.
Related
Version info: Delphi 2010, Express Quantum GridSuite 6.56
I've inherited some code where the programmer has two grids in a master/detail (Customers->Devices) relationship, and he's made this work like so:
He links his master TCXGrid datasource property to the Customer data
source, which in turn links to the Customer table.
He links his detail TCXGrid DataSource to the Device data source,
which then links to the underlying Device Table.
To make the actual Master/Detail magic happen, he's hooked into the
Customer table's AfterScroll event and sets a filter on the Device
dataset based on a key in the current record of the Customer table.
The code:
procedure TdmData.tblCustomerAfterScroll(DataSet: TDataSet);
begin
if tblDevices.Active then
begin
tblDevices.filter := 'DCKEY=' +
inttostr(DataSet.FieldbyName('CKEY').AsInteger)
{+ ViewInactiveString}; //my addition
tblDevices.Filtered:=True;
end;
end;
This works. (That is to say when the user clicks on a customer the Customer grid, the Devices grid changes to display only that customer's devices.)
My task has been to add an additional (global) filter for inactive devices. So I created a checkbox on this master/detail page linking to a global settings table, linked to a field called view_inactive. I've added to the filter described in step 3.
function TdmData.ViewInactiveString: String;
begin
if not tblSettingsVIEW_INACTIVE.AsBoolean
then Result := ' AND ACTIVE <> FALSE'
else Result := '';
end;
This also works in the sense that when the user clicks on the Customer grid, the Device grid displays on that customer's devices which are not inactive. But the Device grid does not update UNTIL the user clicks on the Customer grid, and I want it to update right away.
I hooked into the checkbox OnClick to call the customer's AfterScroll method, but this doesn't update the detail grid. I notice that this seems to be because the bound view_inactive field is still returning the old state. I figure the events aren't firing in the order I expect, so I'll have to hook into the global settings table. I try using the following events:
The settings table AfterPost
The settings data source OnDataChange
The settings data source OnUpdateData
The settings data set AfterPost
In each case, whether the event is fired or not, the result is the same. The underlying view_inactive table has not yet been set. But if I click on the customer grid, it somehow is.
Where am I going wrong?
Looks to me like you are over complicating things. Ignore the global settings table. Put something like this in a new procedure and call it from the AfterScroll for tblCustomer, OnClick for the checkbox and when tblDevices is made active.
if tblDevices.Active then
begin
tblDevices.filter := 'DCKEY=' + inttostr(DataSet.FieldbyName('CKEY').AsInteger);
if not CheckboxViewInActive.checked then Devices.filter := theDevices.Filter +' AND ACTIVE <> FALSE';
tblDevices.Filtered:=True;
end;
If you want to load/save this setting in a global settings table do it separately.
So, I've been using LiveBindings between two tables at a combobox to get the Id and Description of a foreign key(with the Item.LookupData and Item.text properties) and assign the key to a field in another table with the SelectedValue propertie. This is working fine i guess but i'm using custom dbnavigator controls to make a "register form".
I'm using methods like this to make the inserts: adotablealuno.FieldValues['Nome']:=editnomeAluno.Text;
But I'cant find how to use the combobox in this way, i've already tried the ItemIndex and Selected properties, but none of this work (I'm using a Access DB btw). How can I use my foreign key in ComboBox for this?
Actually I was already stating this question and there was no good answer for a long time. I found a way out that I am currently using. At least I know that I'll get reliable data.
You need to handle OnFillingListItem event of the LinkFillControlToField link in alike way and store id number in ComboBox Items. I use Tag property for this purpose though it is not actually good.
procedure TForm1.LinkFillControlToField1FillingListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
begin
(AEditor.CurrentObject as TListBoxItem).Tag :=
YourLookuptable.FieldByName('id').AsInteger;
end;
And then fetch the item id from ListBox1.Selected.Tag. The text value can be accessed via ComboBox1.Selected.Text.
Appended.
You make an alike LinkFillControlToField link.
Then you select this link and create an OnFillingListItem event handler to the link (select events tab in the Object Inspector and double click on OnFillingListItem ComboBox). The event handler (empty procedure) will appear. It will be given a name like TForm1.LinkFillControlToField1FillingListItem(... Then you write the code setting the id property to the items' Tags.
I have a form which has a TDBLookupComboBox on it.
The TDBLookupComboBox is displaying a list of records from within a database table.
At the point of the forms OnShow event I would like the TDBLookupComboBox to already display one of the strings in the list.
I have done this so far...
procedure TfrmMain.FormShow(Sender: TObject);
begin
dblucbox.Text := Username;
end
Username is a string for one of the records already in the list.
At the point of compiling, I get an error saying
Cannot assign to a read only property
I'm a bit stuck with this so any help would be appreciated.
Don't try to modify the Text property, instead if you want set the TDbLookUpComboBox in a particular item you must use the the KeyValue property which will try to locate the record in the underlying TDataSet.
So you if you have Key Value of the user you can use something like this
dblucbox.KeyValue := UserId;
Otherwise you can use the Locate method of the underlying TDataSet, to find the match and the LookUp control will be refreshed automatically
You're going about this backwards. To be more precise, the DBLookupCombo is reflecting the state of the database table. So you want to be manipulating the table, not the combobox.
In other words, the OnShow event needs to open the table that's the object of the DBLookupCombo (if it's not already open) and then position the current record to be the one you want displayed as the default.
I have many "master/detail" forms in my application. A TDBGrid where each row shows a few core values of the item. Below the grid is usually a "Detail area" that shows the complete information of the item that is currently selected in the grid.
Currently I am listening to the "AfterScroll"-event of the TADOQuery behind the grid, but it seems to give me too many events.
Is AfterScroll the correct event for this? How are you doing this?
The "standard" way (in a data-aware environment) would be to not control that using GUI controls, but rather using the data components.
Most of the table data sets provide MasterSource (linked to an appropriate TDataSource component), and MasterFields properties.
You use these to link your datasets in a master-detail relationship.
Then your detail grid (or other data-aware controls) only needs to concern itself with linking to the correct dataset.
EDIT
Other kinds of datasets (e.g. TQuery, TADOQuery) sometimes provide DataSource to be used for a similar purpose. From Delphi 5 help: "Set DataSource to automatically fill parameters in a query with fields values from another dataset."
However, there are quite a few more complications (as will be observed reading the help). So it may be advisable to use TTable or TADOTable for the detail dataset instead.
I'm not aware if there's any 'standard' way but IMO AfterScroll is fine. Use a timer to prevent updating controls in quick succession, for instance while scrolling the grid. An example:
procedure TSomeForm.DataSetAfterScroll(DataSet: TDataSet);
begin
if not DataSet.ControlsDisabled then begin
if ScrollTimer.Enabled then
ScrollTimer.Enabled := False;
ScrollTimer.Enabled := True;
end;
end;
procedure TSomeForm.ScrollTimerTimer(Sender: TObject);
begin
ScrollTimer.Enabled := False;
UpdateGUI;
end;
I think you'll find an interval of 250-300 ms pleasant.
The TcxRadioGroup component of DevExpress has a very nice way to specify items. You can specify a Caption and a Value (and a Tag) for each TcxRadioGroupItem.
The TcxComboBox and the normal TComboBox of Delphi on the other hand use TStrings to store its items.
While TStrings can have a Name and an Object, there is no easy way to hook up a name and a value using the form designer of the Delphi IDE.
Is there a ComboBox control (preferably from DevExpress) that allows to visually design its items with a Caption and a Value?
PS: I'm not looking for a DB aware control.
Try a TcxImageComboBox. See here - you don't have to assign images despite the name. You can also edit the items visually.
(I use it as cell editor in cxGrids because of the separation Description/Value.)
Raize Components have TRzComboBox which introduces a Values property as an addition to the existing Items.
ESBPCS for VCL has an enhanced Lookup ComboBox. It stores 2 Lists, the ones normally in TCombobox's Items as well as the new Values list. These two StringLists are in a 1-1 relationship. Use AsItem to retrieve the string currently displayed and AsValue to retrieve the "related" string from Values.
Use a standard Delphi TComboBox, it can store a string (for visualization, and an object of any TObject descendant that you implement yourself, i.e you can store anything associated to a string in the dropdown).