Link to home
Start Free TrialLog in
Avatar of cyprus106
cyprus106

asked on

Add Hyperlink(s) to a RichEdit

First off, I'm using BCB5.
I can add URL autodetection functionality to a RichEdit easily, but I want to be able to add a hyperlink, (i.e. when the user clicks on something that says:
Go to experts exchange
then it will go to the url https://www.experts-exchange.com)

How would I go about setting this up?
I've spent the entire day trying and guessing (and failing...) I hope somebody on here has a better idea.
Thanks,

Cyprus
Avatar of jemax
jemax

Hi,

there is no simple way, in my opinion. RichEdit ctrl provides you drawing of blue underlined text+hand cursor(CFM_LINK) and EN_LINK (EM_SETEVENTMASK : ENM_LINK|ENM_PROTECTED)notification when user clicks on it, all the rest is on your own.
If you want your URL to appear in rich edit, as it is ("www.w.com') just set selection for this piece of text, apply CFM_LINK style, and handle EN_LINK.
If you want to have a "hidden" URL, you could do the following:
1 Insert text like "G[ww.e-e.com]o to experts exchange"
2 Apply CFM_HIDDEN style on 'www.e-e.com' part of your text
3 When handling EN_LINK, you get the CHARRANGE of your  entire link text, so you can extract the [] part, and launch the browser.

You can use CFM_PROTECTED style for your URL, to prevent deleting or splitting it.

Caution: do not place the hidden text at the beginning or end of the URL, rich edit has prepared some bugs for this case:)      

If you need more info or a sample..., I'll be glad to provide it.

HTH,
 jemax
Avatar of cyprus106

ASKER

Actually, a sample would be nice. I tried implementing the code based on your comment, but it didn't really like how i did things and I'm pretty sure I didn't do it right at all. If you do have sample code I would appreciate it greatly.

Thanks,
     Cyprus
Hi,

 here is the sample. I use VC6, but there is no MFC or VC spec. code, I hope. I make the link as [PROT.][HIDDEN][PROT.], because rich edit has a bug which allows deleting protected chars if you make it [P][H]. It is just a sample, so making it safer, faster, unicode etc is up to you.
 

#define URL_BUF_LEN 512
#define URL_HDR_LEN 4

void CDlg::OnButton1()
{
     ::SendMessage (m_hRichWnd, EM_SETEVENTMASK, 0 , ENM_LINK|ENM_PROTECTED);
     InsertLink (2, "Go to Experts-Exchange", "https://www.experts-exchange.com");
}

BOOL CDlg::InsertLink(int nPos, const char *pszTitle, const char *pszURL)
{
     CHARFORMAT2 cf2;;
     int nTitleLen, nURLen;
     char buf[URL_BUF_LEN];

     if (!pszTitle || !pszTitle[0] || !pszTitle[1])
          return FALSE; //title too short

     memset (&cf2, 0, sizeof(cf2));
     cf2.cbSize = sizeof(cf2);

     nTitleLen = strlen (pszTitle);
     nURLen = strlen (pszURL) + URL_HDR_LEN;

     //1st letter+url len+url text+rest of the title
     // "G[0018https://www.experts-exchange.com]o to Experts-Exchange"
     sprintf (buf, "%c%04X%s%s", *pszTitle, nURLen, pszURL, pszTitle + 1);

     //set title text
     ::SendMessage (m_hRichWnd, EM_SETSEL, nPos, nPos);
     ::SendMessage (m_hRichWnd, EM_REPLACESEL, 0, (LPARAM)buf);
     
     //apply link style on'G'
     ::SendMessage (m_hRichWnd, EM_SETSEL, nPos, nPos+1);
     cf2.dwMask = CFE_LINK|CFE_PROTECTED;
     cf2.dwEffects = CFM_LINK|CFM_PROTECTED;
     ::SendMessage (m_hRichWnd, EM_SETCHARFORMAT, SCF_SELECTION , (LPARAM)&cf2);

     //set url text and hide it
     nPos++;
     
     ::SendMessage (m_hRichWnd, EM_SETSEL, nPos, nPos+nURLen);
     cf2.dwMask = CFE_LINK|CFE_PROTECTED|CFE_HIDDEN;
     cf2.dwEffects = CFM_LINK|CFM_PROTECTED|CFM_HIDDEN;
     ::SendMessage (m_hRichWnd, EM_SETCHARFORMAT, SCF_SELECTION , (LPARAM)&cf2);

     //apply link style on the rest of the title
     nPos += nURLen;

     ::SendMessage (m_hRichWnd, EM_SETSEL, nPos, nPos+nTitleLen-1);
     cf2.dwMask = CFE_LINK|CFE_PROTECTED;
     cf2.dwEffects = CFM_LINK|CFM_PROTECTED;
     ::SendMessage (m_hRichWnd, EM_SETCHARFORMAT, SCF_SELECTION , (LPARAM)&cf2);

     return TRUE;
}

//EN_LINK message handler
void CDlg::OnLink(NMHDR* pNMHDR, LRESULT* pResult)
{
          ENLINK *pEnLink = (ENLINK *)pNMHDR;

     if (pEnLink->msg == WM_LBUTTONDOWN)
     {
          TEXTRANGE tr;
          int nURLen, nRange;

          tr.chrg = pEnLink->chrg;
          tr.lpstrText = new TCHAR[tr.chrg.cpMax-tr.chrg.cpMin + 1];

          if (!tr.lpstrText)
               return;

          //get the link text
          nRange = ::SendMessage (m_rich.m_hWnd, EM_GETTEXTRANGE, 0 , (LPARAM)&tr);

          //get URL length
          if (!sscanf (tr.lpstrText + 1, "%04X", &nURLen) ||
               nRange < nURLen + 1)
               return;

          //place zero at the URL's end
          tr.lpstrText[nURLen + 1] = 0;

          //here is your URL
          printf (tr.lpstrText + 1 + URL_HDR_LEN);

          delete[] tr.lpstrText;
     }
     
     *pResult = 0;
}

//EN_PROTECTED message handler
void CDlg::OnProtected(NMHDR* pNMHDR, LRESULT* pResult)
{
     ENPROTECTED *pEnProtected = (ENPROTECTED *)pNMHDR;

     //allow copy, but deny link modification
     *pResult = pEnProtected->msg != WM_COPY;  
}

Best regards,
jemax



OK, so I stuck in your code and did a few very quick conversions to BCB5, checked the code out: works like a charm, It highlights the hyperlink, changes the cursor when necessary... There's just one problem:
It doesn't launch the hyperlink when it's clicked on. In fact, it doesn't do anything when it's clicked on. We're getting there! Am I supposed to stick in a message handler or something in the header to catch the clicking on the URL?

Much Thanks,
  >>Cyprus
Hi,

in void CDlg::OnLink(...) func, in the the sample, you can see the following 2 lines:
//here is your URL
printf (tr.lpstrText + 1 + URL_HDR_LEN);
(tr.lpstrText + 1 + URL_HDR_LEN) is a ptr to the real URL.
You can replace the 'printf' by
ShellExecute (NULL, "open", tr.lpstrText + 1 + URL_HDR_LEN, NULL, NULL, SW_SHOW); to launch the default browser.

Best regards,
jemax
OnLink(...) is never called anywhere within the functions, which is probably the reason it's not opening up a browser or even thinking when the link is clicked on. Should I be putting this somewhere or something? Where do I put a call to OnLink?
Hi,

I can think of 3 possible reasons for that:
1. ::SendMessage (m_hRichWnd, EM_SETEVENTMASK, 0 , ENM_LINK|ENM_PROTECTED); has not been invoked before inserting a link.

2. Somewhat is incorrect in the message map.
3. Rich Edit control sends EN_LINK & EN_PROTECTED messages to its parent window, so the handles should be there.

HTH,
jemax
but just having OnLink in there wouldn't automatically called when the RichEdit does EN_LINK. Shouldn't there be a message handler, or an actual call to OnLink?
I would think it needs to know that there is an event handler for when it calls EN_LINK...

Do you have the code for the message map somewhere?

Thanks alot, I know this has to be kind of annoying by now. I'll up the points before I accept the answer.
I've got alot of extra I don't need laying around...
ASKER CERTIFIED SOLUTION
Avatar of jemax
jemax

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Sorry, I forgot to say; do not rely too much on my BCB code, it would be better to check/ask it is good, especially usage of 'DefWindowProc' in BCB.

BTW, now I have working sample in BCB. If you need it I can send it to you.

Best regards,
jemax
That did it. That was what I was looking for. Your a lifesaver. Thanks for stickin' with it.
actually, yea if you could send it to me that would be great. this works but theres a few conversion things I did that weren't exactly "proper". my e-mail address is hackcyprus@hotmail.com if you could send me a copy I would be much appreciative
Hi,

ok. I will send it, when I reach the BCB:) computer (several hours, sorry)

Best regards,
jemax