Lazarus

Programming => LCL => Topic started by: lagprogramming on June 19, 2015, 11:07:48 am

Title: TMemo's empty last line
Post by: lagprogramming on June 19, 2015, 11:07:48 am
   Why was TMemo designed to append an additional empty line at the end?
Title: Re: TMemo's empty last line
Post by: Leledumbo on June 19, 2015, 03:03:18 pm
   Why was TMemo designed to append an additional empty line at the end?
I guess it's because its backend, which is a TStrings descendant, behaves that way.
Title: Re: TMemo's empty last line
Post by: lagprogramming on July 08, 2015, 03:14:24 pm
What's Delphi's behaviour? Does it add an empty line at the end?
Title: Re: TMemo's empty last line
Post by: typo on July 08, 2015, 03:16:57 pm
Yes.
Title: Re: TMemo's empty last line
Post by: Graeme on July 11, 2015, 07:10:29 pm
   Why was TMemo designed to append an additional empty line at the end?
Please supply a sample program so I can see what you mean. Then I'll be able to understand what you are saying, and I'll be able to correctly under the question about fpGUI.
Title: Re: TMemo's empty last line
Post by: Windsurfer on July 11, 2015, 08:03:34 pm
When a single TMemo is dropped on a form, it starts with a line that says 'Memo1'. This is followed by a blank line. Every time a line is added,
Code: [Select]
Memo1.Lines.Add('New line')the blank line is pushed down.

