Recent

Author Topic: How to display a TAB (hex 09) character  (Read 1183 times)

garymq

  • Newbie
  • Posts: 5
How to display a TAB (hex 09) character
« on: September 07, 2025, 02:56:35 am »
Hello
I am displaying serial ASCII data from a serial port on a Listbox.
Everything works fine except that the TAB characters (Hex 09) in the incoming data are ignored.
For example, the incoming text string is:
Serial Number   1410 , where the gap between the "Number" and "1410" is an ASCII TAB character, i.e. hex 09.
53 65 72 69 61 6C 20 4E 75 6D 62 65 72 09 31 34 31 30
S   E   R  I    A   L       N   U   M  B   E   R       1   4  1   0
When this is displayed on the Listbox, it shows:
Serial Number1410. That is, the TAB characters are ignored.

I have looked at Listbox Properties, and tried different fonts, but I can't find a way  to make tabs display correctly.

Any suggestions?

Thanks, Gary




440bx

  • Hero Member
  • *****
  • Posts: 5810
Re: How to display a TAB (hex 09) character
« Reply #1 on: September 07, 2025, 04:48:29 am »
presuming you're on Windows, the Listbox control does not honor tab characters.

if you are using a monospaced font in the listbox then you could scan the string for the presence of tabs and replace them with an appropriate number of spaces.

if you are not using a monospaced font then, what comes to mind is to scan the string for tabs and owner-draw how you want the string to appear in the listbox.  if you decide to go this route, have a look at TabbedTextOut, it may prove useful to you.

HTH.


FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

mtrsoft

  • Jr. Member
  • **
  • Posts: 61
Re: How to display a TAB (hex 09) character
« Reply #2 on: September 07, 2025, 07:23:19 am »
The following are snippets from a program that reads the first five lines of a text file and displays them in a memo AND substitutes the space and tab characters with printable characters.

The printable characters were selected using Windows Character Map app.

Code: Pascal  [Select][+][-]
  1. //In a unit containing resource strings
  2. resourcestring  {String Constants}
  3.   rscSpace     = ' ';
  4.   rscTab       = #9;
  5.  
  6.  
  7. //Earlier in the program
  8. const
  9.   DisplayCharTab = '»';
  10.   DisplayCharSpace = '•';
  11.  
  12. procedure TDlgImportText.btnRefreshClick(Sender: TObject);
  13. Var
  14.   I       :LongInt;
  15.   tmpS    :string;
  16. begin
  17. {Read 1st five lines, substitute space and Tab chars with printalble char
  18.  and display in the memo}
  19. If NOT FileExists( AFileName) then Exit;
  20. AssignFile(TF, AFileName);
  21. Reset(TF);
  22. try
  23.   memo1.Clear;
  24.   I := 1;
  25.   While (I <= 5) and not(EOF(TF)) do
  26.     begin
  27.     Readln(TF, tmpS);
  28.     if Length(tmpS) > 0 then
  29.       begin
  30.       tmpS := StringReplace(tmpS, rscSpace, DisplayCharSpace, [rfReplaceAll]);
  31.       tmpS := StringReplace(tmpS, rscTab, DisplayCharTab, [rfReplaceAll]);
  32.       //For J := 1 to Length(tmpS) do
  33.       //  begin
  34.       //  If tmpS[J] = rscSpace[1] then tmpS[J] := DisplayCharSpace[1];
  35.       //  If tmpS[J] = rscTab[1] then tmpS[J] := DisplayCharTab[1];
  36.       //  end;
  37.       end;
  38.     memo1.Lines.Append(tmpS);
  39.     Inc(I);
  40.     end;
  41.   finally
  42.   CloseFile(TF);
  43.   end;
  44. end;
  45.  

Hope this helps.

Regards,
John

wp

  • Hero Member
  • *****
  • Posts: 13210
Re: How to display a TAB (hex 09) character
« Reply #3 on: September 07, 2025, 11:55:46 am »
Hmmm... Why do you declare ' ' and #9 as resourestrings? Nobody will ever translate them to another language... It just pollutes the po file.

garymq

  • Newbie
  • Posts: 5
Re: How to display a TAB (hex 09) character
« Reply #4 on: September 07, 2025, 11:18:00 pm »

