Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Adding a Custom Search Feature to CHtmlViews

0.00/5 (No votes)
22 Nov 2000 1  
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:

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

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.

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