【问题标题】:Adding true hyperlink support to TRichEdit向 TR​​ichEdit 添加真正的超链接支持
【发布时间】:2017-03-01 13:13:17
【问题描述】:

我需要支持 TRichEdit 中的“友好名称超链接”,我发现的所有解决方案都基于 autoURLs (EM_AUTOURLDETECT),它通过检测用户输入的以 www(或 http)开头的字符串来工作。

但我想将链接放在不以 www 开头的字符串上。示例:'Download'。

【问题讨论】:

  • 您的链接使用 TRichEdit 的本机属性,但我认为您需要做一些比这更复杂的事情。您可能使用了错误的控件来满足您的需求,但是很难用如此少的信息说。 TRichEdit 被设计为文本编辑器,因此,例如,用户如何在这样的控件中键入您需要的不可见文本位?如果它是只读的,那么您可能需要某种 HTML 查看器而不是 TRichEdit
  • 对于旧版本,您必须使用文章中描述的技术
  • 我想念一些东西。文章说我使用 ITextRange.Expand 来阅读现有链接。但是如何创建新链接(我最初的问题)或如何修改现有链接?
  • @NAZCA:实际上是这样,通过展示如何对超链接点击做出反应并启动点击的 URL。创建友好名称超链接只是如何格式化CFE_LINK 文本,然后在EN_LINK 通知中解析。有关示例,请参阅下面对我的答案的更新。

标签: html delphi browser delphi-xe7 trichedit


【解决方案1】:

您需要执行以下操作:

  1. 向 RichEdit 发送 EM_SETEVENTMASK 消息以启用 ENM_LINK 标志。创建 RichEdit 后执行一次,然后每次 RichEdit 收到 CM_RECREATEWND 消息时再执行一次。

  2. 选择要转换为链接的所需文本。您可以使用 RichEdit 的 SelStartSelLength 属性,或向 RichEdit 发送 EM_SETSELEM_EXSETSEL 消息。无论哪种方式,然后向 RichEdit 发送带有 CHARFORMAT2 结构的 EM_SETCHARFORMAT 消息,以对所选文本启用 CFE_LINK 效果。

  3. 继承 RichEdit 的 WindowProc 属性以处理 CN_NOTIFY(EN_LINK)CM_RECREATEWND 消息。当收到EN_LINK 时,您可以使用ShellExecute/Ex() 启动所需的URL。

例如:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    PrevRichEditWndProc: TWndMethod;
    procedure InsertHyperLink(const HyperlinkText: string);
    procedure SetRichEditMasks;
    procedure RichEditWndProc(var Message: TMessage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Winapi.RichEdit, Winapi.ShellAPI;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PrevRichEditWndProc := RichEdit1.WindowProc;
  RichEdit1.WindowProc := RichEditWndProc;

  SetRichEditMasks;

  RichEdit1.Text := 'Would you like to Download Now?';

  RichEdit1.SelStart := 18;
  RichEdit1.SelLength := 12;    
  InsertHyperLink('Download Now');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  InsertHyperLink('Another Link');
end;

procedure TForm1.InsertHyperLink(const HyperlinkText: string);
var
  Fmt: CHARFORMAT2;
  StartPos: Integer;
begin
  StartPos := RichEdit1.SelStart;
  RichEdit1.SelText := HyperlinkText;

  RichEdit1.SelStart := StartPos;
  RichEdit1.SelLength := Length(HyperlinkText);

  FillChar(Fmt, SizeOf(Fmt), 0);
  Fmt.cbSize := SizeOf(Fmt);
  Fmt.dwMask := CFM_LINK;
  Fmt.dwEffects := CFE_LINK;

  SendMessage(RichEdit1.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@Fmt));

  RichEdit1.SelStart := StartPos + Length(HyperlinkText);
  RichEdit1.SelLength := 0;
end;

procedure TForm1.SetRichEditMasks;
var
  Mask: DWORD;
