1

I'm using a TRichEdit to edit text. I then use TJvRichEditToHtml to convert the text to HTML which then gets sent as the body of an email via Mailgun. I'd like to include hyperlinks in the text that gets emailed.

Following this SO post, and several other similar ones, I included the code below to insert a formatted hyperlink in the TRichEdit at the insertion point. As expected, that hides the URL and shows the link text as blue, underlined text just as I wanted. i.e. if I insert the url https://stackoverflow.com, with the text this is a link, the HTML will contain HYPERLINK "https://stackoverflow.com/"this is a link and the Rich text box shows this is a link all in blue and underlined.

Just before sending the body to Mailgun I then substitute HYPERLINK &quot; with <a href=" and &quot; with " to get the html <a href="https://stackoverflow.com/">this is a link

To identify the end of the link text, so that I can substitute </a> and close the html hyperlink properly, I added code to append a hidden text token ENDHYPERLINK after the hypertext link in the TRich edit which I then substitute for </a>. ie the html before substitution look like HYPERLINK &quot;https://stackoverflow.com/&quot;this is a link ENDHYPERLINKand after substitution looks like <a href="https://stackoverflow.com/">this is a link</a>

However, when I add this extra text after the hyperlink, although it is correctly invisible in the TRichEdit, the blue underlining of the link text in the TRichEdit shrinks back so that the last word of the hyperlink text is no longer formatted in blue underline (no matter how long the hyperlink text is or how long the last word is) i.e I get this is a and the space char in blue and underlined but not the word link

Can someone help me to append my ending token but still get all of the original hyperlink text shown formatted?

Code used to insert the hyperlink - before adding my ending token - this displays OK.

procedure TForm1.InsertHyperLink(const HyperlinkText, 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;

  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;

Code used to insert the hyperlink but also append a closing token - this loses formatting of the last word in the link text.

procedure TForm1.InsertHyperLink(const HyperlinkText, HyperlinkURL: string);

const
  TOKEN_LINK_END = ' ENDHYPERLINK';

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;

  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;

  ///////////////////////////////////////////////////////////////////////////////
  //  //now add closing token as hidden text
  ///////////////////////////////////////////////////////////////////////////////
  StartPos := RichEdit1.SelStart ;
  RichEdit1.SelText := TOKEN_LINK_END;
  RichEdit1.SelStart := StartPos;
  RichEdit1.SelLength := Length(TOKEN_LINK_END);

  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;

Note This question is not about being able to click the links in the TRichEdit and get sent to the web site, so is not a duplicate of many others (Although I have written the code to do this by overriding WndProc, which works OK even though my last word is not shown formatted correctly)

user2834566
  • 775
  • 9
  • 22
  • When inserting the `TOKEN_LINK_END`, it may be picking up the `CFE_LINK` attribute from the previous insertion that you are appending onto. Try explicitly disabling the `CFE_LINK` attribute on the `TOKEN_LINK_END` insertion: `Fmt.dwMask := CFM_HIDDEN or CFM_LINK; Fmt.dwEffects := CFE_HIDDEN;` The only text that should have the `CFE_LINK` attribute is the actual hyperlink - the hidden `HYPERLINK`+URL prefix, and its visible text. Not your custom `ENDHYPERLINK` suffix. – Remy Lebeau Sep 04 '20 at 02:03
  • I'm afraid that made no difference. In case it helps... Before setting dwMask to CFM_LINK, SelStart = 0 and SelLength = 52 (ie length of FullHyperlink as it should be). On setting it to CFM_HIDDEN, SelStart = 0 and SelLength = 38 (ie length of HyperlinkPrefix as it should be). Before setting dwMask for my custom ENDHYPERLINK,SelStart = 52 and SelLength = 13, (ie the start posn and length of ENDHYPERLINK, as it should be). FillChar(Fmt, SizeOf(Fmt), 0); immediately before 2nd Fmt.dwMask := CFM_HIDDEN should clear any existing format left over from the previous text – user2834566 Sep 04 '20 at 09:49
  • In this link [link](https://learn.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-charformat2a) it says to clear any formatting I should set the `dwMask` but not the `dwEffects`. I therefore put `Fmt.dwMask := CFM_HIDDEN or CFM_LINK; Fmt.dwEffects := 0;` just before inserting my ` ENDHYPERLINK` . That made no difference either. – user2834566 Sep 04 '20 at 09:52
  • Actually I don't really care if it is formatted as a real hyperlink. All I need is to insert tokens around two bits of text eg `HL_STARTthis is a linkHL_MIDhttps://stackoverflow.com/HL_END` and have the bit between `HL_START` and `HL_MID` shown in blue underline and everything else hidden. - Plus of course any text following all this have the default font and attributes. I might look at TJvRichEdit as that seems to have an easier way to set hidden text but I cant find any examples if hiding text using that. – user2834566 Sep 04 '20 at 11:06
  • "*`FillChar(Fmt, SizeOf(Fmt), 0);` immediately before 2nd `Fmt.dwMask := CFM_HIDDEN` should clear any existing format left over from the previous text*" - only in the the `Fmt` variable, not in the RichEdit itself. You are inserting new text at the point where `CFE_LINK` has taken effect, so the RichEdit MAY be extending `CFE_LINK` into the new text (I don't know, I haven't tested it yet). "*In this link it says to clear any formatting. I should set the `dwMask` but not the `dwEffects`*" - that is exactly what I suggested in my previous comment. Clear the `CFE_LINK` effect from the RichEdit – Remy Lebeau Sep 04 '20 at 16:08
  • `"That is exactly what I suggested in my previous comment. Clear the CFE_LINK effect from the RichEdit"`. Sorry Remy, as it was setting a value for `dwEffects` I didn't appreciate that was doing the same thing as the link. I did as you suggested and put `Fmt.dwMask := CFM_HIDDEN or CFM_LINK; Fmt.dwEffects := CFE_HIDDEN;` before inserting my token by it made no difference. Are you able to explain exactly what Fmt.dwMask and Fmt.dwEffects do, and to which bit of text the FMT variable applies at any time, then I might understand more what is going on instead of simply copying code. Thank you – user2834566 Sep 05 '20 at 07:15
  • Ah I think I have got somewhere. If the link text includes a trailing space then the whole link text is underlined as desired - unfortunately including the trailing space. However its better to have an unnecessary space underlined in blue than to have the entire last word of the text not underlined in blue. Is this an undocumented 'feature' I wonder? – user2834566 Sep 05 '20 at 21:47
  • But not quite... Using backspace to delete all or part of the link only deletes the visible text, not the invisible part of the url so all that is still in the text of the richedit.text, making it very difficult to parse later. – user2834566 Sep 06 '20 at 18:21

0 Answers0