Delphi DBGrid showing compressed rows - delphi

I am having the strangest of issues with Delphi's DBGrid.
I noticed that Sometimes, and I mean only sometimes (It is completely random) when I load rows into a delphi DBGrid, the grid does not show the data.
It instead shows a couple of compressed rows, basically the delphi rows are so narrow in height that the information cannot even be read.
What would be the cause of this? And how can one fix it?
Update
I have finally been able to catch the rows do it myself to get an image.
As you can see, the rows are technically showing as 1 is selected. But it is asif they are being compressed very close together so that it apears to be empty...
Please see the image below:
ANY IDEAS would be awesome as to what is causing this, and how to prevent it...

This problem occured to me, too. And I think I have solved.
In my situation I was calling ADOQuery.Open(); inside TThread, and this ADOQuery was bound to DataSource and it was bound to DBGrid. I suspected there may be something with execution in a secondary thread, so I played a little with ADOQuery.
Here's what I did that solved my problem. Before calling ADOQuery.Open() and before starting a new thread, I did DataSource.DataSet := nil;. I assign Thread.OnTerminate := RefreshGridFinished;. Then I start that new TThread with some procedure in which ADOQuery.Open(); eventually is called. Then, when TThread finishes, I have this handler, which will assign fetched and full ADOQuery aka DataSet to DataSource:
procedure TMyForm.RefreshGridFinished(Sender: TObject);
begin
TThread.Synchronize(TThread(Sender),
procedure
begin
DataSource.DataSet := ADOQuery; // I assign fetched dataset
end);
if TThread(Sender).FatalException <> nil then
begin
Exit;
end;
Thread := nil; // Class field
end;

Related

Why fires the AfterScroll event of a nested (detail) dataset more than once when its master dataset scrolled to another record? How to deal with it?

