0

I have a memo field where instructions on how to use my GUI can be displayed. I have both horizontal and vertical scroll bars enabled for the memo. Both scroll bars show as the instructions are detailed and exceed the frame both vertically and horizontally. However, the vertical scroll bar is always at the bottom, and so unless the user scrolls up, he will miss the instructions at the beginning. The horizontal scroll bar is correctly at the left. Is there some property of the memo that I need to change in the Object Inspector to ensure that the vertical bar is on top so that the user can read the instructions from the beginning?

I have saved the instructions as a string called Instructions. I copy this string into the memo by means of the following code:

procedure TfrmHSR.mmuDispInstructionsClick(Sender: TObject);
//display instructions in Memo field
var
  DispText: string;
begin
  Memo1.Clear;
  DispText := Wraptext(Instructions, 125);
  Memo1.Lines.Add(DispText);
  mmuDisplayClear.Enabled := True;
  mmuFileSave.Enabled := False;
  //if input file created and opened, allow display of input data
  if Assigned(InputFile) then //if TStringList has been created
    begin
      if (InputFile.Count > 0) then mmuDispData.Enabled := True
      else mmuDispData.Enabled := False;
    end
  else mmuDispData.Enabled := False;

  //if results available, allow display
  if Assigned(OutputFile) then //if TStringList has been created
    begin
      if (OutputFile.Count > 0) then mmuDispResults.Enabled := True
      else mmuDispResults.Enabled := False;
    end
  else mmuDispResults.Enabled := False;
  mmuDispInstructions.Enabled := False;