begin
  Mask := SendMessage(RichEdit1.Handle, EM_GETEVENTMASK, 0, 0);
  SendMessage(RichEdit1.Handle, EM_SETEVENTMASK, 0, Mask or ENM_LINK);
  SendMessage(RichEdit1.Handle, EM_AUTOURLDETECT, 1, 0);
end;

procedure TForm1.RichEditWndProc(var Message: TMessage);
type
  PENLINK = ^ENLINK;
var
  tr: TEXTRANGE;
  str: string;
  p: PENLINK;
begin
  PrevRichEditWndProc(Message);

  case Message.Msg of
    CN_NOTIFY: begin
     if TWMNotify(Message).NMHdr.code = EN_LINK then
      begin
        P := PENLINK(Message.LParam);
        if p.msg = WM_LBUTTONUP then
        begin
          SetLength(str, p.chrg.cpMax - p.chrg.cpMin);
          tr.chrg := p.chrg;
          tr.lpstrText := PChar(str);
          SendMessage(RichEdit1.Handle, EM_GETTEXTRANGE, 0, LPARAM(@tr));

          if str = 'Download Now' then
          begin
            ShellExecute(Handle, nil, 'http://www.SomeSite.com/download', nil, nil, SW_SHOWDEFAULT);
          end
          else if str = 'Another Link' then
          begin
            // do something else
          end;
        end;
      end;
    end;

    CM_RECREATEWND: begin
      SetRichEditMasks;
    end;
  end;
end;

end.

更新:根据 MSDN:

RichEdit Friendly Name Hyperlinks

在 RichEdit 中,超链接字段实体由字符格式效果表示,这与用于构造数学对象的分隔符不同。因此,这些超链接不能嵌套,尽管在 RichEdit 5.0 和更高版本中它们可以彼此相邻。整个超链接有CFE_LINKCFE_LINKPROTECTED的字符格式化效果,而autoURLs只有CFE_LINK属性。前者包含CFE_LINKPROTECTED,以便自动URL 扫描程序跳过友好名称链接。指令部分,即 URL,也具有 CFE_HIDDEN 属性,因为它不应该显示。 URL 本身用 ASCII 双引号括起来,前面是字符串 “HYPERLINK “。由于CFE_HIDDEN 在友好名称超链接中起着不可或缺的作用,因此不能在名称中使用。

例如,在使用 RichEdit 的写字板中,名称为 MSN 的超链接将具有纯文本

HYPERLINK “http://www.msn.com”MSN

整个链接将具有CFE_LINKCFE_LINKPROTECTED 字符格式属性,并且除了MSN 之外的所有链接都将具有CFE_HIDDEN 属性。

这可以在代码中轻松模拟:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ...
  RichEdit1.Text := 'Would you like to Download Now?';

  RichEdit1.SelStart := 18;
  RichEdit1.SelLength := 12;    
  InsertHyperLink('Download Now', 'http://www.SomeSite.com/downloads');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  InsertHyperLink('A Text Link');
end;

procedure TForm1.InsertHyperLink(const HyperlinkText: string; const HyperlinkURL: string = '');
var
  HyperlinkPrefix, FullHyperlink: string;
  Fmt: CHARFORMAT2;
  StartPos: Integer;
