0

I use DBGrid with Delphi XE2 to show some query results. In the result some (integer) Fields represent a duration in seconds that I want to show in some format like "hh:mm:ss" I don't know in advance the fields order since the queries may be different but I know the fields name that I want to format. What is the most efficient way to do it ?

3
  • 3
    Try using the DisplayFormat property check a sample here stackoverflow.com/questions/3619835/… Commented Jun 26, 2013 at 18:14
  • @RRUz - make an answer - this is the proper variant for the localized question like this Commented Jun 26, 2013 at 19:52
  • @RRUZ I yet use DisplayFormat for other purposes, but it seemed to me that there is not any Format string that can change seconds to 'hh:mm:ss' Commented Jun 27, 2013 at 7:20

2 Answers 2

3

While answer by @AndreaBoc is correct (but only answers one of your questions), it is somewhat repetitive: copy-pasting the same calculations looks an anti-pattern to me.

Repetitiveness may be removed by using http://docwiki.embarcadero.com/Libraries/XE4/en/System.Math.DivMod

procedure TForm1.PrettyPrintSeconds(Sender: TField;
  var Text: string; DisplayText: Boolean);
var
  hh,mm,ss: Word;
begin
  DivMod( Sender.AsInteger, 3600, hh, mm );
  DivMod( mm, 60, mm, ss );
  Text := Format('%.2d:%.2d:%.2d',[hh,mm,ss]);
end;

Additionally, if you really want be efficient ( but then why using TDataset? ) you may replace too flexible Format function with 3 temp string variables and old-style Str procedure like outlined at http://docwiki.embarcadero.com/Libraries/XE4/en/System.SysUtils.IntToStr :-)

This should be set for http://docwiki.embarcadero.com/Libraries/XE2/en/Data.DB.TField.OnGetText

That is better than copy-pasting same operations twice and more, increasin both CPU time and chance to make silly typo.

Then there is the next part of question: don't know the fields order... but I know the fields name that I want to format. That means that you should tune this field after the query opened:

procedure TDataModule1.Query1AfterOpen(DataSet: TDataSet);
var F: TField;
begin
    F := DataSet.FindField('MY-Name-For-Time');
    if F = nil 
       then (* .... no such field - do something about it .... *)
       else F.OnGetText := TForm1.PrettyPrintSeconds;
end;

From BeforeClose event you may remove (set to nil) this event handler similarly. Just for that rare check that the same TField object gets re-used for different purpose later. It should not happen except for some very exotic situations, but just to "clean after oneself".


There is less effective (from CPU point of view) but more VCL in spirit approach as well.

For the query in its AfterOpen event: look for the given field name like above, and if found then:

  1. save the result for the later use (as linear string search inside FieldByName is not the most effecient operation per se, so should not repeat it for every value displaying)
    • Proper but somewhat tedious way would be to make new TTimeField property for the query owner.
    • under Win32 you may (ab)use Int32 property TDataSet.Tag, storing there the pointer via type-cast. That is fragile, unsafe and unportable code, but storing Integer, pointer and TObject values in any of those variables is somewhat "old Delphi style" ( like in TStringList.Objects ) and it works, if you're careful and only interested in Win32.
  2. add a calculated field of datetime type
  3. set 'hh:nn:ss' as format for that new field: http://docwiki.embarcadero.com/Libraries/XE2/en/Data.DB.TDateTimeField.DisplayFormat
  4. Make the new field friendly to DBGrid and other db-aware controls
  5. Make the original field invisible or remove its column from the grid - you have the new field instead.

Then set OnCalcFields on the query like:

// 1. Delphi TDateTime is double and 1 day equals to 1.0
// 2. Multiplication is more efficient than division - do it by pen and paper and see
procedure TDataModule1.Query1AfterOpen(DataSet: TDataSet);
const coeff = 1.0 / ( 24 * 60 * 60 );
begin
  MyCalcField.AsTime := MySourceField.AsInteger * coeff;
end; 

Then you would be able to use the new virtual field in every place where Delphi routines would expect TDateTime value.

Sign up to request clarification or add additional context in comments.

Comments

1

You can use OnGetText event of the field for you request:

procedure TForm1.Table1secondsGetText(Sender: TField;
  var Text: string; DisplayText: Boolean);
var
  seconds,hh,mm,ss:Integer;
begin
  seconds := Sender.AsInteger;
  hh := seconds div 3600;
  mm := (seconds - (hh * 3600)) div 60;
  ss := (seconds - (hh * 3600) - (mm * 60));
  Text := Format('%.2d:%.2d:%.2d',[hh,mm,ss]);
end;

for example 185 seconds will be displayed in this way: 00:03:05

1 Comment

If you want to use this in more than one place, create a library function SecsToTime(Sec: Integer): string;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.