Solved

Add Hyperlink(s) to a RichEdit

Posted on 2002-07-20
13
1,552 Views
Last Modified: 2007-11-27
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 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
0
Comment
Question by:cyprus106
  • 7
  • 6
13 Comments
 
LVL 1

Expert Comment

by:jemax
ID: 7167813
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
0
 

Author Comment

by:cyprus106
ID: 7167974
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
0
 
LVL 1

Expert Comment

by:jemax
ID: 7168575
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", "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[0018www.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



0
 

Author Comment

by:cyprus106
ID: 7169490
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
0
 
LVL 1

Expert Comment

by:jemax
ID: 7171233
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
0
 

Author Comment

by:cyprus106
ID: 7171873
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?
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 1

Expert Comment

by:jemax
ID: 7172401
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
0
 

Author Comment

by:cyprus106
ID: 7172511
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...
0
 
LVL 1

Accepted Solution

by:
jemax earned 200 total points
ID: 7172809
Hi,

I am not familiar to BCB too much. I checked the issue out on my coworker's computer, it seems you should write something like this:

in your unit1.h, add to the class (TForm1, assuming it is the form where you richedit resides) declaration
protected:
void __fastcall OnNotify(TMessage &Message);

        BEGIN_MESSAGE_MAP
        MESSAGE_HANDLER(WM_NOTIFY, TMessage, OnNotify)
        END_MESSAGE_MAP(TControl)

in your unit1.cpp
void __fastcall TForm1::OnNotify(TMessage &Message)
{
        NMHDR *pNMHDR;
        pNMHDR = (NMHDR *)Message.LParam;

        switch (pNMHDR->code)
        {
                case EN_LINK :
                        OnLink (pNMHDR, (LRESULT *)&Message.Result);
                        break;

                case EN_PROTECTED :
                        OnProtected (pNMHDR, (LRESULT *)&Message.Result);
                        break;

                default :
                        Message.Result = DefWindowProc (this->Handle, WM_NOTIFY, Message.WParam, Message.LParam);
        }
}

if you have more than 1 richedits, and you need different EN_LINK processing, check pNMHDR->idFrom, that is control id of the rich which sent the WM_NOTIFY.

Best regards,
jemax
0
 
LVL 1

Expert Comment

by:jemax
ID: 7172829
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
0
 

Author Comment

by:cyprus106
ID: 7172839
That did it. That was what I was looking for. Your a lifesaver. Thanks for stickin' with it.
0
 

Author Comment

by:cyprus106
ID: 7172856
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
0
 
LVL 1

Expert Comment

by:jemax
ID: 7173643
Hi,

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

Best regards,
jemax
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now