简体   繁体   English

Delphi:通过滚动滚动在TRichEdit中的中心特定线

[英]Delphi: Center Specific Line in TRichEdit by Scrolling

I have a Delphi 2007 TRichEdit with several lines in it. 我有一个带有多行代码的Delphi 2007 TRichEdit。 I want to scroll the richedit vertically such that a specific line number if approximately centered in the visible/display area of the richedit. 我想垂直滚动RichEdit,以使特定的行号大约位于RichEdit的可见/显示区域的中心。 For example, I want to write the code for CenterLineInRichEdit in this example: 例如,在此示例中,我想编写CenterLineInRichEdit的代码:

procedure CenterLineInRichEdit(Edit: TRichEdit; LineNum: Integer);
begin
  ...
  Edit.ScrollTo(...);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  REdit: TRichEdit;
  i: Integer;
begin
  REdit := TRichEdit.Create(Self);
  REdit.Parent := Self;
  Redit.ScrollBars := ssVertical;
  REdit.SetBounds(10, 10, 200, 150);
  for i := 1 to 25 do
    REdit.Lines.Add('This is line number ' + IntToStr(i));
  CenterLineInRichEdit(REdit, 13);
end;

I looked into using the WM_VSCROLL message, and it allows scrolling up/down one line, etc. but not scrolling to center a specific line. 我调查了使用WM_VSCROLL消息的情况,该消息允许向上/向下滚动一行,等等,但是不允许滚动以使特定行居中。

Send an EM_LINESCROLL message to the RichEdit: 发送EM_LINESCROLL消息到RichEdit:

SendMessage(REdit.Handle, EM_LINESCROLL, 0, NumberOfVerticalLinesToScroll);

See the EM_LINESCROLL MSDN topic . 请参阅EM_LINESCROLL MSDN主题

Based on the ideas here, I came up with one solution. 根据这里的想法,我提出了一个解决方案。 It assumes that all the lines in the richedit are the same height and that the richedit's default font correctly reports its height, but it might be useful to some people: 它假定richedit中的所有行都具有相同的高度,并且richedit的默认字体正确报告了其高度,但是它对某些人可能有用:

type
  TCustomEditHack = class(TCustomEdit);

procedure CenterLineInEdit(Edit: TCustomEdit; LineNum: Integer);
var
  VisibleLines: Integer;
  TopLine: Integer;
  FirstLine: Integer;
begin
  FirstLine := Edit.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
  VisibleLines := Round(Edit.ClientHeight / Abs(TCustomEditHack(Edit).Font.Height));

  if VisibleLines <= 1 then
    TopLine := LineNum
  else
    TopLine := Max(LineNum - Round((VisibleLines/2)) + 1, 0);

  if FirstLine <> TopLine then
    Edit.Perform(EM_LINESCROLL, 0, TopLine - FirstLine);
end;

I tested this with TRichEdit, but it might work for TMemo as well. 我使用TRichEdit进行了测试,但它可能也适用于TMemo。

You will need to use a couple of Windows messages to manipulate this aspect of your control in a generic fashion: 您将需要使用几个Windows消息以通用方式来控制控件的这一方面:

You will need to calculate how many lines to scroll up/down from the current top-line to bring a desired absolute line number into view, but you will have to calculate the number of lines visible in the control yourself (using font metrics and control height). 您将需要计算从当前顶行向上/向下滚动多少行才能看到所需的绝对行号,但是您必须自己计算控件中可见的行数(使用字体指标和控件高度)。

Note that with a RichEdit control the height of each line may vary according to fonts applied to the text in the control so any approach based on line numbers alone is likely to be only approximately accurate. 请注意,使用RichEdit控件时,每行的高度可能会根据控件中应用于文本的字体而有所不同,因此仅基于行号的任何方法都可能仅近似准确。 Also I'm not sure that it's possible to determine the current visible range of the control (ie the number of lines currently visible) directly, so calculating it yourself is necessary. 另外,我不确定是否可以直接确定控件的当前可见范围(即当前可见的行数),因此需要自己计算。

From memory, the SynEdit control offers some additional control over such things, providing both a read/write TopLine property as well as a LinesInWindow property. 从内存中, SynEdit控件对这些事情提供了一些附加控件,同时提供了读/写TopLine属性以及LinesInWindow属性。 However, I think SynEdit is not rich text capable, but if this is not actually a concern in your application (ie you can use a consistent font for all lines in the content) then it may be an attractive or suitable alternative. 但是,我认为SynEdit不支持富文本格式,但是如果您的应用程序中实际上并不关心此问题(即,您可以对内容中的所有行使用一致的字体),那么它可能是一种有吸引力的方法或合适的选择。

Give this a try; 试试看;

procedure VertCenterLine(RichEdit: TRichEdit; LineNum: Integer);
// I don't know the reason but the RichEdit 2 control in VCL does not
// respond to the EM_SCROLLCARET in Richedit.h but it does so to the
// constant in WinUser.h
const
  EM_SCROLLCARET  = $00B7;
var
  TextPos: lResult;
  Pos: TSmallPoint;
begin
  TextPos := SendMessage(RichEdit.Handle, EM_LINEINDEX, LineNum, 0);

  if TextPos <> -1 then begin
    // Go to top
    SendMessage(RichEdit.Handle, EM_SETSEL, 0, 0);
    SendMessage(RichEdit.Handle, EM_SCROLLCARET, 0, 0);

    // Get the coordinates for the beginning of the line
    Longint(Pos) := SendMessage(RichEdit.Handle, EM_POSFROMCHAR, TextPos, 0);

    // Scroll from the top
    SendMessage(RichEdit.Handle, WM_VSCROLL,
        MakeWParam(SB_THUMBPOSITION, Pos.y - RichEdit.ClientHeight div 2), 0);

    // Optionally set the caret to the beginning of the line
    SendMessage(RichEdit.Handle, EM_SETSEL, TextPos, TextPos);
  end;
end;

The below is an alternative in that it centers the first occurance of a string instead of a line number; 下面是一种替代方法,它以字符串的首次出现而不是行号为中心;

procedure VertCenterText(RichEdit: TRichEdit; Text: string);
const
  EM_SCROLLCARET  = $00B7;
var
  FindText: TFindText;
  TextPos: lResult;
  Pos: TSmallPoint;
begin
  FindText.chrg.cpMin := 0;
  FindText.chrg.cpMax := -1;
  FindText.lpstrText := PChar(Text);
  TextPos := SendMessage(RichEdit.Handle, EM_FINDTEXT,
      FR_DOWN or FR_WHOLEWORD, Longint(@FindText));

  if TextPos <> -1 then begin
    SendMessage(RichEdit.Handle, EM_SETSEL, 0, 0);
    SendMessage(RichEdit.Handle, EM_SCROLLCARET, 0, 0);

    Longint(Pos) := SendMessage(RichEdit.Handle, EM_POSFROMCHAR, TextPos, 0);
    SendMessage(RichEdit.Handle, WM_VSCROLL,
        MakeWParam(SB_THUMBPOSITION, Pos.y - RichEdit.ClientHeight div 2), 0);

    SendMessage(RichEdit.Handle, EM_SETSEL, TextPos, TextPos);
  end;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM