When I use DBCtrlGrid Delphi component, setting the RowCount property to 5, for example, it will always display 5 lines on the component, even if the table has only 3 records. I need to know how do I hide the additional lines and show only the lines that my table has records.
You can use the AfterScroll event of the dataset connected to the DBCtrlGrid to set its RowCount:
procedure TForm1.qApplsAfterScroll(DataSet: TDataSet);
begin
DBCtrlGrid1.RowCount := DataSet.RecordCount;
end;
Be aware, though, that not all types of Delphi dataset return meaningful numbers for their RecordCounts. If yours doesn't, you'll need to do something like running a "SELECT COUNT(*) ..." query in the AfterScroll event to get the value you need to set RowCount to.
Btw, the main use of datasets' AfterScroll event is to allow you to do things like this, where some action needs to be taken when the dataset's cursor moves.
Related
I have found many solutions online but they aren't working becaue the StringGrid1.ColumnCount peoperty is read only. I am using Delphi 10 Seattle.
I have a StringGrid1 and I need to add columns at runtime. To be specific I have to add columns according to the size of a TList. In particular:
var a: TList<double>;
begin
//fill the TList...
for i := 0 to a.Count - 1 do
begin
StringGrid1.AddColumn(); //how can I do this?
end;
end;
I find this very easy on Lazarus (but it has FPC of course) but on Delphi I really don't know what to do. I am working on Firemonkey.
Use the grid's AddObject() or InsertObject() method to add an object instance of the desired TColumn-derived class, like TStringColumn. The column object will get added to the grid's Columns array. The ColumnCount property simply returns the number of columns in the array, that is why it is read-only.
I have a table called Artist which currently contains four records and a TSQLQuery that contains the following statement:
SELECT name
FROM Artist
The table Artist also contains the following records:
id name
1 Artist 1
2 Artist 2
3 Artist 3
4 Artist 4
Current method of record retrieval:
Query1.Open;
for i := 0 to qArtist.FieldCount -1 do
with cbArtist.ListBox.ListItems[i] do
Text := qArtist.Fields[i].AsString;
Previous method of record retrieval:
Data bind Query1 to ComboBox1.
With the "previous" method, ComboBox1 would display all the expected records from the Artist table. However, when I try to use "current" method Query1 is only selecting the very first record from the Artist table despite there being three other existing values. I have tried the "current" method across other queries and they also returned only the first value of the table.
The reason I am going for this new approach is because I feel that I am very limited in what I can do if I continue to the "previous" / data bind method, but that is besides the point.
So how can I fix this problem? i.e. the problem of the query only selecting the very first record from the table.
You must use the Eof and Next methods to iterate over the records.
Query1.Open;
while not Query1.eof do
begin
cbArtist.Items.Add(Query1.FieldByName('Artist').AsString);
Query1.Next;
end;
You code show an interaction over fields, if you need iterate all record then you must use a code like:
Query1.Open;
Query1.first;
while not Query1.eof do
begin
with cbArtist.ListBox.ListItems[i] do
Text := qArtist.Fields[1].AsString; //put here field you want to bind on ListBox.
Query1.next;
end;
I don't think you are navigating your query's dataset correctly. The FieldCount and Fields[i] access the field metadata (columns going across), not the rows. I beleive in Delphi you use While not Eof begin... end.
Navigating Datasets
I would consider altering the data binding fields to suit your needs. Delphi's Databinding is very powerful. Manually iterating the dataset just to populate a control will just be extra code where bugs can hide. Utilize the built-in capabilities of the tools and it will be easier to understand and maintain.
I have a column in a DB table which stores pressure. The pressure is always stored as PSI and can be converted to BAR by diving by 14.5.
The user can toggle display of PSI/BAR with a Radio Group.
I was using a TStringGrid and am converting to a TDbGrid - which is quite new to me.
When the user toggles PSI/BAR, how to I update the display in my DB grid? (I imagine that I just execute it's query again? or Call query.Refresh()?) But how do I do the conversion?
Possibly a stored procedure, although that seems like overkill and stored procedurs are also new to me...
By changing the SELECT statement of my query? But how would I do that? SELECT pressure / 14.5 FROM measurements? Or how?
Or is there an OnBeforeXXX() which I can code? Or OnGetDisplayText() or some such?
I am sure thta this is very basic, but until now I have just been displaying unmanipulated data and now I need a conversion function. Google didn'ty help, but I probably didn't know what to ask for.
I also want to change the text of the column title, toggling between "Presure (PSI)" and "pressure (BAR)".
Thanks in advance for any help.
Code a OnGetText event handler for the pressure field like this:
type
TPressureMU = (pmuPSI, pmuBAR);
const
PSIToBarFactor = 1/14.5;
procedure TdmData.qMeasurementsPressureGetText(Sender: TField; var Text: string;
DisplayText: Boolean);
begin
case PressureMU of
pmuPSI: Text := FloatToStr(Sender.AsFloat); //Already PSI
pmuBAR: Text := FloatToStr(Sender.AsFloat * PSIToBarFactor); //ConvertingToBAR
end
end;
I'm using a property PressureMU of the declared enumeration to control if the pressure is shown in PSI or BAR measurement unit.
This way, when the user changes the selection, you just adjust the value of that property.
If you use persistent fields, you can link the event handler directly to you field using the object inspector, and if not, you can do it by code like this:
begin
qMeasurements.FieldByName('Pressure').OnGetText := qMeasurementsPressureGetText;
end;
where qMeasurementsPressureGetText is the name of the method.
Create persistent fields (right-click the query, and choose Add Fields to create fields at design time that are stored in the .dfm). Right-click the query, and add a new field. Make it a calculated field, and in the OnCalcFields event of the query do the conversion from PSI to BAR.
Now when the user toggles the display, you just display either the PSI or BAR column by setting the Column.FieldName, setting it to the actual PSI column or tne newly calculated BAR column.
Of course, if you're not using persistent fields, you can do it all in the query. Simply add a new column in your SQL statement that contains the result of the conversion, and you can still just change the Column.FieldName at runtime to toggle which value is being displayed.
I have a DBGrid and I´m trying to do a billing sheet but sometimes it doesn't do the calculations. How can I avoid that??
procedure TOrcamentos.DBGridEh1ColExit(Sender: TObject);
var
percent: double;
Unid: double;
tot: currency;
vaz: string;
begin
if Dorcamen_SUB.DataSet.State in [dsEdit, dsInsert] then
try
Dorcamen_SUB.DataSet.Post;
finally
vaz := DBGridEh1.Columns[3].Field.text;
if (vaz<> '') then
try
Torcamen_SUB.Edit;
Unid := (Torcamen_SUB.FieldByName('QT').AsFloat);
tot := (Torcamen_SUB.FieldByName('Precovenda').AsFloat);
percent := (Torcamen_SUB.FieldByName('Desconto').AsFloat);
try
tot := tot+(tot * percent)/ 100;
finally
Torcamen_SUB.FieldByName('Total').AsFloat := unid*tot;
Torcamen_SUB.Post;
Orcamentos.TotalExecute(self);
end;
except
end;
end;
end;
The better way to implement calculations is actually to move the calculation to your TTable component that the grid is linked to. The Total field shouldn't actually be a field in the database since but rather a calculated field based on values from other fields. Simply add an extra field using the field editor of the table, type in the field name Total, select the correct datatype and then select the field type as Calculated. Click Ok and then add code similar to this for the OnCalcField event of the table:
Torcamen_SUB.FieldByName('Total').AsFloat := Torcamen_SUB.FieldByName('QT').AsFloat * (
Torcamen_SUB.FieldByName('Precovenda').AsFloat + (Torcamen_SUB.FieldByName('Precovenda').AsFloat * Torcamen_SUB.FieldByName('Desconto').AsFloat)/100) ;
A general rule of thumb is that calculated values shouldn't be saved to the database unless really really necessary. It's best to simply add them as calculated fields to the dataset and then link the grid to the dataset. All calculated fields will then be displayed in the grid and each row will show the correct calculated value based on the values for that row.
I think you're mixing a business logic (like calculating a total) with User Interaction logic (like the event on which some grid column loses the focus) and that's the source of the erratic behavior of your application.
Looks like not even you know where it happens and where it doesn't happen.
Consider using the Field's events (for example, OnChange event) to perform that kind of calculations.
Lucky you if you're using a dataset with aggregation capabilities (like TClientDataSet), because you can just declare what you want in a TAggregateField and forget about doing calculations "by hand".
Not your question but... be careful with the way you're using try/finally also... for example, in this bit of code:
try
tot := tot+(tot * percent)/ 100;
finally
Torcamen_SUB.FieldByName('Total').AsFloat := unid*tot;
//other things
end;
be aware that if for some reason an exception occurs on the line between the try and finally clauses, the variable tot will have an undefined value (in this case, the result of the previous assignment), so the Assignment to the Torcamen_SUB.total field will be wrong, after all. I'm not sure if it is really what you want.
I'm using Delphi 7 and QuickReports on Windows 7. Normally QuickReports require a DataSet generated by a query, but I want to make a report from the contents of a StringGrid as though the StringGrid is a representation of the results of a query.
How?
Use the QuickReport.OnNeedData event handler. It passes a var parameter called MoreData (a boolean); setting it to True means it gets called again. Leave the QuickReport.DataSource property blank, and use plain TQRText controls rather than TQRDBText.
// CurrLine is an Integer. In your case, it can represent a row in the StringGrid.
procedure TPrintLogForm.QuickRep1NeedData(Sender: TObject;
var MoreData: Boolean);
begin
MoreData := (CurrLine < StringGrid1.RowCount);
if MoreData then
begin
qrTextLine.Caption := StringGrid1.Cells[0, CurrLine];
qrTextData.Caption := StringGrid1.Cells[1, CurrLine];
Inc(CurrLine);
end;
end;
I assume the set of columns is fixed within the StringGrid (and withing the corresponding TClientDataSet). Step-by-step instructions:
Drop a TClientDataSet on the form
Doublic-click on the TClientDataSet, hit the INSERT key on the keyboard to add an new field, add one field for each of your grid's columns. Example: COL1, String, 128 width.
Right-click on the TClientDataSet on the form and hit "Create DataSet"
AT RUNTIME run this kind of code:
CS.Append;
CS['COL1'] := 'Whatever';
CS['COL2'] := 'An other thing';
CS.Post;
You'll need to do the Append/Post in a loop, looping over each row in the grid. You can assign the COL1, COL2 etc in an other loop, or you can hand-code it.