end;
Serge
  • 199
  • 4
  • 17
  • How are you filling the memo? I can't replicate this in a quick test app. – Ken White Dec 09 '13 at 01:09
  • If you're going to keep the caret on the upper left corner, then I don't think there's an explicit setting to do so. However, you can call this `Memo.Perform(EM_SETSEL, 0, 0); Memo.Perform(EM_SCROLLCARET, 0, 0);` after you add the lines. – TLama Dec 09 '13 at 01:09
  • I have saved the instructions as a long string (with plenty of line breaks) that is displayed when the user clicks on the "Display Instructions" sub-menu option, which is part of a "Display" menu (other things can be displayed in the menu field as well, such as the uploaded data). – Serge Dec 09 '13 at 01:16
  • So you have used `Lines.Add` method for adding those lines ? Could you edit your question and add this information there, please ? – TLama Dec 09 '13 at 01:21
  • Sorry about that. i have now included my code in the main question. – Serge Dec 09 '13 at 01:22
  • I can't replicate this, using `var TempStr: string; i: Integer; begin TempStr := ''; for i := 0 to 100 - 1 do TempStr := TempStr + Format('This is line %d.'#13#10, [i]); Memo1.Text := TempStr;` in a new form's constructor. The memo correctly displays, with the memo positioned at the top row, correctly at the left side. Also, you don't need `WrapText` if you set `WordWrap` to true, and don't need `Memo1.Clear` if you just assign to `Text` instead of using `Add`. – Ken White Dec 09 '13 at 01:22
  • @Ken, you need to use `Lines.Add` without `Lines.BeginUpdate` and `Lines.BeginUpdate` lock not from form's constructor to reproduce. – TLama Dec 09 '13 at 01:25
  • @TLama: If you're adding the text in a single assignment to `Text` (because `Add` isn't needed here, nor is `Clear`), it works fine. – Ken White Dec 09 '13 at 01:26
  • @Ken, I know. The same as if you add more lines and use the update lock. But if you need (for some reason) add lines not so frequently (so without update lock), you will see the memo automatically scrolling to bottom. And although the code in the question has a simple solution, the case I described could have been a good topic. – TLama Dec 09 '13 at 01:32
  • @user1505202: No; I can change the code to use two loops, make a single block of text (with no line breaks) of 37,260 chars), and still end up in the top left corner. – Ken White Dec 09 '13 at 01:33
  • @Ken, thanks for the tips re `WordWrap` and assigning v adding. Fewer lines of code too. But I am using 'Lines.Add' so I guess I will need to clear the memo. – Serge Dec 09 '13 at 01:34
  • @TLama, tried `Memo.Perform(EM_SETSEL, 0, 0); Memo.Perform(EM_SCROLLCARET, 0, 0);`, but the vertical scroll bar is still at the bottom. – Serge Dec 09 '13 at 01:37
  • @user1505202: :-) You missed what I said. If you **don't** use `Add`, but just assign to `Memo1.Text` instead, you need neither `Clear` or `Add`. As you're replacing the entire content at once, there's no point in clearing the text and then adding it again; just assign it all directly. – Ken White Dec 09 '13 at 01:39
  • 2
    @user1505202, here is [`the code`](http://pastebin.com/ywruPxrX) where I've reproduced your problem and where I've been able to move the caret to top. (however, as Ken said, in that code using `Clear` and `Add` is useless; coincidentally I [`pointed this`](http://stackoverflow.com/questions/20452314/how-to-get-sum-of-a-certain-column-from-oracle-8i-database#comment30557639_20452314) out today). – TLama Dec 09 '13 at 01:44
  • 2
    Thanks, @TLama. It worked. When I first tried it, I had put the lines before the `Line.Add' statement, assuming that I need to first set the scroll bar before I add anything. But I see that you added the two lines after. I did too, and it worked. Thanks much. And thanks to Ken too for the tips. – Serge Dec 09 '13 at 01:58
  • @TLama: You should post your code here as an answer, so it can be accepted and the question marked as answered. – Ken White Dec 09 '13 at 06:30
  • @Ken, but the right answer here is to assign that string by using `Text` as you suggested, so it's your answer ;-) My code would be useful in case I described in [`this comment`](http://stackoverflow.com/questions/20461337/vertical-scroll-bar-always-at-the-bottom-delphi#comment30574608_20461337). – TLama Dec 09 '13 at 08:10

2 Answers2

2

The simplest fix for the problem you're having is simply to not use TMemo.Clear and TMemo.Add, but to directly assign to the Text property as a whole. (You have all the text at once, so there's no need to use Add at all.)

I've taken the liberty of making some other minor changes to your code that you might also find helpful; IMO it's a little clearer to read, and somewhat shorter. :-)

//display instructions in Memo field
procedure TfrmHSR.mmuDispInstructionsClick(Sender: TObject);
var
  DispText: string;
begin
  DispText := Wraptext(Instructions, 125);
  Memo1.Text := DispText;

  mmuDisplayClear.Enabled := True;
  mmuFileSave.Enabled := False;

  //if input file created and opened, allow display of input data
  //if TStringList has been created
  mnuDispData.Enabled := Assigned(InputFile) and (InputFile.Count > 0); 

  //if results available, allow display
  //if TStringList has been created
  mnuDispResults.Enabled := Assigned(OutputFile) and (OutputCount > 0);
  mmuDispInstructions.Enabled := False;
end;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • thanks. Your code is certainly shorter. I also did not know that you could use assign in this manner: `mnuDispData.Enabled := Assigned(InputFile) and (InputFile.Count > 0); mmuDispInstructions.Enabled := False;` instead of the more cumbersome `if` statement: ` if Assigned(InputFile) then begin if (InputFile.Count > 0) then mmuDispData.Enabled := True else mmuDispData.Enabled := False; end else mmuDispData.Enabled := False;`. But what happens if the Input file has not been created and/or nothing has been added to the OutputFile? What does the assignment do? – Serge Dec 09 '13 at 17:08
  • 1
    By default, Delphi does short-circuit Boolean evaluations, which means if the first condition is false, the second never executes. So in the case of `InputFile`, for instance, if `Assigned` returns false, the `InputFile.Count - 1` never executes. If `Assigned` is true, then the `InputFile.Count` is evaluated. If *both* are true, then the entire expression returns true (because of the `and`), and that is assigned to the `Enabled` property of the menu item. If *either* returns false, then the entire expression returns `false` (again, because of the `and`), and that is assigned to `Enabled`. – Ken White Dec 09 '13 at 17:13
  • Nice. Thanks for the explanation. I hope to think like a programmer some day! :0( But this forum has been an immense help! Thanks again. – Serge Dec 09 '13 at 17:38
0
//if you need to add the lines using lines.add() you can use this...

    procedure TForm2.Button1Click(Sender: TObject);
    var
      I: Integer;
     begin        
       for I := 0 to 1000 do
        begin
         memo1.Lines.add('line ' + intToStr(i));
        end;
    SendMessage(Memo1.Handle, EM_SETSEL, 0, 0);
    SendMessage(Memo1.Handle, EM_SCROLLCARET, 0, 0);        
    end;
Pericles
  • 120
  • 7