I always assumed this was so that there was a blank line for the user to write on.
Title: Re: TMemo's empty last line
Post by: jc99 on July 11, 2015, 11:06:58 pm
If anybody likes,
Here's a small test-program
Code: [Select]
unit frm_MemoEmptyLineMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    btnClearMemo: TButton;
    btnMemoAdd: TButton;
    btnSetText: TButton;
    btnAddText: TButton;
    btnInsertText: TButton;
    lblShowText: TLabel;
    Memo1: TMemo;
    procedure btnClearMemoClick(Sender: TObject);
    procedure btnMemoAddClick(Sender: TObject);
    procedure btnSetTextClick(Sender: TObject);
    procedure btnAddTextClick(Sender: TObject);
    procedure btnInsertTextClick(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.btnClearMemoClick(Sender: TObject);
begin
  Memo1.Clear;
  lblShowText.Caption:=''''+memo1.Text+'''';
end;

procedure TForm1.btnMemoAddClick(Sender: TObject);
begin
  memo1.lines.add('Add Testline '+ inttostr(memo1.Lines.Count));
  lblShowText.Caption:=''''+memo1.Text+'''';
end;

procedure TForm1.btnSetTextClick(Sender: TObject);
begin
  memo1.lines.Text:='This is a test'+LineEnding+
  'if there is alway an empty line'+LineEnding+
  'at the end.';
  lblShowText.Caption:=''''+memo1.Text+'''';
end;

procedure TForm1.btnAddTextClick(Sender: TObject);
begin
  memo1.lines.AddText('AddText Testline '+ inttostr(memo1.Lines.Count));
  lblShowText.Caption:=''''+memo1.Text+'''';
end;

procedure TForm1.btnInsertTextClick(Sender: TObject);
begin
  memo1.lines.Insert(0,'Insert Testline '+ inttostr(memo1.Lines.Count));
  lblShowText.Caption:=''''+memo1.Text+'''';
end;

end.
The form:
Code: [Select]
object Form1: TForm1
  Left = 207
  Height = 217
  Top = 361
  Width = 594
  Caption = 'Form1'
  ClientHeight = 217
  ClientWidth = 594
  LCLVersion = '1.4.0.4'
  object Memo1: TMemo
    Left = 0
    Height = 176
    Top = 0
    Width = 320
    Lines.Strings = (
      'Memo1'
    )
    TabOrder = 0
  end
  object btnClearMemo: TButton
    Left = 8
    Height = 25
    Top = 184
    Width = 75
    Caption = 'Clear'
    OnClick = btnClearMemoClick
    TabOrder = 1
  end
  object btnMemoAdd: TButton
    Left = 92
    Height = 25
    Top = 184
    Width = 75
    Caption = 'Add'
    OnClick = btnMemoAddClick
    TabOrder = 2
  end
  object btnSetText: TButton
    Left = 176
    Height = 25
    Top = 184
    Width = 75
    Caption = 'set Text'
    OnClick = btnSetTextClick
    TabOrder = 3
  end
  object btnAddText: TButton
    Left = 256
    Height = 25
    Top = 184
    Width = 75
    Caption = 'AddText'
    OnClick = btnAddTextClick
    TabOrder = 4
  end
  object btnInsertText: TButton
    Left = 336
    Height = 25
    Top = 184
    Width = 75
    Caption = 'Insert'
    OnClick = btnInsertTextClick
    TabOrder = 5
  end
  object lblShowText: TLabel
    Left = 325
    Height = 15
    Top = 8
    Width = 64
    Caption = 'lblShowText'
    ParentColor = False
  end
end
The only way not to have an empty line, is to set the .text property directly.
Title: Re: TMemo's empty last line
Post by: eny on July 12, 2015, 01:41:44 am
When a single TMemo is dropped on a form, it starts with a line that says 'Memo1'. This is followed by a blank line. Every time a line is added,
Code: [Select]
Memo1.Lines.Add('New line')the blank line is pushed down.
It's a matter of definition.
If you were to check Memo.Lines.Count it would return 1, which is correct. There is no 2nd ('blank') line.
However by default each line is displayed ending with a line ending character/characters (platform dependant constant 'LineEnding').
So the cursor in the TMemo component can be positioned just after the text of the first line (including the LineEnding), which is on line 2.
That second line is not part of the memo text.

The only way not to have an empty line, is to set the .text property directly.
True; this is the confusing part.
If you do this:
Code: [Select]
Memo1.Lines.Text := 'One line'... then the cursor can not be placed beyond the end of the first line as there is no LineEnding (yet).
If you then in turn do "Memo1.Lines.Add('New line')" this is restored and line 1 ánd line 2 will have a LineEnding at the end and you can position the cursor on line 3 (which in turn is not part of the actual Memo text...).
Title: Re: TMemo's empty last line
Post by: lagprogramming on July 12, 2015, 11:01:01 am
I've attached an example. As previously pointed by other users, I think the important thing is that
Code: [Select]
Memo.Clear;Memo.Lines.Add('Text line');will make the TMemo have two lines, the additional empty line influences the vertical scrollbar.
The user would have to scroll each TMemo on the form because he would think that additional informations appear upon scrolling the list.
Things start to become annoying when dealing with multiple TMemos vertically arranged.
For this reason I'd like to know why people chose this design. I don't see the benefits of having that empty line.
Title: Re: TMemo's empty last line
Post by: eny on July 12, 2015, 11:25:52 am
The user would have to scroll each TMemo on the form because he would think that additional informations appear upon scrolling the list.
Which is absolutely no problem for anybody that is used to editors or word processors in general.

If you only want to display information (no editing) simply disable the scroll bar.
Or choose a different component (listbox, virtual string list etc.).
Title: Re: TMemo's empty last line
Post by: jc99 on July 12, 2015, 11:35:32 am
I've attached an example. As previously pointed by other users, I think the important thing is that
Code: [Select]
Memo.Clear;Memo.Lines.Add('Text line');will make the TMemo have two lines, the additional empty line influences the vertical scrollbar.
The user would have to scroll each TMemo on the form because he would think that additional informations appear upon scrolling the list.
Things start to become annoying when dealing with multiple TMemos vertically arranged.
For this reason I'd like to know why people chose this design. I don't see the benefits of having that empty line.
... but that's up to you, because you can always do :
Code: [Select]
  Memo.Lines.Text := TrimRight(Memo.Lines.Text); // Get rid of the last line.
And your annoying design issue only apply when you have exactly so much lines in your memo that it fits.
But you are using a memo, meaning your user can change data, also when the form resizes your loose your design-advantage.
And if you only want to display a static text, why not use a label ?
if you want to get rid of the scrollbars use
Code: [Select]
  Memo.Scrollbars := ssNone; // Get rid of the scrollbars.

[EDIT]
@eny: +1 you've been a little faster. 
Title: Re: TMemo's empty last line
Post by: eny on July 12, 2015, 12:40:31 pm
@eny: +1 you've been a little faster.
I had less text to type  ;)
Title: Re: TMemo's empty last line
Post by: lagprogramming on July 12, 2015, 01:22:05 pm
   Thank you for your recommendations but I'm still interested in why this design has been chosen. I still fail to understand the benefits of that empty line.

   Are you absolutely sure that Delphi's TMemo has two lines after executing the code:
Code: [Select]
Memo.Clear;Memo.Lines.Add('Text line');
   Reading http://bugs.freepascal.org/view.php?id=24973 (http://bugs.freepascal.org/view.php?id=24973) it looks like I've remembered right. In the past TListBox acted the same as TMemo acts now. Why is that nowadays for a TListBox is not acceptable to have an empty line but it's acceptable for a TMemo?
   Also, could that empty line at the end have an impact on copying to clipboard processes!?

   By the way, "Memo1.Lines.Text := TrimRight(Memo1.Lines.Text);" not only slows down things but also removes all [#0..' '] characters. This means all empty lines at the end, space only lines, and who knows what else... I'm not sure this workaround is usable.

   I'm still looking forward. If you have a new idea on the subject, share it here. I'm not much interested in workarounds as I'm interested in why is TMemo behaving this way.
Title: Re: TMemo's empty last line
Post by: jc99 on July 12, 2015, 04:09:09 pm
[...]   Are you absolutely sure that Delphi's TMemo has two lines after executing the code:
Code: [Select]
Memo.Clear;Memo.Lines.Add('Text line');
positive !!
Two lines (one line with CRLF at the end)
With trim CRLF is gone -> one line
(Testet on DXE)
Title: Re: TMemo's empty last line
Post by: Graeme on July 13, 2015, 10:30:55 am
When a single TMemo is dropped on a form, it starts with a line that says 'Memo1'. This is followed by a blank line. Every time a line is added,
Code: [Select]
Memo1.Lines.Add('New line')the blank line is pushed down.

I always assumed this was so that there was a blank line for the user to write on.
No, fpGUI's TfpgMemo doesn't do that. The Lines property is completely empty, not even the silly 'Memo1' text. Here is code that the fpGUI UI Designer creates when you drop a memo on a form.

Code: [Select]
  Memo1 := TfpgMemo.Create(self);
  with Memo1 do
  begin
    Name := 'Memo1';
    SetPosition(20, 10, 225, 194);
    FontDesc := '#Edit1';
    Hint := '';
    TabOrder := 2;
  end;

If you do the following on a newly created Memo (as shown above) and execute the following code...

Code: [Select]
procedure TMainForm.ButtonClicked(Sender: TObject);
begin
  ShowMessage(IntToStr(Memo1.Lines.Count));
  Memo1.Lines.Add('new line');
  ShowMessage(IntToStr(Memo1.Lines.Count));
end;

The results would be 0 and then 1.
Title: Re: TMemo's empty last line
Post by: Graeme on July 13, 2015, 10:33:24 am
I guess it's because its backend, which is a TStrings descendant, behaves that way.
Rubbish! When you create a new TStrings or TStringList, the line count is 0 (no text at all).  It is VCL or LCL that injects text into the Memo.Lines property at creation of the Memo component.
Title: Re: TMemo's empty last line
Post by: jc99 on July 13, 2015, 06:08:13 pm
Rubbish! When you create a new TStrings or TStringList, the line count is 0 (no text at all).  It is VCL or LCL that injects text into the Memo.Lines property at creation of the Memo component.
Hey keep the temper down.
Have you even tried, what you caim ?
I tried, they behave EXACTLY the same on adding text.
The only difference is, that you can't get rid of the last CRLF with trimright.
In TStringList the LineEnding seems to be hard-coded the "End-of-the-Line".

[..]
If you do the following on a newly created Memo (as shown above) and execute the following code...

Code: [Select]
procedure TMainForm.ButtonClicked(Sender: TObject);
begin
  Memo1.Clear;
  ShowMessage(IntToStr(Memo1.Lines.Count));
  Memo1.Lines.Add('new line');
  ShowMessage(IntToStr(Memo1.Lines.Count));
end;

The results would be 0 and then 1.
The results are 0 and then 1.
But ...
Memo1.Lines.Text is 'new line'+LineEnding; !!
You see 2 lines, but you actually have only one, with LineEnding at the end.
 
By the way that stupid 'Memo1' as a start is not added by default of the wigetset, it is inserted because you didn't cleared Lines property in the Designer. YOU can put there whatever you like, even rubbish or nothing.
Title: Re: TMemo's empty last line
Post by: Graeme on July 14, 2015, 01:22:14 am
Have you even tried, what you caim ?
Yes I did.

Quote
Memo1.Lines.Text is 'new line'+LineEnding; !!
You see 2 lines, but you actually have only one, with LineEnding at the end.
If you bothered to try it in fpGUI you would have noticed that what you said is NOT true with fpGUI. If you run the sample code I listed, you have exactly 1 line of text in the memo. To get to line 2, YOU the user need to press the Enter key. Simply clicking with the mouse, you will only be able to place the cursor on line 1, because there is only one line of text.

Quote
By the way that stupid 'Memo1' as a start is not added by default of the wigetset,
It is inserted by the VCL or LCL forms designer - forcing the developer to manually go and clear something that the form designer inserted. Such a stupid idea - hence the fpGUI UI Designer does NOT insert such pointless lines of text.
Title: Re: TMemo's empty last line
Post by: jc99 on July 14, 2015, 02:12:15 am
If you had read my reply right I was not referring to fpGUI but to TStringList.

You are right, I didn't use fpGui, but i never said, I did.
I used delphi, win32, win64, in lazarus, all with the same result.
maybe fpGui has a compatibility-issue here.

And as I said before, please keep your tamper down.

BTW. Inserting the Name of the Control as default text is also done in all of these,
  maybe there is a compatibility issue here too.
Title: Re: TMemo's empty last line
Post by: eny on July 14, 2015, 08:47:22 am
And as I said before, please keep your tamper down.
http://learning.colostate.edu/guides/guide.cfm?guideid=4
Title: Re: TMemo's empty last line
Post by: lagprogramming on July 14, 2015, 10:00:34 am
   As I understand, this behaviour is implemented in Lazarus in order to be compatible with Delphi. Unlike Lazarus, fpGui acts differently, probably mseIDE, too. Each project implements it's own philosophy and it's good to know that there is diversity. There is a single situation where Lazarus's behaviour doesn't fit very well the needs: form with multiple vertically arranged Memos that have vertical scrollbars and approximately the same number of lines as memo's rows. This is an unpleasant situation(situation that I've encountered), but it's a situation with few chances to be encountered by other developers.
   Thank you very much for your replies, they cleared things up to me.
