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.
Related
I have a form with a dbgrid and a second form with dbedits. My form with the dbgrid has a double click event(code below is the event) the dbedits on the second form has a data source that is connected to the first form's CDS and when I compile and run the program and open the form with db grid I can double click any record and it is display in the dbedits on the second form, but if I close the forms and reopen the form the only record that will display in the second form dbedits is the first record in the table. I have to open and close CDS and that is not working. what else would I need to do to correct this problem.
procedure TFRM_ADMIN.DBGrid1DblClick(Sender: TObject);
BEGIN
frm_LEADDETAILADMINLEAD := tfrm_LEADDETAILADMINLEAD.Create(FRM_ADMIN);
frm_LEADDETAILADMINLEAD.SHOW;
END;
The site will not allow me to add the dmf text. It is to large. I am using sqlconnection, sqlquery, data set provider, client data set, data source set up if this helps any.
This is a very wild quess, but I suspect the following at play:
You use the public variables for the forms that the IDE automatically has added to each unit.
You create FRM_ADMIN the way you create frm_LEADDETAILADMINLEAD, something like:
FRM_ADMIN := TFRM_ADMIN.Create(MainForm);
FRM_ADMIN.Show;
You don't close this form, but hide it (the default close action of a form).
The second time you create your second form, the designtime set DataSet property of the DataSource of your second form is automatically resolved to the ClientDataSet on the first instance of the first form.
So in the second run you are editing the record that was selected in the first run.
Solution and recommendations:
Destroy forms (set Action := caFree) in the OnClose event.
Do not use the public variables, remove them. Keep you form reference in a separate private field, unique to each instance.
Assign DataSource and/or DataSet properties at runtime explicitly.
Use DataModules.
Further reading:
Binding a second instance of a form to a second instance of a data module?
Will there be any detrimental effects if I accidentally remove something from a forms uses list which a control is referencing?
Separate DataSet instances using DataModules.
Show message when Tform2 is created?
Suppose I have a dataset say dsSample in my Delphi application. To read or write data in a dataset, one must open the dataset. I just wanted to know what is the difference between following statements:
dsSample.Open;
dsSample.Active := True;
and
dsSample.Close;
dsSample.Active := False;
If Open and Active perform same operation, why two different keywords for opening and closing the datasets in Delphi?
Use either as here is little difference, the DataSet.Open method has one line of code: Active := True. Active being a property that will call SetActive or GetActive. Finding out these things is fairly straight forward if you hold down CTRL and click on Open or Active and have a read of the code in the VCL source, knowing a few of the internals of the VCL will stop you doing things like:
if not DataSet.Active then
DataSet.Open;
rather than just
DataSet.Open;
As TLama indicated, Active is a published property making it available to the Delphi IDE, allowing you to toggle it at design time for your DataSets on Forms or Datamodules. Open and Close are probably not strictly required, but is a fairly common pattern across many languages.
TDataset.Open is a procedure so you can't get dataset is populated with data or not
procedure TDataSet.Open;
begin
Active := True;
end;
Active is a property :
Use Active to determine or set whether a dataset is populated with data. When Active is false, the dataset is closed; the dataset cannot read or write data and data-aware controls can not use it to fetch data or post edits. When Active is true, the dataset can be populated with data. It can read data from a database or other source (such as a provider). Depending on the CanModify property, active datasets can post changes.
Setting Active to true:
Generates a BeforeOpen event.
Sets the dataset state to dsBrowse.
Establishes a way to fetch data (typically by opening a cursor).
Generates an AfterOpen event.
If an error occurs while opening the dataset, dataset state is set to dsInactive, and any cursor is closed.
Setting Active to false:
Triggers a BeforeClose event.
Sets the State property to dsInactive.
Closes the cursor.
Triggers an AfterClose event.
An application must set Active to false before changing other properties that affect the status of a database or the controls that display data in an application.
Note: Calling the Open method sets Active to true; calling the Close method sets Active to false.
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.
I have a form with a query, a dataset, an editable dbgrid and an updatesql component. When I need to save the changes made in the dbgrid, I call this procedure:
procedure TEditCardDetailForm.SaveChanges;
begin
Database1.StartTransaction;
try
Query2.ApplyUpdates;
Database1.Commit;
except
Database1.Rollback;
raise;
end;
Query2.CommitUpdates;
end;
However I want the changes to be applied automatically to the database when I press Enter or go to another row after editing a cell in the dbgrid - the way it is done when I use a TTable component. Is there a way to do it?
If I understand it right (please correct me if not) you have a TQuery with CachedUpdates set to true, but want it to behave as if it would not be using cached but immediate updates. If that is the case the way you have set up your TQuery contradicts your desired behaviour. Cached updates are to be "held" on the client side until you decide to manually post them to the database by using ApplyUpdates.
In case you can set CachedUpdates to false, you only need to do following:
Link the TUpdateSQL to the TQuery via its UpdateObject property.
Write the insert, update and delete statements and assign them to the InsertSQL, ModifySQL and DeleteSQL properties of the TUpdateSQL.
I guess you already have done these two things, so putting CachedUpdates to false should do it.
You can find more information on Cached Updates for Delphi 5 here.
HTH
You have two scenarios to handle here:
save changes when changing the grid row
save changes when changing the grid column
The first one is easy to implement by calling your SaveChanges procedure in the AfterPost event of the underlying dataset (query2, a TClientDataSet?).
For the second one you only have to call query2.Post after the column has changed. This can be done in the OnDataChange event of the datasource. Make sure to check for Field <> nil and the dataset being in insert or edit mode before calling post.
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.