【问题标题】:RichEdit does not process hyperlinksRichEdit 不处理超链接
【发布时间】:2016-10-13 22:28:37
【问题描述】:

我希望我的 RichEdit 处理超链接,所以我按照以下说明操作:http://delphi.about.com/od/vclusing/l/aa111803a.htm

以下是我对代码所做的更改:

interface

type
  TProgCorner = class(TForm)
    RichEdit2: TRichEdit;
    RichEdit1: TRichEdit;
    RichEdit3: TRichEdit;
    RichEdit4: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    procedure InitRichEditURLDetection(RE: TRichEdit);
  protected
    procedure WndProc(var Msg: TMessage); override;
  end;

implementation

{$R *.DFM}

uses
  ShellAPI, RichEdit;

const
  AURL_ENABLEURL = 1;
  AURL_ENABLEEAURLS = 8;

procedure TProgCorner.InitRichEditURLDetection(RE: TRichEdit);
var
  mask: LResult;
begin
  mask := SendMessage(RE.Handle, EM_GETEVENTMASK, 0, 0);
  //In the debugger mask is always 1, for all 4 Richedits.
  SendMessage(RE.Handle, EM_SETEVENTMASK, 0, mask or ENM_LINK); 
  //returns 67108865
  SendMessage(RE.Handle, EM_AUTOURLDETECT, AURL_ENABLEURL, 0);
  //Returns 0 = success (according to MSDN), but no joy.
  //SendMessage(RE.Handle, EM_AUTOURLDETECT, AURL_ENABLEEAURLS, 0); 
  //When uncommented returns -2147024809
  //I don't think the registration works, but don't know how to fix this.
end;

procedure TProgCorner.WndProc(var Msg: TMessage);
var
  p: TENLink;
  sURL: string;
  CE: TRichEdit;
begin
  //'normal' messages do get through here, but...
  if (Msg.Msg = WM_NOTIFY) then begin
    //...the following line is never reached.
    if (PNMHDR(Msg.lParam).code = EN_LINK) then begin
      p:= TENLink(Pointer(TWMNotify(Msg).NMHdr)^);
      if (p.Msg = WM_LBUTTONDOWN) then begin
        try
          CE:= TRichEdit(ProgCorner.ActiveControl);
          SendMessage(CE.Handle, EM_EXSETSEL, 0, LPARAM(@(p.chrg)));
          sURL:= CE.SelText;
          ShellExecute(Handle, 'open', PChar(sURL), 0, 0, SW_SHOWNORMAL);
        except
          {ignore}
        end;
      end;
    end;
  end;

 inherited;
end;

procedure TProgCorner.FormCreate(Sender: TObject);
begin
  InitRichEditURLDetection(RichEdit1);
  InitRichEditURLDetection(RichEdit2);
  InitRichEditURLDetection(RichEdit3);
  InitRichEditURLDetection(RichEdit4);
  //If I set the text here (and not in the object inspector) 
  //the richedit shows a hyperlink with the 'hand' cursor.
  //but still no WM_notify message gets received in WndProc.
  RichEdit1.Text:= 'http://www.example.com';

end;

end.

但是,我使用对象检查器嵌入到我的RichEditx.Lines 中的超链接显示为纯文本(不是链接)并且单击它们不起作用。

我正在使用在 Win32 模式下在 Windows 7 上运行的 Delphi Seattle。

我做错了什么?

更新
结合使用已弃用的
SendMessage(RE.Handle, EM_AUTOURLDETECT, AURL_ENABLEURL, 0); 和在 FormCreate 中手动设置 RichEditx.Text:= 'http://www.example.com',我可以让 Richedit 显示超链接和手形光标。
但是,WndProc 仍然没有收到 WM_Notify 消息。
WndProc 确实接收其他消息。

更新2
为了简化问题,我忽略了RichEdit 位于Panel 之上的事实。面板吃掉WM_Notify 消息,因此它们不会到达下方的表单。

【问题讨论】:

  • mask 应声明为LResult
  • SendMessage(CE.Handle, EM_EXSETSEL, 0, Longint(@(p.chrg))); 应该是SendMessage(CE.Handle, EM_EXSETSEL, 0, LPARAM(@(p.chrg)));
  • @LURD,已更新,但这是 win32,所以这不是问题,反正我认为 WM_Notify 被拦截了,确实是这样。
  • @Johan:你的分析是错误的。如果表单是 RichEdit 的直接 Parent,则使用 RichEdit 的 WM_NOTIFY 消息调用表单的 WndProc() DOES。您没有考虑到的是,VCL 有时可能必须重新创建 RichEdit 的 HWND,从而丢失您的 EM_SETEVENTMASKEM_AUTOURLDETECT 设置。每次重新创建 RichEdit 的 HWND 时,您都必须再次发出这些消息。覆盖 RichEdit 的 CreateWnd() 方法来处理它。是的,在TRichEdit 内拦截CN_NOTIFY 是最好的,但这不是唯一的选择。