I have a Delphi program with 3 successive nested (master-details) FireDAC datasets namely: SuperMaster, Master and Details dataset.
In the AfterScroll event of the Details dataset only, new records are inserted into the database.
My program logic needs the AfterScroll event of the Details dataset to fire only once: when any of the parent datasets (SuperMaster or Master) scrolled to another record. The AfterScroll event should not trigger more than once because this will cause my logic to insert wrong records.
The problem is that when scrolling occurs to any of the parent datasets (from the current record to another one, even to the adjacent one), i.e. moving a single record only, the Details dataset AfterScroll event fired more than once and sometimes more than twice!
I do not want to mess with the original FireDac classes by overriding its AfterScroll event to force it to do my logic only once, nor to add a Boolean flag to my logic so that it runs only once.
I tried to put all the tables into one view, but it costs me to change the program logic and make future updates more demanding.
I searched and read many articles and a book ("Delphi in Depth: FIREDAC" by Cary Jensen), but could not find a solution nor know why or how it occurs!
Why does this happen? I want to understand the basics please. Is there any elegant solution?
Short of changing the FireDAC source (which I think would be a thoroughly bad idea - beware of opening Pandora's box) I don't think your goal of avoiding the
use of a Boolean flag is achievable. It should however be trivial to achieve if you do use a Boolean flag.
I also wonder whether you observation that the Detail's AfterScroll event occurs more than once per detail dataset
is correct, if that is what you are saying. If it is, you should edit your q to include a minimal, reproducible example, preferably based on the code below.
TDataSet and its descendants, including all the FireDAC datasets operate to a very tightly designed
state machine which includes the handling of master-detail behaviour that's been tested by all the TDataSet
usage since Delphi was first released. Trying to mess with that would invite
disaster.
If you try the minimal VCL project below, I think you'll find it's not very hard to satisfy yourself that
the Detail's AfterScroll event behaves exactly as you would expect from the coding of TDataSet's and FireDAC's source.
Running the code you will find that the breakpoint in DetailAfterScroll trips 4 times before
the first breakpoint on MasterFirst. The next time the BP trips, observe the call stack via
View | Debug Windows | Call stack; if you look down the cal stack, you'll find that the call to Detail.AfterScroll
was ultimately called via the penultimate line of
procedure TFDMasterDataLink.DataEvent(Event: TDataEvent; Info: NativeInt);
An FDMasterDataLink is automatically created to handle the triggering of Detail data events based on
the operation of the Master. And that, really, is the end of the story. because however much you might
disagree with this behaviour , you can't really do anything about it short of using a Boolean flag
in your own code.
I think it would be wise to verify that DetailAfterScroll is only being called once per dataset that's
on the detail side of the Master. If it's happening more than once, it would be worth checking that
it isn't your own code (or linking together of DataSets) that's causing it.
As you'll see, nearly all of what the example does is defined in the OnCreate event
to avoid having to use the Object Inspector to set up the components, so that is should "just work".
Code:
type
TForm1 = class(TForm)
Master: TFDMemTable;
Detail: TFDMemTable;
DataSource1: TDataSource;
procedure FormCreate(Sender: TObject);
procedure DetailAfterScroll(DataSet: TDataSet);
public
DetailsScrolled : Integer;
end;
[...]
procedure TForm1.DetailAfterScroll(DataSet: TDataSet);
begin
// Place debugger breakpoint on the following line
Inc(DetailsScrolled);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
AField : TField;
begin
AField := TIntegerField.Create(Self);
AField.FieldName := 'MasterID';
AField.DataSet := Master;
Master.CreateDataSet;
AField := TIntegerField.Create(Self);
AField.FieldName := 'DetailID';
AField.DataSet := Detail;
AField := TIntegerField.Create(Self);
AField.FieldName := 'MasterID';
AField.DataSet := Detail;
Detail.CreateDataSet;
DataSource1.DataSet := Master;
Detail.MasterSource := DataSource1;
Detail.MasterFields := 'MasterID';
Detail.IndexFieldNames := 'MasterID;DetailID';
Master.InsertRecord([1]);
Master.InsertRecord([2]);
Detail.InsertRecord([1, 1]);
Detail.InsertRecord([2, 1]);
Detail.InsertRecord([3, 2]);
// Place debugger breakpoint on EACH of the following three lines
Master.First;
Master.Next;
Master.First;
end;

Odd behaviour when adding a toolbutton to the delphi ide

I was trying out some things and wanted to make a delphi IDE extension.
My basic idea was expanding the ToDo list feature that is currently in the IDE.
Step one was adding a toolbutton to the IDE which would open a form showing the todo items.
But I noticed some weird things that I hopefully caused myself since that would mean it can be easily fixed.
I am adding my toolbutton to the CustomToolbar, which is the one with the blue questionmark (see screenshot later)
The thing that happens: I install my package and the button is added with the correct image, right next to the existing button.
Now I close the modal form with the installed packages and then the blue questionmark changes.
Don't mind the icon I used, I will use a different one eventually but ok.
So basicly the existing item changes to my own icon but disabled for some reason. And I can't figure out why this happens.
As suggested in the guide I found online I used a TDatamodule to implement my code.
My code:
procedure TDatamoduleToDoList.Initialize;
var
LResource, LhInst: Cardinal;
begin
LhInst := FindClassHInstance(Self.ClassType);
if LhInst > 0 then
begin
LResource := FindResource(LhInst, 'icon', RT_Bitmap);
if LResource > 0 then
begin
FBMP := Vcl.Graphics.TBitmap.Create;
FBMP.LoadFromResourceName(LhInst, 'icon');
end
else
DoRaise('Resource not found');
end
else
DoRaise('HInstance Couldn''t be found');
FToDoAction := TTodoAction.Create(Self);
FToDoAction.Category := actionCat;
FToDoAction.ImageIndex := FIntaServices.ImageList.Add(FBMP, nil);
FToDoAction.Name := 'my_very_own_action_man';
end;
procedure TDatamoduleToDoList.DataModuleCreate(Sender: TObject);
begin
//Create extension
if Supports(BorlandIDEServices, INTAServices, FIntaServices) then
begin
Initialize;
if FToDoAction <> nil then
FCustBut := TSpeedButton(FIntaServices.AddToolButton(sCustomToolBar, 'CstmToDoList', FToDoAction))
else
DoRaise('Initialize failed');
end
else
DoRaise('Something went wrong');
end;
DoRaise is my own procedure that simply destroys all of my objects and raises an exception, did this to prevent mem leaks in the ide.
But, I think, I don't do anything weird but yet this problem occurs.
So I'm hoping someone here might have done something simular and sees the error in my code.
Thanks in advance.
P.s. if you need any more info or see the rest of the unit let me know and ill put the entire unit on github or something like that.
Edit:
Thanks to #Uwe Raabe I managed to solve this problem.
The problem was found in the comments of INTAServices.AddImages
AddImages takes all the images from the given image list and adds them
to the
main application imagelist. It also creates an internal mapping array from the
original image indices to the new indices in the main imagelist. This
mapping is used by AddActionMenu to remap the ImageIndex property of the
action object to the new ImageIndex. This should be the first method
called when adding actions and menu items to the main application window.
The return value is the first index in the main application image list of
the first image in the source list. Call this function with an nil
image list to clear the internal mapping array. Unlike the AddImages function from
the ancestor interface, this version takes an Ident that allows the same base index
to be re-used. This is useful when the IDE implements demand-loading of
personalities so that the images will only get registered once and the same image
indices can be used.
The solution eventually was adding my image to a local imagelist which was added to the imagelist of IntaServices
Code:
procedure TDatamoduleToDoList.DataModuleCreate(Sender: TObject);
begin
//Create extension
if Supports(BorlandIDEServices, INTAServices, FIntaServices) then
begin
Initialize;
if FToDoAction <> nil then
begin
FCustBut := TSpeedButton(FIntaServices.AddToolButton(sCustomToolBar, 'CstmToDoList', FToDoAction));
FToDoAction.ImageIndex := FIntaServices.AddImages(FImages);//This is the fix
end
else
DoRaise('Initialize failed');
end
else
DoRaise('Something went wrong');
end;
You are not supposed to fiddle around with the INTAServices.ImageList directly. Instead use either INTAServices.AddMasked or INTAServices.AddImages (in case you have a local imagelist in your datamodule).
You can safely use the INTAServices.ImageList to be connected to your controls, but you should neither Add nor Delete the images in it directly.

Argument Out of Range issues using FMX TListBox

I'm using a TListBox in Firemonkey, and I'm facing a strange issue when it comes to dynamically showing/hiding items. This includes both Delphi XE7 and XE8. The setup, there is a TPopupBox at the top of the form, where user chooses one of the items listed. Depending on which was chosen, the TListBox should show only certain TListBoxItems, and hide the rest. Part of this consists of resizing each list item height to 0 when not visible (otherwise it would leave an ugly gap between the items).
The problem is that very randomly and spontaneously (no pattern), selecting an item in this TPopupBox (calling OnChange which modifies visibility), produces an EArgumentOutOfRangeException at an unknown point. The code breaks in System.Generics.Collections.TListHelper.SetItemN() on the first line calling CheckItemRangeInline(AIndex); Within there, it's simply:
procedure TListHelper.CheckItemRangeInline(AIndex: Integer);
begin
if (AIndex < 0) or (AIndex >= FCount) then
raise EArgumentOutOfRangeException.CreateRes(#SArgumentOutOfRange);
end;
The exception continues to be raised over and over and over again with no end (starts with 4 in a row). When I use the debugger to step in, I can never manage to get it to happen.
There are a couple common procedures used here which control item visibility:
//lstTrans = TListBox
//Iterates through all items and hides everything
procedure TfrmMain.HideTransItems;
var
X: Integer;
begin
for X := 0 to lstTrans.Count-1 do begin
lstTrans.ListItems[X].Visible:= False;
end;
end;
//Sets height of visible items to 42, invisible items to 0
procedure TfrmMain.ResetTransHeights;
var
X: Integer;
LI: TListBoxItem;
begin
for X := 0 to lstTrans.Count-1 do begin
LI:= lstTrans.ListItems[X];
if LI.Visible then
LI.Height:= 42
else
LI.Height:= 0;
end;
end;
Then, when choosing something in the TPopupBox:
//cboTrans = TPopupBox
procedure TfrmMain.cboTransChange(Sender: TObject);
procedure E(AItem: TListBoxItem);
begin
AItem.Visible:= True;
end;
begin
HideTransItems; //Make all list items invisible
case cboTrans.ItemIndex of
0: begin
E(lbSomeListBoxItem);
E(lbSomeOtherItem);
//More calls to "E"
end;
1: begin
E(lbSomeListBoxItem2);
//More calls to "E"
end;
//More indexes
end;
ResetTransHeights; //Adjust visible list item heights to be seen
end;
(The full procedure is just a lot of the exact same types of calls, too much to post here)
Nowhere am I adding or removing items - only changing visibility
There are no events triggered which might be causing some faulty loop
The TPopupBox is located outside of the TListBox
Each TListBoxItem has one or two controls (yet it doesn't matter which ones are being shown/hidden)
Selecting an item in this TPopupBox may work one time, yet fail the next
Sometimes it occurs the first time I show/hide these items, sometimes it takes 20-30 tries
Never able to reproduce while stepping through in Debug
Why would I be receiving this exception, and how do I fix it?
Why would I be receiving this exception, and how do I fix it?
You know why you are receiving it. You are accessing an array with an index that lies outside the valid range.
The question is where that index is. If you cannot readily reproduce then you need to debug to gather diagnostics. On Windows you'd use a tool like madExcept to gather information. Most useful would be the call stack that led to the error.
If you don't have madExcept or a similar tool at hand use trace logging. Instrument your code so that it logs information that allows you to determine which access of the list is out of bounds. You'll likely end up iterating around this as you narrow down the search.
Finally, once you identify which code leads to the error, usually the problem becomes apparent.
I had the same issue when I was animating the height of a TListBoxItem.
The issue only occurred when I was changing the Height of a Selected item. I implemented Jerry Dodge's solution of setting the height to 0.01 instead of 0 which fixed the issue.
Delphi Berlin Code
{Delphi Berlin}
ItemIndex := 0;
Item := ListBox.ItemByIndex(ItemIndex);
Height := Item.Height;
FloatAnimation := TFloatAnimation.Create(nil);
FloatAnimation.Parent := Item;
FloatAnimation.PropertyName := 'height'
FloatAnimation.StartValue := Height;
FloatAnimation.StopValue := 0.01; {Setting to 0 causes "Argument out of range" if the item is selected}
FloatAnimation.Start;

Delphi KBMMemtable filter speed problem

Hi I am using KBmmemtable in a small project and come across a small speed issue i cannot seem to fix.
Basically I have a field in the table which has a boolean value, the table has about 100 records in it. If I itterate though the records in the table setting the value of the field to true it does it very quickly, however if I set a filter on the table and then itterate through the filtered records it takes about 10 times longer even though there could only be 10 records to iteerate through.
Anyone got any ideas
The code I am using is
DM1.DS1.Enabled := False;
with DM1.DS1.DataSet do begin
First;
while not Eof do begin
edit;
Fields[18].AsBoolean := TickState;
// FieldByName('Selected').AsBoolean := TickState;
post;
next;
end;
end;
DM1.DS1.Enabled := true;
I do have an index on the field, I have also tried it without an index
thanks
colin
There is a way to use a filter on a kbmMemTable and make it work really fast...
Set kbmmem.Filtered:=true;
and dont use the Filter property, instead use the OnFilter Event...
procedure Tform1.kbmmemFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept:=Fields[18].AsBoolean;
// when you iter your table you would see only thouse rows having "true"
// on the field "Selected"
end;
and yes dont forget to DisableControls before the while...
with kbmMem do
try
DisableControls;
Filtered:=true;
First;
while not eof do
begin
// do your stuff here
Next;
end;
finally
EnableControls;
end;
This is a suggestion for the loop, it should not take any longer than with an unfiltered kbmMemTable:
with kbmMemTable do
begin
First;
while not EOF do
begin
//do something, but don't change the position of the record-pointer!
//if you do some writing to the record, be sure to
// enable "AutoReposition" in your kbmMemTable
Next;
end;
end;
Disabling the DataSource is not such a good option. Every Component attached to the DataSource is then "empty" and must be refreshed. You get a lot of problems if you use recursion or more than one "disabling" of a DataSource. Same, when you enable the DataSet. With DisableControls you signal all the attached components, that they must not update data. With EnableControls, this condition is ended and the controls are refreshed. Another advantage is, that there is a counter incremented with every DisableControls and decremented with every EnableControls. So you can call this multiple times (for example in an recursion) and only the last call of EnableControls finally enables the controls.

how to maintain database output in combo box or dbgrid after closing TAdoconnection

In this question my objective is to retrieve a database table content.
populate dbGrid, close connection.
If I use the following code, dbgrid or combobox are going to loose the information.
adoQry := TADOQuery.Create(self);
adoQry.Connection := adoConn;
adoQry.SQL.Add(SqlStr);
adoQry.Prepared := true;
try
adoQry.Active := True;
except
on e: EADOError do
begin
MessageDlg('Error while doing query', mtError,
[mbOK], 0);
Exit;
end;
end;
for i := 0 to adoQry.RecordCount - 1 do
begin
cmbCnty.Items.Add(adoQry.Fields[1].AsString);
adoQry.Next
end;
FreeAndNil(adoConn);
FreeAndNil(adoQry);
In case dbGrid, I use StringGrid and it works for me.
However, sometimes I would like to use dbGrid, but not sure how to keep a content with the close connection to the database (after retrieving the content, of course)
Any suggestions, examples would appreciated.
Chris
You can populate a TClientDataSet with your Query ResultSet, and then link the TClientDataSet to the TDBGrid.
If I remember correctly (it a long time agoo I used it, and i can't try it from here), you can set the connection on til adoQuery to nil, and then it behaves like a disconnected dataset (like t-client dataset).
But the data goes away if you close the dataset (adqQry).
In the comment it was stated that the above statement is incorrect (and I still haven't tested it).
But in this Microsoft Knowlegde Base article "How To Create ADO Disconnected Recordsets" http://support.microsoft.com/kb/184397, it shows the same technique.
The same technique is also described in the Delphi about article "TOP ADO programming TIPS" http://delphi.about.com/od/beginners/l/aa021202a.htm
It it correct that setteing connection to nil in most dataset, also closes the dataset.
Benny is correct. You may have to use an TAdoDataSet instead of a TAdoQuery. TAdoDatasets and TClientDatasets have similar functionality. From my understanding, the TAdoQuery components were designed to help migrate a BDE app to dbGo.

Resources