I want my RichEdit to process hyperlinks, so I followed the instructions on: http://delphi.about.com/od/vclusing/l/aa111803a.htm
Here are the changes I made to the code:
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.
However the hyperlinks that I embedded into my RichEditx.Lines
using the object inspector show up as plain text (not links) and clicking on them does not work.
I'm using Delphi Seattle running on Windows 7 in Win32 mode.
What am I doing wrong?
UPDATE
Using a combination of issuing the deprecated
SendMessage(RE.Handle, EM_AUTOURLDETECT, AURL_ENABLEURL, 0);
and setting the RichEditx.Text:= 'http://www.example.com'
manually in FormCreate
I am able to have the Richedit display a hyperlink and handcursor.
However the WndProc still does not receive a WM_Notify
message.
The WndProc does receive other messages.
UPDATE2
In my eagerness to simplify the issue I left out the fact that the RichEdit
sits on top of a Panel
. The panel eats the WM_Notify
messages so they don't reach the form underneigh.
The code shown in your question works perfect for me as-is. Despite your claim, the Form's WndProc()
does receive the EN_LINK
notifications and launches the clicked URLs, as expected.
However, if you place a RichEdit on another parent control, like a TPanel
, then the Form will not receive the WM_NOTIFY
message anymore. The parent control will receive them, and as such you will have to subclass that parent control instead.
That being said, there are a few improvements that can be made to the code shown:
in your EN_LINK
handling, you can replace this:
CE := TRichEdit(ProgCorner.ActiveControl);
with this instead:
CE := TRichEdit(FindControl(TWMNotify(Msg).NMHdr.hwndFrom));
The notification tells you the HWND
of the RichEdit control that is sending it, and the VCL knows how to retrieve a TWinControl
from an HWND
.
use EM_GETTEXTRANGE
to retrieve the clicked URL, instead of using EM_EXSETSEL
and SelText
(which is a combination of EM_EXGETSEL
and EM_GETTEXTEX
). This way, you are using fewer messages, and don't have to manipulate the RichEdit's selected text at all. The notification tells you the exact range of characters for the URL, so you can just grab those characters directly.
you need to handle HWND
recreation. The VCL may recreate a RichEdit's HWND
at any time. Every time a new HWND
is created, you have to send your EM_SETEVENTMASK
and EM_AUTOURLDETECT
messages again, otherwise you will lose your auto-detection. The best way to handle this is to derive a class from TRichEdit
and override its CreateWnd()
method.
Since you have to derive a class anyway, you can have it handle the VCL's CN_NOTIFY
message, instead of handling the original WM_NOTIFY
message directly in the parent's WndProc
. The VCL knows how to redirect a WM_NOTIFY
message to the VCL control that sent it. This allows VCL controls to handle their own notifications. Thus, your EN_LINK
handler will work no matter what parent control the RichEdit is placed on, you don't have to subclass/override the parent's WndProc()
at all, and you can use the Self
pointer of the RichEdit that is processing the message when accessing members of the RichEdit, such as its Handle
property.
With all of that said, the following code works for me:
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.