标签: delphi hyperlink richedit


【解决方案1】:

问题是 WM_Notify 消息永远不会到达主窗体。
相反,它被 Richedit 的父级截获(我放置在其中的面板用于对齐目的)。
我错误地在问题中忽略了这个事实,认为这并不重要。
那就是说以下对我有用。

但是,我强烈支持 Remy 在架构上更合理的方法,遇到这个问题的人应该首先尝试这种方法。

在 VCL.ComCtrls 中

  TCustomRichEdit = class(TCustomMemo)
  private  //Why private !?
    procedure CNNotify(var Message: TWMNotifyRE); message CN_NOTIFY;

解决办法是插入我们自己的TRichEdit:

uses   
  ...., RichEdit;

type
  TRichEdit = class(ComCtrls.TRichEdit)
    procedure CNNotify(var Message: TWMNotifyRE); message CN_NOTIFY;
  end;  //never mind that its ancester is private, it will still work.

  TProgCorner = class(TForm)

我将 RichRdits 存储在一个数组中,因此我可以通过它们的 HWnd 来查找它们,而无需遍历我表单的所有子控件。

implementation

function TProgCorner.RichEditByHandle(Handle: HWnd): TRichEdit;
var
  i: integer;
begin
  //Keep track of the richedits in an array, initialized on creation.
  for i:= Low(RichEdits) to High(RichEdits) do begin
    if RichEdits[i].Handle = Handle then exit(RichEdits[i]);
  end;
  Result:= nil;
end;

procedure TRichEdit.CNNotify(var Message: TWMNotifyRE);
var
  p: TENLink;
  sURL: string;
  CE: TRichEdit;
begin
  if (Message.NMHdr.code = EN_LINK) then begin
    p:= TENLink(Pointer(TWMNotify(Message).NMHdr)^);
    if (p.Msg = WM_LBUTTONDOWN) then begin
      try
        //CE:= TRichEdit(ProgCorner.ActiveControl);
        //SendMessage(CE.Handle, EM_EXSETSEL, 0, Longint(@(p.chrg)));
        SendMessage(p.nmhdr.hwndFrom, EM_EXSETSEL, 0, Longint(@(p.chrg)));
        CE:= ProgCorner.RichEditByHandle(p.nmhdr.hwndFrom);
        if assigned(CE) then begin
          sURL:= CE.SelText;
          ShellExecute(Handle, 'open', PChar(sURL), 0, 0, SW_SHOWNORMAL);
        end;
      except
        {ignore}
      end;
    end;
  end;
  inherited;
end;

幸运的是,即使原始消息被声明为私有,消息处理程序的插入仍然有效。

现在可以了。像一个魅力。

以下是该单元的完整副本以供将来参考:

unit ProgCorn;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, Menus, Clipbrd, LifeConst, Tabnotbk, LifeUtil,
  MyLinkLabel, RichEdit;

