Click here to Skip to main content
15,879,095 members
Articles / Desktop Programming / MFC

Adding a Custom Search Feature to CHtmlViews

Rate me:
Please Sign up or sign in to vote.
4.96/5 (14 votes)
22 Nov 2000 204.5K   1.8K   40   53
Could be used to create a Visual C++ like search combo for CHtmlViews... With the update, you can highlight all matching words!
In this article, you will see how to make a globally working search combo like in Visual C++ work in your CHtmlView.

Introduction

Ever wanted to make a globally working search combo like in Visual C++? Well, here's how to make it work in your CHtmlView. BTW, this code can be used for anything, not just for a search combo!

Search!

The search feature is based on three functions owned by your CHtmlView-derived class:

  • C++
    CMyHtmlView::FindText(CString searchText, long lFlags /* =2 */, 
    BOOL bNNF /* =FALSE (for internal use)*/)

    Searches through the document for the specified word/text and highlights the next match.

    Parameters

    • searchText -Text to search for (duh!)
    • lFlags - Search flags passed to IHTMLTxtRange::fidText. Can be any combination of 2 (search for whole words only) and 4 (match case)
    • bNNF - Used internally. If you want to know what this does, look at the code, I don't remember.
  • C++
    CMyHtmlView::FindText2(CString searchText, long lFlags /* =2 */, 
    CString matchStyle, CString searchID)

    Searches through the document for the specified word/text and highlights all found occurrences.

    Parameters

    • searchText - Text to search for (again, duh!)
    • lFlags - Search flags passed to IHTMLTxtRange::findText. Can be any combination of 2 (search for whole words only) and 4 (match case)
    • matchStyle - Allows you to customize the way the found text is highlighted in the document. This is a CSS style assigned to the <span> HTML tag. Default: white text on dark blue background.
    • searchID - Use this if you want to highlight multiple search results at one time (you could use a different matchStyle for each different searchID. Normally, you would leave this to default.
  • C++
    CMyHtmlView::ClearSearchResults(CString searchID /* ="CHtmlView_Search" */)

    Un-highlights the search results from FindText2.

    Parameters

    • searchID - the searchID you used in FindText2. If you used the default value, do the same here.

And here comes the code for the functions:

C++
void CMyHtmlView::FindText(CString searchText, 
   long lFlags /* =2 */, BOOL bNNF /* =FALSE  (for internal use)*/)
{
    static CString sLastSearch;
    static BSTR lastBookmark = NULL;

    if(sLastSearch != searchText)
        lastBookmark = NULL;
    sLastSearch = searchText;    

    IHTMLDocument2 *lpHtmlDocument = NULL;
    LPDISPATCH lpDispatch = NULL;
    lpDispatch = GetHtmlDocument();
    ASSERT(lpDispatch);

    lpDispatch->QueryInterface(IID_IHTMLDocument2, 
                                (void**)&lpHtmlDocument);
    ASSERT(lpHtmlDocument);

    lpDispatch->Release();

    IHTMLElement *lpBodyElm;
    IHTMLBodyElement *lpBody;
    IHTMLTxtRange *lpTxtRange;

    lpHtmlDocument->get_body(&lpBodyElm);
    ASSERT(lpBodyElm);
    lpHtmlDocument->Release();
    lpBodyElm->QueryInterface(IID_IHTMLBodyElement,(void**)&lpBody);
    ASSERT(lpBody);
    lpBodyElm->Release();
    lpBody->createTextRange(&lpTxtRange);
    ASSERT(lpTxtRange);
    lpBody->Release();

    CComBSTR search(searchText.GetLength()+1,(LPCTSTR)searchText);
    bool bFound,bTest;
    long t;

    if(lastBookmark!=NULL)
    {
        lpTxtRange->moveToBookmark(lastBookmark,
                               (VARIANT_BOOL*)&bTest);
        if(!bTest)
        {
            lastBookmark=NULL;
            lpTxtRange->moveStart((BSTR)CComBSTR("Textedit"),1,&t);
            lpTxtRange->moveEnd((BSTR)CComBSTR("Textedit"),1,&t);
        } else
        {
            lpTxtRange->moveStart((BSTR)CComBSTR("Character"),1,&t);
            lpTxtRange->moveEnd((BSTR)CComBSTR("Textedit"),1,&t);
        }
    } else
    {
        lpTxtRange->moveStart((BSTR)CComBSTR("Textedit"),0,&t);
        lpTxtRange->moveEnd((BSTR)CComBSTR("Textedit"),1,&t);
    }
    lpTxtRange->findText((BSTR)search,0,
                    lFlags,(VARIANT_BOOL*)&bFound);

    if(!bFound)
    {
        if(lastBookmark==NULL && !bNNF)
        {
            CString message;
            message.Format("Cannot find the string: '%s'",searchText);
            AfxMessageBox(message);
        } else if(lastBookmark!=NULL)
        {
            lastBookmark = NULL;
            FindText(searchText,lFlags,TRUE);
        }
    } else
    {
        if(lpTxtRange->getBookmark(&lastBookmark)!=S_OK)
            lastBookmark=NULL;
        lpTxtRange->select();
        lpTxtRange->scrollIntoView(TRUE);
    }

    lpTxtRange->Release();
}
C++
void CMyHtmlView::FindText2(CString searchText, 
   long lFlags /* =2 */, CString matchStyle, CString searchID)
{
    ClearSearchResults(searchID);

    IHTMLDocument2 *lpHtmlDocument = NULL;
    LPDISPATCH lpDispatch = NULL;
    lpDispatch = GetHtmlDocument();
    ASSERT(lpDispatch);

    lpDispatch->QueryInterface(IID_IHTMLDocument2, 
                               (void**)&lpHtmlDocument);
    ASSERT(lpHtmlDocument);

    lpDispatch->Release();

    IHTMLElement *lpBodyElm;
    IHTMLBodyElement *lpBody;
    IHTMLTxtRange *lpTxtRange;

    lpHtmlDocument->get_body(&lpBodyElm);
    ASSERT(lpBodyElm);
    lpHtmlDocument->Release();
    lpBodyElm->QueryInterface(IID_IHTMLBodyElement,(void**)&lpBody);
    ASSERT(lpBody);
    lpBodyElm->Release();
    lpBody->createTextRange(&lpTxtRange);
    ASSERT(lpTxtRange);
    lpBody->Release();

    CComBSTR html;
    CComBSTR newhtml;
    CComBSTR search(searchText.GetLength()+1,(LPCTSTR)searchText);

    long t;
    bool bFound;
    while(lpTxtRange->findText(search,0,lFlags,
                        (VARIANT_BOOL*)&bFound),bFound)
    {
        newhtml.Empty();
        lpTxtRange->get_htmlText(&html);
        newhtml.Append("<span id='");
        newhtml.Append((LPCTSTR)searchID);
        newhtml.Append("' style='");
        newhtml.Append((LPCTSTR)matchStyle);
        newhtml.Append("'>");
        if(searchText==" ")
            // doesn't work very well, but prevents (some) sh*t
            newhtml.Append(" ;"); 
        else 
            newhtml.AppendBSTR(html);
        newhtml.Append("</span>");
        lpTxtRange->pasteHTML(newhtml);
                
        lpTxtRange->moveStart((BSTR)CComBSTR("Character"),1,&t);
        lpTxtRange->moveEnd((BSTR)CComBSTR("Textedit"),1,&t);
    }

    lpTxtRange->Release();
}
C++
void CMyHtmlView::ClearSearchResults(CString searchID)
{
    CComBSTR testid(searchID.GetLength()+1,searchID);
    CComBSTR testtag(5,"SPAN");
    IHTMLDocument2 *lpHtmlDocument = NULL;
    LPDISPATCH lpDispatch = NULL;
    lpDispatch = GetHtmlDocument();
    ASSERT(lpDispatch);

    lpDispatch->QueryInterface(IID_IHTMLDocument2, 
                                (void**)&lpHtmlDocument);
    ASSERT(lpHtmlDocument);
    lpDispatch->Release();

    IHTMLElementCollection *lpAllElements;
    lpHtmlDocument->get_all(&lpAllElements);
    ASSERT(lpAllElements);
    lpHtmlDocument->Release();

    IUnknown *lpUnk;
    IEnumVARIANT *lpNewEnum;
    if (SUCCEEDED(lpAllElements->get__newEnum(&lpUnk)) && lpUnk != NULL)
    {
        lpUnk->QueryInterface(IID_IEnumVARIANT,(void**)&lpNewEnum);
        ASSERT(lpNewEnum);
        VARIANT varElement;
        IHTMLElement *lpElement;

        while (lpNewEnum->Next(1, &varElement, NULL) == S_OK)
        {
            _ASSERTE(varElement.vt == VT_DISPATCH);
            varElement.pdispVal->QueryInterface
                   (IID_IHTMLElement,(void**)&lpElement);
            ASSERT(lpElement);
            if (lpElement)
            {
                CComBSTR id;
                CComBSTR tag;
                lpElement->get_id(&id);
                lpElement->get_tagName(&tag);
                if((id==testid)&&(tag==testtag))
                {
                    BSTR innerText;
                    lpElement->get_innerHTML(&innerText);
                    lpElement->put_outerHTML(innerText);
                }
            }
            VariantClear(&varElement);
        }
    }
}

The definition:

C++
void FindText(CString searchText, long Flags = 2, 
    BOOL bNNF = FALSE /*for internal use*/);
void FindText2(CString searchText, long Flags = 2, 
    CString matchStyle = "color: white; background-color: darkblue", 
    CString searchID = "CHtmlView_Search");
void ClearSearchResults(CString searchID = "CHtmlView_Search");

And the additional files to include to your CHtmlView derived class:

C++
#include <mshtml.h>
#include <atlbase.h>

Usage

  • Is you want a search that highlights one matching word at a time, use FindText with the text you want to search for as the first parameter, and the other params left to default.
  • If you want a search that does the same as above, but that also searches for matching text within words, use the same function as above, but with the second parameter set to 2. (Use this one or the above one for standard combo-box search)
  • If you want a search that scans through the whole document and highlights all found occurrences, call FindText2 with the text you want to search for as the first parameter, and the second parameter used as described above.

That's it!

BTW, if you want to use the "advanced" features of these functions, you should know at least a little CSS.

Where's the Combo?

For information on how to implement the combo-box, check the source code of the demo project. If you have questions on how to do this, post them here.

Known Bugs

When you search for " " (space) in FindText2, the function replaces all spaces with non-breaking spaces (otherwise they simply disappear). This causes the page to display like sh*t, but it's still better than the spaces disappearing. :)

Update

In this latest update, I have added two more functions that allow you to make a search that selects all off the result at the same time. Here are the screenshots of the demo-app:

Image 1

Single selection search:

Image 2

Select-all search.

Credits

COM code inspired by some script on Nic's Javascript Page.

History

  • 18th November, 2000: Initial version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Engineer Nokia
Denmark Denmark
My programming experience started a long time ago in
QBasic (on a 25MHz 486).
I'm now mainly using Java, C++, C, MFC, Perl and PHP, but have used quite a number of other languages as well for various projects.

Comments and Discussions

 
GeneralIHTMLElement *lpElement not released Pin
chocm17-Oct-09 19:02
chocm17-Oct-09 19:02 
Questionforever loop in findText Pin
obayan31-Mar-06 13:59
obayan31-Mar-06 13:59 
GeneralWindow flickering Pin
Kronuz16-Jun-05 14:32
Kronuz16-Jun-05 14:32 
Question2 bugs!? Pin
Member 22731316-May-05 11:20
Member 22731316-May-05 11:20 
Questionhow to do this in c# Pin
dotnetdevotee24-Mar-05 1:39
dotnetdevotee24-Mar-05 1:39 
AnswerRe: how to do this in c# Pin
Anonymous4-Aug-05 21:30
Anonymous4-Aug-05 21:30 
QuestionHow to add jscript in IHTMLDocument and to execute it? (GOOD) Pin
alexiworld9-Oct-04 1:16
alexiworld9-Oct-04 1:16 
AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) Pin
alexiworld10-Oct-04 19:35
alexiworld10-Oct-04 19:35 
AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) Pin
Alexander Fedorov9-Apr-08 5:39
Alexander Fedorov9-Apr-08 5:39 
QuestionHow to add jscript in IHTMLDocument and to execute it? Pin
alexiworld9-Oct-04 1:11
alexiworld9-Oct-04 1:11 
GeneralRegular Expression Pin
sixcans27-Jun-04 16:26
sixcans27-Jun-04 16:26 
Generalabove code is not working properly Pin
senthil_kumar_project31-May-04 23:04
senthil_kumar_project31-May-04 23:04 
GeneralRe: above code is not working properly Pin
jotterst17-Dec-09 6:38
jotterst17-Dec-09 6:38 
Generalrelease version Pin
includeh1013-Apr-04 6:49
includeh1013-Apr-04 6:49 
the code is not working with release version
how to figue it out?
all programmers here just use debug version?

