Click here to Skip to main content
Click here to Skip to main content

Adding a custom search feature to CHtmlViews

, 22 Nov 2000
Rate this:
Please Sign up or sign in to vote.
Could be used to create a Visual C++ like search combo for CHtmlViews...Update: Now you can highlight all matching words!

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:

  • 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.
  • 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.
  • 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:

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();
}
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();
}
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:

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:

#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 or mail them to me: devix@devix.cjb.net.

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 Smile | :)

Update

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

Single selection search

Select-all search

Credits

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

    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

    Share

    About the Author

    Marc Richarme
    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 Pinmemberchocm17-Oct-09 20:02 
    Questionforever loop in findText Pinmemberobayan31-Mar-06 14:59 
    GeneralWindow flickering PinmemberKronuz16-Jun-05 15:32 
    Question2 bugs!? Pinmemberbeginner23qweqe16-May-05 12:20 
    Questionhow to do this in c# Pinmemberdotnetdevotee24-Mar-05 2:39 
    AnswerRe: how to do this in c# PinsussAnonymous4-Aug-05 22:30 
    QuestionHow to add jscript in IHTMLDocument and to execute it? (GOOD) Pinsussa_jordanov9-Oct-04 2:16 
    AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) Pinmemberalexiworld10-Oct-04 20:35 
    AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) PinmemberAlexander Fedorov9-Apr-08 6:39 
    QuestionHow to add jscript in IHTMLDocument and to execute it? Pinsussa_jordanov9-Oct-04 2:11 
    GeneralRegular Expression Pinmembersixcans27-Jun-04 17:26 
    Generalabove code is not working properly Pinsusssenmah1-Jun-04 0:04 
    GeneralRe: above code is not working properly Pinmemberjotterst17-Dec-09 7:38 
    Generalrelease version Pinmemberincludeh1013-Apr-04 7:49 
    GeneralRe: release version Pinmemberbruno cortona8-Jun-04 21:53 
    Generalquery PinmembermailMonty3-Mar-04 3:41 
    Generalsolution PinmembermailMonty12-Mar-04 0:05 
    QuestionHow to skip hidden text? PinmemberEdwin Evans2-Nov-03 9:35 
    GeneralThis with frames Pinmemberinc995-Oct-03 6:05 
    GeneralHighlight the search parameter with some extra words Pinmemberpallavibondre1-Oct-03 8:54 
    GeneralThe &quot;right&quot; way to do it. PinmemberShomoiog30-Jul-03 4:37 
    GeneralRe: The &quot;right&quot; way to do it. PinmemberNeville Franks11-Aug-03 12:48 
    GeneralRe: The "right" way to do it. PinmemberYu Hailiang11-Apr-04 19:03 
    GeneralRe: The &quot;right&quot; way to do it. PinmemberJames R. Twine23-Aug-04 11:05 
    GeneralRe: The "right" way to do it. Pinmemberpeterchen27-Jul-10 3:49 
    GeneralSearch BackWards Pinmemberrashvi27-Jun-03 2:47 
    GeneralRe: Search BackWards PinmemberMr Delphi Developer19-May-09 11:19 
    Generalhighlight PinsussAnonymous28-Oct-02 16:44 
    GeneralCHtmlView and Swing PinmemberSky17-Jul-02 23:44 
    GeneralThis With Frames PinsussThe_InCall12-Jul-02 9:57 
    QuestionHow to Get this Working With Frames Pinmemberandyk808-Jan-02 15:54 
    Generalcombine it with... Pinmembersedonya7-Nov-01 9:20 
    GeneralTo get document size of Html page PinmemberBablu29-Jan-01 4:02 
    GeneralRe: To get document size of Html page Pinmember*Marc Richarme*29-Jan-01 8:25 
    GeneralRe: To get document size of Html page PinmemberMarc Richarme29-Jan-01 13:55 
    GeneralRe: To get document size of Html page PinmemberBablu30-Jan-01 4:40 
    GeneralRe: To get document size of Html page PinmemberMarc Richarme31-Jan-01 10:32 
    GeneralHow can I get the Source ? PinmemberOren Farber11-Oct-01 6:51 
    GeneralRe: How can I get the Source ? PinmemberMarc Richarme11-Oct-01 8:21 
    GeneralProblem under W2K PinmemberWayne Tanner24-Nov-00 11:28 
    GeneralRe: Problem under W2K Pinmembersong8225-Jun-02 20:17 
    GeneralRe: Problem under W2K Pinmemberincludeh1013-Apr-04 8:30 
    GeneralDon't Make Promises... PinmemberBrian Hart22-Nov-00 17:23 
    GeneralRe: Don't Make Promises... PinmemberMarc Richarme23-Nov-00 6:48 
    GeneralGraphic PinmemberBrian Hart22-Nov-00 17:18 
    GeneralNice example PinmemberObliterator20-Nov-00 7:40 
    GeneralRe: Nice example PinmemberMarc Richarme21-Nov-00 13:51 
    GeneralRe: Nice example PinmemberMike Stevenson21-Nov-00 14:05 
    GeneralRe: Nice example PinmemberMarc Richarme21-Nov-00 14:21 
    GeneralRe: Nice example PinmemberDavid Wulff21-Nov-00 15:35 

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

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

    | Advertise | Privacy | Terms of Use | Mobile
    Web02 | 2.8.141223.1 | Last Updated 23 Nov 2000
    Article Copyright 2000 by Marc Richarme
    Everything else Copyright © CodeProject, 1999-2014
    Layout: fixed | fluid