The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,861 other subscribers

file – String format procedure similar to writeln – Stack Overflow

Posted by jpluimers on 2021/05/11

Cool Format feature from [WayBack] file – String format procedure similar to writeln – Stack Overflow:

The cool thing about using Format is that you use for Format Strings not only to parameterize things like width and precision inside that Format String, but also as parameters like you normally would provide values.

You can get very close to using a width of 8 and a precision of 2, like the example in your question.

For instance, to quote the documentation:

Format ('%*.*f', [8, 2, 123.456]);

is equivalent to:

Format ('%8.2f', [123.456]);

That is a much overlooked feature of Format and Format Strings.

Edit 20250910:

This was part of my answer¹ there to mimic WriteLn formatting behaviour which was not even documented at the now deleted [Wayback/Archive] Standard Routines and I/O.

Normally deleted information like above results in worse information at their current documentation site.

This time however was an exception: the current documentation is better².

¹ the start of my answer:

Technically the answer is “yes”. But it’s not recommended.

You can write your own text file device driver based on the System.TTextRec type.

I’ve done this in the past (especially in the Turbo Pascal era) for debugging purposes. It is a lot of work, and requires you to write a special MyAssign procedure, and be sure to close the Text file using the TTextRec in a try..finally block. Cumbersome, but doable.

A much easier alternative is using the Format function as described by Andreas Rejbrand.

Later that day, [Wayback/Archive] Uwe Raabe posted how to use this technique:

Although Jeroen doesn’t recommend it, I have done something like this about a year ago – just to learn how to do it. This is the code:

type
  TTextFile = class
  private type
    TTextRecHelper = record helper for TTextRec
    public
      function GetTextFile: TTextFile;
      procedure SetTextFile(const Value: TTextFile);
      property TextFile: TTextFile read GetTextFile write SetTextFile;
    end;
  private var
    FBuilder: TStringBuilder;
    class function TextClose(var F: TTextRec): Integer; static;
    class function TextIgnore(var F: TTextRec): Integer; static;
    class function TextInput(var F: TTextRec): Integer; static;
    class function TextOpen(var F: TTextRec): Integer; static;
    class function TextOutput(var F: TTextRec): Integer; static;
    procedure AppendString(const Value: string);
    procedure AssignFile(var F: Text);
  public
    var F: Text;
    constructor Create;
    destructor Destroy; override;
    function ToString: string; override;
  end;

constructor TTextFile.Create;
begin
  inherited Create;
  FBuilder := TStringBuilder.Create();
  AssignFile(F);
  Rewrite(F);
end;

destructor TTextFile.Destroy;
begin
  Close(F);
  FBuilder.Free;
  inherited Destroy;
end;

procedure TTextFile.AppendString(const Value: string);
begin
  FBuilder.Append(Value);
end;

procedure TTextFile.AssignFile(var F: Text);
begin
  FillChar(F, SizeOf(F), 0);
  with TTextRec(F)do
  begin
    Mode := fmClosed;
    BufSize := SizeOf(Buffer);
    BufPtr := @Buffer;
    OpenFunc := @TextOpen;
    TextFile := Self;
  end;
end;

class function TTextFile.TextClose(var F: TTextRec): Integer;
begin
  Result := 0;
end;

class function TTextFile.TextIgnore(var F: TTextRec): Integer;
begin
  Result := 0;
end;

class function TTextFile.TextInput(var F: TTextRec): Integer;
begin
  F.BufPos := 0;
  F.BufEnd := 0;
  Result := 0;
end;

class function TTextFile.TextOpen(var F: TTextRec): Integer;
begin
  if F.Mode = fmInput then
  begin
    F.InOutFunc := @TextInput;
    F.FlushFunc := @TextIgnore;
    F.CloseFunc := @TextIgnore;
  end else
  begin
    F.Mode := fmOutput;
    F.InOutFunc := @TextOutput;
    F.FlushFunc := @TextOutput;
    F.CloseFunc := @TextClose;
  end;
  Result := 0;
end;

class function TTextFile.TextOutput(var F: TTextRec): Integer;
var
  AStr: AnsiString;
begin
  SetLength(AStr, F.BufPos);
  Move(F.BufPtr^, AStr[1], F.BufPos);
  F.TextFile.AppendString(string(AStr));
  F.BufPos := 0;
  Result := 0;
end;

function TTextFile.ToString: string;
begin
  Close(F);
  result := FBuilder.ToString;
  Rewrite(F);
end;

function TTextFile.TTextRecHelper.GetTextFile: TTextFile;
begin
  Move(UserData[1], Result, Sizeof(Result));
end;

procedure TTextFile.TTextRecHelper.SetTextFile(const Value: TTextFile);
begin
  Move(Value, UserData[1], Sizeof(Value));
end;

Example of how to use it according to your question:

  tf := TTextFile.Create;
  try
    Writeln(tf.F, 'Result is: ', var1:8:2,' | ', var2:8:2,' |');
    Caption := tf.ToString;
  finally
    tf.Free;
  end;

² the current documentation not only documents how to format Writeln parameters, but also how to assign your own file handlers using the TTextRec record.

Documentation on this (Write has the detailed formatting information, Writeln is very brief):

TTextRec is really cool. It does not just allow you to assign your own text/TextFile handlers, but also copy a text/Textfile variable to another one as shown in [Wayback/Archive] Why can’t I Assign between system.file types in Delphi – Stack Overflow (thanks [Wayback/Archive] Alister and [Wayback/Archive] Mark Elder):

Casting the System.Text variable to a TTextRec seems to allow the assignment. As some of the other commenters indicated this is probably still a bad idea and may have other side effects that are not evident in this simple test case.

var
  x: System.Text;
  y: System.Text;

begin
  AssignFile(x, 'c:\localdata\temp.txt');
  Rewrite(x);

  WriteLn(x, 'hello from x');

  TTextRec(y) := TTextRec(x);
  WriteLn(y, 'hello from y');

  CloseFile(y); // Both x and y are sharing the same handle, only close 1
end;

For binary file types, you can use TFileRec to do this trick.

This allows passing and copying text/TextFile/file parameters. As an alternative to using TTextRec or TFileRec for this is to use pointers like ˆfile or ˆtext:

Related blog posts:

Query: [Wayback/Archive] TTextRec at DuckDuckGo

--jeroen

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.