【问题标题】:Delphi: Center Specific Line in TRichEdit by ScrollingDelphi:通过滚动在 TRichEdit 中居中特定行
【发布时间】:2010-05-12 20:20:59
【问题描述】:

我有一个 Delphi 2007 TRichEdit,里面有几行。我想垂直滚动richedit,以使特定的行号大约位于richedit的可见/显示区域的中心。比如我想在这个例子中写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;

我研究过使用 WM_VSCROLL 消息,它允许向上/向下滚动一行等,但不能滚动到特定行的中心。

【问题讨论】:

  • 要使行居中,您必须计算所需行的当前位置和 RichEdit 中心之间的行数。使用该差异作为垂直滚动的行数。

标签: delphi line center richedit trichedit


【解决方案1】:

基于这里的想法,我想出了一个解决方案。它假定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;

我用 TRichEdit 对此进行了测试,但它可能也适用于 TMemo。

【讨论】:

    【解决方案2】:

    向 RichEdit 发送 EM_LINESCROLL 消息:

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

    请参阅EM_LINESCROLL MSDN topic

    【讨论】:

    • 在上面的代码中,我需要改变什么才能让“x”能够向上滚动而不是向下滚动?
    • @petersmileyface: -NumberOfVerticalLinesToScroll 可能会起作用。 :-)
    【解决方案3】:

    试试这个;

    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;
    

    下面是另一种选择,因为它将第一次出现的字符串而不是行号居中;

    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;
    

    【讨论】:

    • 这个解决方案效果很好。我发现的唯一问题是当您尝试在编辑顶部聚焦一行时,Pos.y - RichEdit.ClientHeight div 2 可能为负数,这对于 Word 类型无效,因此我添加了 Max(0, . ..) 包裹着该声明,现在它工作正常。我还查看了此处的其他建议,并独立提出了另一种解决方案,该解决方案更简单一些,但它假定整个文档的行高恒定,并且字体正确报告其高度,因此不适用于大家。
    【解决方案4】:

    您将需要使用一些 Windows 消息以通用方式操作控件的这一方面:

    您需要计算从当前顶行向上/向下滚动多少行才能将所需的绝对行号显示在视图中,但您必须自己计算控件中可见的行数(使用字体指标和控制高度)。

    请注意,对于 RichEdit 控件,每行的高度可能会根据应用于控件中文本的字体而有所不同,因此任何仅基于行号的方法可能只是大致准确。另外我不确定是否可以直接确定控件当前的可见范围(即当前可见的行数),因此需要自己计算。

    根据记忆,SynEdit control 提供了一些额外的控制,提供了一个读/写 TopLine 属性以及一个 LinesInWindow 属性。但是,我认为 SynEdit 不支持富文本,但如果这实际上不是您的应用程序中的一个问题(即您可以为内容中的所有行使用一致的字体),那么它可能是一个有吸引力或合适的替代方案。

    【讨论】:

    • 我查看了您的想法并发布了与此类似的解决方案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-28
    • 1970-01-01
    相关资源
    最近更新 更多