Title: Re: TMemo's empty last line
Post by: Graeme on July 14, 2015, 10:59:01 am
If you had read my reply right I was not referring to fpGUI but to TStringList.
Then please take a look at the implementation of TStringList in Free Pascal. The TStringList.Add(...) boils down to the following code:

Code: [Select]
Procedure TStringList.InsertItem(Index: Integer; const S: string; O: TObject);
begin
  Changing;
  If FCount=Fcapacity then Grow;
  If Index<FCount then
    System.Move (FList^[Index],FList^[Index+1],
                 (FCount-Index)*SizeOf(TStringItem));
  Pointer(Flist^[Index].Fstring):=Nil;  // Needed to initialize...
  Flist^[Index].FString:=S;
  Flist^[Index].FObject:=O;
  Inc(FCount);
  Changed;
end;

Nowhere in there do you see a LineEnding character being appended to the S parameter contents. So if a LineEnding character is added to the TMemo, then it is LCL that injects that extra text, not the TStringList.
Title: Re: TMemo's empty last line
Post by: jc99 on July 14, 2015, 11:18:20 am
No, but it's in TStrings.GetTextStr
file: stringl.inc (line 463 ff)
Code: [Select]
Function TStrings.GetTextStr: string;

Var P : Pchar;
    I,L,NLS : Longint;
    S,NL : String;

begin
  CheckSpecialChars;
  // Determine needed place
  Case FLBS of
    tlbsLF   : NL:=#10;
    tlbsCRLF : NL:=#13#10;
    tlbsCR   : NL:=#13;
  end;
  L:=0;
  NLS:=Length(NL);
  For I:=0 to count-1 do
    L:=L+Length(Strings[i])+NLS;
  Setlength(Result,L);
  P:=Pointer(Result);
  For i:=0 To count-1 do
    begin
    S:=Strings;
    L:=Length(S);
    if L<>0 then
      System.Move(Pointer(S)^,P^,L);
    P:=P+L;
    For L:=1 to NLS do
      begin
      P^:=NL[L];
      inc(P);
      end;
    end;
end;
Title: Re: TMemo's empty last line
Post by: Graeme on July 14, 2015, 11:44:26 am
No, but it's in TStrings.GetTextStr
file: stringl.inc (line 463 ff)
That is only if you reference all the content via the Text property. How else is TStrings supposed to differentiated between individual lines of text, if you ask for all the data return at once.

Alternatively, you can query the data line by line in a loop, which means there will be no LineEnding character appended for each line returned. The point is, by adding text it doesn't add a LineEnding character. On returning data, you as a developer have a choice - all in one go (which means it needs to somehow delimit each line), or line by line.

Regards,
  Graeme
TinyPortal © 2005-2018