Thanks everyone for comments and suggestions.

The main takeaway is that the Listbox simply ignores TAB characters, and that's that.

It's worth mentioning that the source of the ASCII strings is an instrument which uses a lot of TABs to space out the characters.
This produces a nicely arranged display in standard Terminal programs (e.g. Teraterm), which correctly display TABs.

What I ended up doing was substituting a space character for the tab character.
Code: Pascal  [Select][+][-]
  1. str1 := StringReplace(str1,#09,#32,[rfReplaceAll]);

The resulting display in the Listbox is not as nice as on a Terminal program, but is at least clear, and the final string has the same number of characters as the original, which is what I want.

Best Regards, Gary



wp

  • Hero Member
  • *****
  • Posts: 13210
Re: How to display a TAB (hex 09) character
« Reply #5 on: September 08, 2025, 12:21:47 am »
But you could write a little function which splits the string at the tabs and puts the parts together again, but before adding a part it must expand the previously accumulated string by adding spaces such that its length is a multiple of the tab size:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.   SysUtils, StrUtils;
  5.  
  6.   function ExpandTabs(AText: String; ATabSize: Integer): String;
  7.   var
  8.     sa: TStringArray;
  9.     i: Integer;
  10.   begin
  11.     Result := '';
  12.     sa := AText.Split(#9);
  13.     for i := 0 to High(sa) do
  14.     begin
  15.       if (sa[i] = '') and (i <> High(sa)) then
  16.         Result := Result + DupeString(' ', ATabSize)
  17.       else
  18.         while Length(Result) mod ATabSize <> 0 do
  19.           Result := Result + ' ';
  20.       Result := Result + sa[i];
  21.     end;
  22.   end;
  23.  
  24. begin
  25.   WriteLn('"', ExpandTabs('1234567'#9'abc', 8), '"');
  26.   WriteLn('"', ExpandTabs('123456'#9'abc', 8), '"');
  27.   WriteLn('"', ExpandTabs('123'#9'abc', 8), '"');
  28.   WriteLn('"', ExpandTabs('1'#9'abc',8), '"');
  29.   WriteLn('"', ExpandTabs(#9'abc', 8), '"');
  30.   WriteLn('"', ExpandTabs('123456781234567812345678', 8), '"');
  31.   WriteLn('"', ExpandTabs('1234'#9'abcd'#9'xyz', 8), '"');
  32.   WriteLn('"', ExpandTabs('abc'#9#9'abc', 8), '"');
  33.   WriteLn('"', ExpandTabs(#9#9'abc', 8), '"');
  34.   WriteLn('"', ExpandTabs('abc'#9, 8), '"');
  35.   WriteLn('"', ExpandTabs('abc'#9#9, 8), '"');
  36.   WriteLn('"', ExpandTabs('abc'#9'abc'#9, 8), '"');
  37.  
  38.   ReadLn;
  39. end.

Output:
Code: [Select]
"1234567 abc"
"123456  abc"
"123     abc"
"1       abc"
"        abc"
"123456781234567812345678"
"1234    abcd    xyz"
"abc             abc"
"                abc"
"abc     "
"abc             "
"abc     abc     "

Thausand

  • Sr. Member
  • ****
  • Posts: 396
Re: How to display a TAB (hex 09) character
« Reply #6 on: September 08, 2025, 12:51:00 am »
May be I mistake but was not work LB_SETTABSTOPS and LBS_USETABSTOPS for windows ?

jamie

  • Hero Member
  • *****
  • Posts: 7306
Re: How to display a TAB (hex 09) character
« Reply #7 on: September 08, 2025, 01:04:13 am »
Here is my version. :D
Code: Pascal  [Select][+][-]
  1. Function TabsToAlignedSpace(S:String;ATabStep:Integer=8):String; //Use a Mono Font for this.
  2. var
  3.   C:Char;
  4. Begin
  5.   Result:='';Inc(aTabStep);
  6.   For C in S do
  7.     if C=#9 Then
  8.       Result:=Result+StringOfChar(' ',(aTabStep-1)-(Length(Result) Mod ATabStep)) else
  9.       Result := Result+C;
  10.    Result := TrimRight(Result);
  11. end;                                                
  12.  

There is a Tab2Space in the StrUtils but I didn't want to include that because it does not index the space, simply generates the number of spaces you entered without calculating the current length of the string.

Jamie

The only true wisdom is knowing you know nothing

wp

  • Hero Member
  • *****
  • Posts: 13210
Re: How to display a TAB (hex 09) character
« Reply #8 on: September 08, 2025, 01:46:52 am »
@jamie: Yes, nice and compact. But it does not handle repeated tabs correctly, as well as tabs at the end of the input string.

And there is a problem in both your and my code: Length() is correct only for ASCII strings. If the string is allowed to contain UTF8-encoded characters, we should use the function UTF8Length (from unit LazUtf8) instead.
« Last Edit: September 08, 2025, 01:55:31 am by wp »

cdbc

  • Hero Member
  • *****
  • Posts: 2464
    • http://www.cdbc.dk
Re: How to display a TAB (hex 09) character
« Reply #9 on: September 08, 2025, 01:55:15 am »
Hi
Yup, Werner is right and while you're at it, you should use the enumerator from 'LazUnicode' and change the
Code: Pascal  [Select][+][-]
  1. var
  2.   C:Char;
to
Code: Pascal  [Select][+][-]
  1. var
  2.   C:String;
I think it's enough to include 'lazunicode' in uses and change the C var to string... And as @wp says, 'Length, 'Trim' & 'StringOfChar' should come from 'LazUtf8'...
Have fun  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

jamie

  • Hero Member
  • *****
  • Posts: 7306
Re: How to display a TAB (hex 09) character
« Reply #10 on: September 08, 2025, 02:01:50 am »
Maybe but I really don't think the device is pumping utf8 strings.

Just name it AnsiTabSpace etc..

The only true wisdom is knowing you know nothing

wp

  • Hero Member
  • *****
  • Posts: 13210
Re: How to display a TAB (hex 09) character
« Reply #11 on: September 08, 2025, 11:47:36 am »
Jamie's and my previously presented solutions have the disadvantage that they require a font with constant character width; by default, however, Lazarus has proportional fonts in which every character has its own width, and the alignment of columns breaks down.

Many controls can "owner-draw" their text, a listbox for example when its Style property is changed to lbOwnerDrawFixed and when the drawing code is implemented in the OnDrawItem event handler. Using the Canvas method TextRect it is possible to expand tabs automatically by setting the canvas' TextStyle.ExpandTabs is true:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  2.   ARect: TRect; State: TOwnerDrawState);
  3. var
  4.   ts: TTextstyle;
  5.   listbox: TListbox;
  6.   txt: String;
  7.   R: TRect;
  8. begin
  9.   listbox := TListbox(Control);
  10.   if ([odSelected, odFocused] * State <> []) then
  11.   begin
  12.     listbox.Canvas.Brush.Color := clHighlight;
  13.     listbox.Canvas.Font.Color := clHighlightText;
  14.   end else
  15.   begin
  16.     listbox.Canvas.Brush.Color := clWindow;
  17.     listbox.Canvas.Font.Color := clWindowText;
  18.   end;
  19.   listbox.Canvas.FillRect(ARect);
  20.  
  21.   ts := listbox.Canvas.TextStyle;
  22.   ts.Expandtabs := true;
  23.   txt := listbox.Items[Index];
  24.   listbox.Canvas.TextRect(ARect, ARect.Left + 2, ARect.Top, txt, ts);
  25. end;
The tab width here is 8 characters where the character width is understood here as the average character width of the font.

For individual tab widths there is a solution for Windows as mentioned earlier by @Thausand.

If you need a cross-platform solution you can call the following function in the DrawItem event handler instead of listbox.Canvas.TextRect. It gets an array of tab positions (in pixels) and, optionally, the default tab size (in pixels) which is used when there are more tabs in the text than tab positions. When the default tab size is not given, it is assumed to be PixelsPerInch/2 (i.e. 1/2 inch).
Code: Pascal  [Select][+][-]
  1. procedure DrawTabText(ACanvas: TCanvas; ARect: TRect;
  2.   AText: String; ATabPos: Array of Word; ADefaultTabSize: Integer = 0);
  3. var
  4.   sa: TStringArray;
  5.   i, wPart, tabIdx, tabPos, nextTabPos: Integer;
  6.   part: String;
  7. begin
  8.   if ADefaultTabSize <= 0 then
  9.     ADefaultTabSize := Screen.PixelsPerInch div 2;  // 1/2 inch tabs by default
  10.  
  11.   sa := AText.Split(#9);
  12.   tabIdx := -1;
  13.   tabPos := 0;
  14.   for i := 0 to High(sa) do
  15.   begin
  16.     part := sa[i];
  17.     ACanvas.TextOut(ARect.Left + tabPos, ARect.Top, part);
  18.     wPart := ACanvas.TextWidth(part);
  19.     nextTabPos := tabPos;
  20.     repeat
  21.       inc(tabIdx);
  22.       if tabIdx >= Length(ATabPos) then
  23.         nextTabPos := nextTabPos + ADefaultTabSize
  24.       else
  25.         nextTabPos := ATabPos[tabIdx];
  26.     until (nextTabPos > tabPos + wPart);
  27.     tabPos := nextTabPos;
  28.   end;
  29. end;
I checked several cases, and the procedure seems to work. UTF8 characters are considered correctly as well.

jamie

  • Hero Member
  • *****
  • Posts: 7306
Re: How to display a TAB (hex 09) character
« Reply #12 on: September 08, 2025, 01:20:27 pm »
Here is a direct canvas out attempt. I've tested multiple tabs chained together.

Code: Pascal  [Select][+][-]
  1. Procedure TextOutWithTabs(aCanvas:TCanvas;X,Y:Integer;aStr:String;ATabStep:Integer=8); //Use a Mono Font for this.
  2. var
  3.   W:Integer;
  4.   C:Char;
  5.   B:RawBytestring;//Build str;
  6. Begin
  7.  W := ACanvas.TextWidth('X')*aTabStep;
  8.  B:='';
  9.   For C in aStr do
  10.     if C=#9 Then
  11.       Begin
  12.         aCanvas.TextOut(X,Y,B);
  13.         X:=X+aCanvas.TextWidth(B);
  14.         if W>0 Then
  15.         X:=X+(W-(X Mod W));
  16.         B:='';
  17.       end Else B:=B+C;
  18.  aCanvas.TextOut(X,Y,B);
  19. end;
  20.  
  21. procedure TForm1.Button1Click(Sender: TObject);
  22. begin
  23.  Canvas.Brush.Style :=bsClear;
  24.  TextOutWithTabs(Canvas,0,0,'ΩT'#9'êTest2'#9#9'Test3',10);
  25.  
  26. end;                                        
  27.  

One question, why is it I can't have a 0 value for the right expression of the MOD ?

Its all binary I would of thought the product should be 0, not a DIV by 0 error?

Jamie

The only true wisdom is knowing you know nothing

creaothceann

  • Full Member
  • ***
  • Posts: 201
Re: How to display a TAB (hex 09) character
« Reply #13 on: September 08, 2025, 02:17:00 pm »
mod is the remainder after a division. Since dividing by 0 is impossible, modulo 0 is also impossible. (A x86 CPU is perhaps doing both at the same time.)

An intuitive (though perhaps mathematically insufficient) way to think of 6 / 3 = 2 is in terms of subtraction: you need to subtract 3 from 6 two times until the result is <= zero. Dividing x by zero would mean subtracting zero from x until the result is zero.
And don't start an argument, I am right.

garymq

  • Newbie
  • Posts: 5
Re: How to display a TAB (hex 09) character
« Reply #14 on: September 09, 2025, 01:47:19 am »

hello All

Thanks for all the detailed replies to my original question about displaying TAB characters in a Listbox.
This discussion sort of morphed into a more general discussion of various ways to display TAB characters.

FWIW: Due to time constraints of needing to get the application finished, I went with the simple approach of just substituting a space character (hex 20) before adding the string to the Listbox. (str1 := StringReplace(str1,#09,#32,[rfReplaceAll]);)

This produces a reasonable, readable display in the Listbox. Although not as nice as a full implementation of TAB characters, it's much better than simply ignoring the TAB character (which is what the standard Listbox does), and doesn't need any processing.
So, as often the case, a compromise approach.
Thanks for all contributions.

Best Regards, Gary



 

TinyPortal © 2005-2018