Sleepy | :zzz: Confused | :confused: Sleepy | :zzz: Confused | :confused: Sleepy | :zzz: Confused | :confused:

includeh10
GeneralRe: release version Pin
bruno cortona8-Jun-04 20:53
bruno cortona8-Jun-04 20:53 
Generalquery Pin
Monty23-Mar-04 2:41
Monty23-Mar-04 2:41 
Generalsolution Pin
Monty211-Mar-04 23:05
Monty211-Mar-04 23:05 
QuestionHow to skip hidden text? Pin
Edwin Evans2-Nov-03 8:35
Edwin Evans2-Nov-03 8:35 
GeneralThis with frames Pin
inc995-Oct-03 5:05
inc995-Oct-03 5:05 
GeneralHighlight the search parameter with some extra words Pin
pallavibondre1-Oct-03 7:54
pallavibondre1-Oct-03 7:54 
GeneralThe &quot;right&quot; way to do it. Pin
Member 42324330-Jul-03 3:37
Member 42324330-Jul-03 3:37 
GeneralRe: The &quot;right&quot; way to do it. Pin
Neville Franks11-Aug-03 11:48
Neville Franks11-Aug-03 11:48 
GeneralRe: The "right" way to do it. Pin
Hailiang Yu11-Apr-04 18:03
Hailiang Yu11-Apr-04 18:03 
GeneralRe: The &quot;right&quot; way to do it. Pin
James R. Twine23-Aug-04 10:05
James R. Twine23-Aug-04 10:05 
GeneralRe: The "right" way to do it. Pin
peterchen27-Jul-10 2:49
peterchen27-Jul-10 2:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.