type
  TRichEdit = class(ComCtrls.TRichEdit)
    procedure CNNotify(var Message: TWMNotifyRE); message CN_NOTIFY;
  end;


  TProgCorner = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Label1: TLabel;
    TabbedNotebook1: TTabbedNotebook;
    PopupMenu1: TPopupMenu;
    Copy1: TMenuItem;
    Panel3: TPanel;
    Button1: TButton;
    RichEdit1: TRichEdit;
    RichEdit2: TRichEdit;
    RichEdit3: TRichEdit;
    RichEdit4: TRichEdit;
    Button2: TButton;
    procedure Copy1Click(Sender: TObject);
    procedure PopupMenu1Popup(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    RichEdits: array[1..4] of TRichEdit;
    procedure InitRichEditURLDetection(RE: TRichEdit);
    function RichEditByHandle(Handle: HWnd): TRichEdit;
  public
    { Public declarations }
  end;

var
  ProgCorner: TProgCorner;


implementation

{$R *.DFM}

uses
  ShellAPI;

const
  AURL_ENABLEEAURLS = 8;
  AURL_ENABLEURL = 1;

procedure TProgCorner.InitRichEditURLDetection(RE: TRichEdit);
var
  mask: NativeInt;
begin
  mask := SendMessage(RE.Handle, EM_GETEVENTMASK, 0, 0);
  SendMessage(RE.Handle, EM_SETEVENTMASK, 0, mask or ENM_LINK);
  SendMessage(RE.Handle, EM_AUTOURLDETECT, {AURL_ENABLEEAURLS} AURL_ENABLEURL, 0);
end;



procedure TProgCorner.FormCreate(Sender: TObject);
begin
  ProgCorner:= Self;
  InitRichEditURLDetection(RichEdit1);
  InitRichEditURLDetection(RichEdit2);
  InitRichEditURLDetection(RichEdit3);
  InitRichEditURLDetection(RichEdit4);
  RichEdits[1]:= RichEdit1;
  RichEdits[2]:= RichEdit2;
  RichEdits[3]:= RichEdit3;
  RichEdits[4]:= RichEdit4;

  //WordWarp should be set during runtime only, because
  //otherwise the text will not warp, but rather be cut off
  //before run time.
  RichEdit1.Text:= RichEdit1.Text + ' ';
  RichEdit2.Text:= RichEdit2.Text + ' ';
  RichEdit3.Text:= RichEdit3.Text + ' ';
  RichEdit4.Text:= RichEdit4.Text + ' ';
  RichEdit1.WordWrap:= true;
  RichEdit2.WordWrap:= true;
  RichEdit3.WordWrap:= true;
  RichEdit4.WordWrap:= true;
end;

procedure TProgCorner.Copy1Click(Sender: TObject);
var
  ActiveRichEdit: TRichEdit;
begin
  ActiveRichEdit:= TRichEdit(Self.FindComponent('RichEdit'+
    IntToStr(TabbedNotebook1.PageIndex+1)));
  with ActiveRichEdit do begin
    if SelText <> '' then Clipboard.AsText:= SelText
    else ClipBoard.AsText:= Lines.Text;
  end; {with}
end;

procedure TProgCorner.PopupMenu1Popup(Sender: TObject);
begin
  Copy1.Enabled:= true;
end;


procedure TProgCorner.Button2Click(Sender: TObject);
begin
  Application.HelpContext(4);
end;

{ TRichEdit }

function TProgCorner.RichEditByHandle(Handle: HWnd): TRichEdit;
var
  i: integer;
begin
  for i:= Low(RichEdits) to High(RichEdits) do begin
    if RichEdits[i].Handle = Handle then exit(RichEdits[i]);
  end;
  Result:= nil;
end;

procedure TRichEdit.CNNotify(var Message: TWMNotifyRE);
var
  p: TENLink;
  sURL: string;
  CE: TRichEdit;
begin
  //if (Message.Msg = WM_NOTIFY) then begin
    if (Message.NMHdr.code = EN_LINK) then begin
      p:= TENLink(Pointer(TWMNotify(Message).NMHdr)^);
      if (p.Msg = WM_LBUTTONDOWN) then begin
        try
          //CE:= TRichEdit(ProgCorner.ActiveControl);
          //SendMessage(CE.Handle, EM_EXSETSEL, 0, Longint(@(p.chrg)));
          SendMessage(p.nmhdr.hwndFrom, EM_EXSETSEL, 0, Longint(@(p.chrg)));
          CE:= ProgCorner.RichEditByHandle(p.nmhdr.hwndFrom);
          if assigned(CE) then begin
            sURL:= CE.SelText;
            ShellExecute(Handle, 'open', PChar(sURL), 0, 0, SW_SHOWNORMAL);
          end;
        except
          {ignore}
        end;
      end;
    end;
  //end;
  inherited;
end;

end.

【讨论】:

  • "问题在于 WM_Notify 消息永远不会到达主窗体" - 是的,确实如此,如果主窗体是 立即的 RichEdit 的父级。 “相反,它会被 RichEdit 的 CNNotify 消息拦截”——这就是它最终结束的地方,但它会首先出现在 RichEdit 父级的 WndProc() 中,然后向下传递到TControl.Dispatch(),它将传递给TWinControl.WMNotify(),后者将传递给RichEdit的Perform(),然后向下传递到TCustomRichEdit.CNNotify()
  • "我将 RichEdits 存储在一个数组中,这样我就可以通过它们的 HWnd 来查找它们,而不必遍历我表单的所有子控件" - 有更简单的方法。 VCL 提供了TWinControl 与其HWND 之间的链接。 WM_NOTIFY 包含发送它的控件的HWND。在WM_NOTIFY 处理程序中,您可以将HWND 传递给Controls 单元中的FindControl() 函数,然后将返回的TWinControl 类型转换为TRichEdit。但在这种情况下这是多余的,因为在CNNotify() 内部,您可以访问正在处理它的TRichEditSelf 指针。
  • 您没有处理 VCL 可能重新创建 RichEdit 的HWND 的情况。每次 RichEdit 为自己创建一个新的 HWND 时,您的插入器需要重写 CreateWnd() 方法来发出您的 EM_SETEVENTMASKEM_AUTOURLDETECT 消息。完全摆脱TProgCorner.InitRichEditURLDetection()
  • @RemyLebeau。我尝试覆盖CreateWnd(根本不起作用)。我还尝试覆盖CreateWindowHandle,这让Richedit 显示链接,但随后它破坏了CN_Notify 事件,即CN_Notify 被各种东西调用,但不是EN_Link 消息。我在架构上同意你的观点,但没有时间全面调查这个问题,所以我会满足于我现在所拥有的。
  • 您所拥有的是不完整,最终可能会损坏。您需要处理窗口重新创建。正确完成覆盖 CreateWnd 即可。
【解决方案2】:

您问题中显示的代码按原样非常适合我。尽管您提出了要求,但表单的 WndProc() 确实会收到 EN_LINK 通知并按预期启动点击的 URL。

但是,如果您将 RichEdit 放在另一个父控件上,例如 TPanel,则表单将不再收到 WM_NOTIFY 消息。父控件将接收它们,因此您必须改为子类化该父控件。

话虽如此,可以对所示代码进行一些改进:

  1. 在你的EN_LINK 处理中,你可以替换这个:

    CE := TRichEdit(ProgCorner.ActiveControl);
    

    用这个代替:

    CE := TRichEdit(FindControl(TWMNotify(Msg).NMHdr.hwndFrom));
    

    通知告诉您发送它的 RichEdit 控件的 HWND,并且 VCL 知道如何从 HWND 检索 TWinControl

  2. 使用EM_GETTEXTRANGE 来检索点击的URL,而不是使用EM_EXSETSELSelText(这是EM_EXGETSELEM_GETTEXTEX 的组合)。这样,您就可以使用更少的消息,并且根本不必操作 RichEdit 的选定文本。通知会告诉您 URL 的确切字符范围,因此您可以直接抓取这些字符。

  3. 您需要处理HWND 娱乐。 VCL 可能随时重新创建 RichEdit 的 HWND。每次创建新的HWND 时,您都必须再次发送EM_SETEVENTMASKEM_AUTOURLDETECT 消息,否则您将失去自动检测功能。处理此问题的最佳方法是从 TRichEdit 派生一个类并覆盖其 CreateWnd() 方法。

  4. 1234563 VCL 知道如何将WM_NOTIFY 消息重定向到发送它的VCL 控件。这允许 VCL 控件处理自己的通知。因此,无论 RichEdit 放置在哪个父控件上,您的 EN_LINK 处理程序都可以工作,您根本不必继承/覆盖父控件的 WndProc(),并且您可以使用 RichEdit 的 Self 指针在访问 RichEdit 的成员时正在处理消息,例如其 Handle 属性。

综上所述,以下代码对我有用:

unit RichEditUrlTest;

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
  TRichEdit = class(Vcl.ComCtrls.TRichEdit)
  private
    procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  protected
    procedure CreateWnd; override;
  end;

  TProgCorner = class(TForm)
    RichEdit2: TRichEdit;
    RichEdit1: TRichEdit;
    RichEdit3: TRichEdit;
    RichEdit4: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  ProgCorner: TProgCorner;

implementation

{$R *.dfm}

uses
  Winapi.ShellAPI, Winapi.RichEdit;

const
  AURL_ENABLEURL = 1;
  AURL_ENABLEEAURLS = 8;

procedure TRichEdit.CreateWnd;
var
  mask: LResult;
begin
  inherited;
  mask := SendMessage(Handle, EM_GETEVENTMASK, 0, 0);
  SendMessage(Handle, EM_SETEVENTMASK, 0, mask or ENM_LINK);
  SendMessage(Handle, EM_AUTOURLDETECT, AURL_ENABLEURL, 0);
end;

procedure TRichEdit.CNNotify(var Message: TWMNotify);
type
  PENLink = ^TENLink;
var
  p: PENLink;
  tr: TEXTRANGE;
  url: array of Char;
begin
  if (Message.NMHdr.code = EN_LINK) then begin
    p := PENLink(Message.NMHdr);
    if (p.Msg = WM_LBUTTONDOWN) then begin
      { optionally, enable this:
      if CheckWin32Version(6, 2) then begin
        // on Windows 8+, returning EN_LINK_DO_DEFAULT directs
        // the RichEdit to perform the default action...
        Message.Result :=  EN_LINK_DO_DEFAULT;
        Exit;
      end;
      }
      try
        SetLength(url, p.chrg.cpMax - p.chrg.cpMin + 1);
        tr.chrg := p.chrg;
        tr.lpstrText := PChar(url);
        SendMessage(Handle, EM_GETTEXTRANGE, 0, LPARAM(@tr));
        ShellExecute(Handle, nil, PChar(url), 0, 0, SW_SHOWNORMAL);
      except
        {ignore}
      end;
      Exit;
    end;
  end;
  inherited;
end;

procedure TProgCorner.FormCreate(Sender: TObject);
begin
  RichEdit1.Text:= 'http://www.example.com';
end;

end.

【讨论】:

    猜你喜欢
    • 2020-05-31
    • 2017-11-13
    • 2016-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多