Wednesday 5 September 2007

Scrolling to the right place in TMemo

After programmatically inserting a number of lines I find that the TMemo will show at the last line as it normally should. However, sometimes what I really want is for TMemo to move to the first line, or perhaps even move to the top of the last lot of inserted lines.

Here are a few little gems I have picked up on my travels that work a treat.

Move to a particular line in a Memo...

function MemoMoveToLine(LineNo: integer);
begin
with Memo1 do
begin
SelStart := Perform(EM_LINEINDEX, LineNo, 0);
Perform(EM_SCROLLCARET, LinePos, 0);
end;
end;


What this is doing is calling TControl.Perform. This function actually uses a Windows message to perform some task, in this case placing the cursor at a line number, and then scrolling to that line. Let's take a look at using that function...

var
CurrentLine: integer;
begin
// Get the current position
with Memo1 do
CurrentLine := Perform(EM_LINEFROMCHAR, SelStart, 0);
AddLotsOfLines;
// Move to the line we started at
MemoMoveToLine(CurrentLine);
// Now Move to the end
MemoMoveToLine(Memo1.Lines.Count);
// Now Move to the start
MemoMoveToLine(0);
end;


And there you have it.

Ramblings


I'm well into packing in the evenings now and surrounded by boxes. The truck comes in 2 days to take my furniture to Hamilton where I will start work as Software Development Manager about mid month. I look forward to the challange of a new team and new company.

Although it will be hard to leave the Mount and this wonderful job and environment, my family is still at the Mount so I'll be back often. Its only a short drive to get here to see them and I'll probably end up with a caravan here for easy weekend stays.

I'll still be codeing in Delphi for my own programs (and sanity), always have, always will. This will ensure that the blog stays relative to Delphi, although I might introduce a few new subjects as well.

6 comments:

  1. Just one thing - Perform() uses a Windows message, but it doesn't use messagING. Calling Perform() results in a direct call to the underlying WindowProc to process a message immediately. There's no posting or sending involved.

    i.e. you bypass the message queue.

    I don't think it's a significant distinction in this case, but it is an important distinction, never-the-less.

    ReplyDelete
  2. Thanks Jolyon, always bang on in good feedback. Much appreciated, I'll update my post.

    Steve

    ReplyDelete
  3. Excellent tips Steve,
    As it happens I currently have a project meandering about in my head that is still "percolating" and it will involve quite a bit of work with TMemo [in it's current manifestation] ... these tips will certainly come in handy.

    Many thanks,

    ReplyDelete
  4. Hi Steve,
    I think function MemoMoveToLine needs result type, or I am wrong?

    ReplyDelete
  5. Hi again TDelphiHobbyist, and thanks for your nice comments.

    Issam Ali - Not necessary in this situation. However, I suppose you should check to see if LineNo is greater than Memo1.Lines.Count.

    I haven't tested it out to see if it produces an exception if it is, but you could give it that value of Memo1.Lines.Count if it is greater.

    Well spotted.

    Steve

    ReplyDelete
  6. Or implemented as a class helper (so that you can use just Memo1.LineNo:=NewLineNo)

    TYPE
    TMemoHelper = CLASS HELPER FOR TMemo
    STRICT PRIVATE
    FUNCTION GetLineNo : INTEGER;
    PROCEDURE SetLineNo(NewLineNo : INTEGER);
    PUBLIC
    PROPERTY LineNo : INTEGER Read GetLineNo Write SetLineNo;
    END;

    FUNCTION TMemoHelper.GetLineNo : INTEGER;
    BEGIN
    Result:=Perform(EM_LINEFROMCHAR,SelStart,0)
    END;

    PROCEDURE TMemoHelper.SetLineNo(NewLineNo : INTEGER);
    BEGIN
    IF NewLineNo<0 THEN NewLineNo:=0;
    IF NewLineNo>Lines.Count THEN NewLineNo:=Lines.Count;
    SelStart:=Perform(EM_LINEINDEX,NewLineNo,0);
    Perform(EM_SCROLLCARET,LinePos,0); // Should LinePos be NewLineNo ?? //
    END;

    ReplyDelete

Note: only a member of this blog may post a comment.