Click here to Skip to main content
Licence 
First Posted 17 Nov 2000
Views 144,344
Bookmarked 41 times

Adding a custom search feature to CHtmlViews

By | 22 Nov 2000 | Article
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) shit
            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 :)

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

    About the Author

    Marc Richarme

    Engineer
    Nokia
    Denmark Denmark

    Member

    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.


    Sign Up to vote   Poor Excellent
    Add a reason or comment to your vote: x
    Votes of 3 or less require a comment

    Comments and Discussions

     
    You must Sign In to use this message board. (secure sign-in)
     
    Search this forum  
     FAQ
        Noise  Layout  Per page   
      Refresh
    GeneralIHTMLElement *lpElement not released Pinmemberchocm19:02 17 Oct '09  
    Questionforever loop in findText Pinmemberobayan13:59 31 Mar '06  
    GeneralWindow flickering PinmemberKronuz14:32 16 Jun '05  
    Question2 bugs!? Pinmemberbeginner23qweqe11:20 16 May '05  
    Questionhow to do this in c# Pinmemberdotnetdevotee1:39 24 Mar '05  
    AnswerRe: how to do this in c# PinsussAnonymous21:30 4 Aug '05  
    QuestionHow to add jscript in IHTMLDocument and to execute it? (GOOD) Pinsussa_jordanov1:16 9 Oct '04  
    AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) Pinmemberalexiworld19:35 10 Oct '04  
    AnswerRe: How to add jscript in IHTMLDocument and to execute it? (GOOD) PinmemberAlexander Fedorov5:39 9 Apr '08  
    QuestionHow to add jscript in IHTMLDocument and to execute it? Pinsussa_jordanov1:11 9 Oct '04  
    GeneralRegular Expression Pinmembersixcans16:26 27 Jun '04  
    Generalabove code is not working properly Pinsusssenmah23:04 31 May '04  
    GeneralRe: above code is not working properly Pinmemberjotterst6:38 17 Dec '09  
    Generalrelease version Pinmemberincludeh106:49 13 Apr '04  
    GeneralRe: release version Pinmemberbruno cortona20:53 8 Jun '04  
    Generalquery PinmembermailMonty2:41 3 Mar '04  
    Generalsolution PinmembermailMonty23:05 11 Mar '04  
    QuestionHow to skip hidden text? PinmemberEdwin Evans8:35 2 Nov '03  
    GeneralThis with frames Pinmemberinc995:05 5 Oct '03  
    GeneralHighlight the search parameter with some extra words Pinmemberpallavibondre7:54 1 Oct '03  
    GeneralThe "right" way to do it. PinmemberShomoiog3:37 30 Jul '03  
    GeneralRe: The "right" way to do it. PinmemberNeville Franks11:48 11 Aug '03  
    GeneralRe: The "right" way to do it. PinmemberYu Hailiang18:03 11 Apr '04  
    GeneralRe: The "right" way to do it. PinmemberJames R. Twine10:05 23 Aug '04  
    GeneralRe: The "right" way to do it. Pinmemberpeterchen2:49 27 Jul '10  

    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.

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