Lazarus
Programming => LCL => Topic started by: lagprogramming on June 19, 2015, 11:07:48 am
-
Why was TMemo designed to append an additional empty line at the end?
-
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.
-
What's Delphi's behaviour? Does it add an empty line at the end?
-
Yes.
-
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.
-
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,
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.
-
If anybody likes,
Here's a small test-program
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:
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.
-
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, 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: 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...).
-
I've attached an example. As previously pointed by other users, I think the important thing is that
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.
-
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.).
-
I've attached an example. As previously pointed by other users, I think the important thing is that
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 :
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
Memo.Scrollbars := ssNone; // Get rid of the scrollbars.
[EDIT]
@eny: +1 you've been a little faster.
-
@eny: +1 you've been a little faster.
I had less text to type ;)
-
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: 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.
-
[...] Are you absolutely sure that Delphi's TMemo has two lines after executing the code: 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)
-
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, 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.
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...
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.
-
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.
-
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...
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.
-
Have you even tried, what you caim ?
Yes I did.
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.
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.
-
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.
-
And as I said before, please keep your tamper down.
http://learning.colostate.edu/guides/guide.cfm?guideid=4
-
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.
-
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:
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.
-
No, but it's in TStrings.GetTextStr
file: stringl.inc (line 463 ff)
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;
-
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