begin
  if HyperlinkURL <> '' then
  begin
    HyperlinkPrefix := Format('HYPERLINK "%s"', [HyperlinkURL]);
    FullHyperlink := HyperlinkPrefix + HyperlinkText;
  end else begin
    FullHyperlink := HyperlinkText;
  end;

  StartPos := RichEdit1.SelStart;
  RichEdit1.SelText := FullHyperlink;

  RichEdit1.SelStart := StartPos;
  RichEdit1.SelLength := Length(FullHyperlink);

  FillChar(Fmt, SizeOf(Fmt), 0);
  Fmt.cbSize := SizeOf(Fmt);
  Fmt.dwMask := CFM_LINK;
  Fmt.dwEffects := CFE_LINK;
  if HyperlinkURL <> '' then
  begin
    // per MSDN: "RichEdit doesn’t allow the CFE_LINKPROTECTED attribute to be
    // set directly by programs. Maybe it will allow it someday after enough
    // testing is completed to ensure that things cannot go awry"...
    //
    {
    Fmt.dwMask := Fmt.dwMask or CFM_LINKPROTECTED;
    Fmt.dwEffects := Fmt.dwEffects or CFE_LINKPROTECTED;
    }
  end;

  SendMessage(RichEdit1.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@Fmt));

  if HyperlinkURL <> '' then
  begin
    RichEdit1.SelStart := StartPos;
    RichEdit1.SelLength := Length(HyperlinkPrefix);

    FillChar(Fmt, SizeOf(Fmt), 0);
    Fmt.cbSize := SizeOf(Fmt);
    Fmt.dwMask := CFM_HIDDEN;
    Fmt.dwEffects := CFE_HIDDEN;

    SendMessage(RichEdit1.Handle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@Fmt));
  end;

  RichEdit1.SelStart := StartPos + Length(FullHyperlink);
  RichEdit1.SelLength := 0;
end;

然后通过解析点击的超链接文本在EN_LINK通知中处理:

uses
  ..., System.StrUtils;

...

SendMessage(RichEdit1.Handle, EM_GETTEXTRANGE, 0, LPARAM(@tr));

// Per MSDN: "The ENLINK notification structure contains a CHARRANGE with
// the start and end character positions of the actual URL (IRI, file path
// name, email address, etc.) that typically appears in a browser URL
// window. This doesn’t include the “HYPERLINK ” string nor the quotes in
// the hidden part. For the MSN link above, it identifies only the
// http://www.msn.com characters in the backing store."
//
// However, without the CFM_LINKPROTECTED flag, the CHARRANGE will report
// the positions of the entire "HYPERLINK ..." string instead, so just strip
// off what is not needed...
//
if StartsText('HYPERLINK "', str) then
begin
  Delete(str, 1, 11);
  Delete(str, Pos('"', str), MaxInt);
end;

if (str is a URL) then begin
  ShellExecute(Handle, nil, PChar(str), nil, nil, SW_SHOWDEFAULT);
end
else begin
  // do something else
end;

【讨论】:

  • @NAZCA:当然,如果您正在加载外部 RTF,例如文档文件。但是,如果您只是在代码中使用自己的文本填充 RichEdit,则很容易在需要的地方插入超链接,如上所示。如果您不想在代码中跟踪它们,那么将实际 URL 存储在文本中并不难。例如,通过在其超链接文本之后嵌入和隐藏 URL,然后相应地调整 EM_GETTEXTEX 范围。
  • @NAZCA:不,通过使用CFE_HIDDEN 效果,就像实际的友好名称超链接一样。我用一个例子更新了我的答案。
  • 注意:SetLength(str, chrg.cpMax - chrg.cpMin) - 仅当您在 RichEdit 中有一个链接时才有效。如果 RichEdit 中的文本比较复杂,则 'str' var 的长度需要与 RichEdit 中的字符数相等,包括隐藏字符。
  • @NAZCA:EN_LINK 提供的CHARRANGE 仅包含被点击链接的字符(请阅读文档!),因此chrg.cpMax - chrg.cpMin 是正确的String 长度。我的错误是使用EM_GETTEXTEX,我的意思是使用EM_GETTEXTRANGE。我现在已经更正了。
  • @NAZCA:但是,话虽如此,在 RichEdit 中拥有多个链接就可以正常工作(我对其进行了测试)。您的示例编码错误。您正在操作 RichEdit 的 Text 属性,该属性会丢失所有现有格式。要在现有文本中插入超链接,您需要改用SelText 属性,例如:RichEdit1.SelText := HyperLnkTxt;。如果有当前选择 (SelLength &gt; 0),新文本将替换选择。否则,新文本将简单地插入到当前插入符号位置。我更新了我的答案以显示这一点。
猜你喜欢
  • 1970-01-01
  • 2020-05-08
  • 2011-06-05
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-04
相关资源
最近更新 更多