0

We have been using IHTMLElement and IHTMLElement2 scripting interface to access web objects and its properties. Right now we are in a situation, want to know the client area occupied by the element, minus any area taken by borders and scroll bars. I came across HTMLDocument class which has a method ClientRectangle(). Its documentation sounds very similar to what we are looking at. I am really not sure how do I access this method, if its ever possible.

can anyone let know is it possible to create an instance of this HTMLDocument class and access its methods?

Link to MSDN documentation of the class what I am talking about. http://msdn.microsoft.com/en-us/library/system.windows.forms.htmlelement.clientrectangle.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
prakashjv
  • 329
  • 2
  • 8
  • 16
  • If you already have a pointer to a valid `IHTMLElement2` object just call the `getClientRects()` member function like you would for any other. – Captain Obvlious Apr 22 '13 at 20:34
  • As a side not, when posting questions it's very helpful to include a [Short Self Contained Correct/Compilable Example - SSCCE](http://sscce.org/) of the code you're having trouble with. Showing us what you've tried and where you're having problems and what those problems are will help us provide you with a working solution. – Captain Obvlious Apr 22 '13 at 20:44
  • getClientRects() would return a collection which is of all the objects on the document. This would be a overhead to identify required one out of them. I am looking at an method which can help me get the clientRectangle with respect to the object of interest. – prakashjv Apr 22 '13 at 22:09
  • Also, thanks for your suggestion, I will do that. Right now I do not have anything working and that was the reason I did not put up code. – prakashjv Apr 22 '13 at 22:11
  • Looking with .NET Reflector, HtmlElement.ClientRectangle just creates a Rectangle with IHtmlElement2.clientLeft, clientTop, clientWidth and clientHeight. Nothing magic here. – Simon Mourier Apr 23 '13 at 08:26

1 Answers1

3

Since your question is somewhat broad the solution I'm providing it somewhat large but complete and tested. If you already have a pointer to a valid IHTMLDocument or IHTMLElement object you can retrieve the location and dimensions of an element quite easily. The only requirement to get the dimensions is that the document must be attached to an IHTMLWindow / IWebBrowser object. I have included a stand along function that creates the IWebBrowser and IHTMLDocument objects for testing.

I've included some comments to help you along the way. This was tested with Visual Studio 2010 on Windows 7 system with Internet Explorer 9. This is the result set I get from the example:

Rect = x=8 y=89 width=992 height=31

contents=hello

#include <comutil.h>    // _bstr_t
#include <mshtml.h>     // IHTMLDocument and IHTMLElement
#include <exdisp.h>     // IWebBrowser2
#include <atlbase.h>    // CComPtr
#include <string>
#include <iostream>

// Make sure we link in the support library!
#pragma comment(lib, "comsuppw.lib")

static const std::wstring
    exampleHtml(L"<body><html><br><br><p id=\"someid\">hello</p></body>");

HRESULT CreateBrowserDocument(
    const std::wstring& html,
    CComPtr<IWebBrowser2>& returnBrowser,
    CComPtr<IHTMLDocument3>& returnDoc);


int main()
{
    ///////////////////////////////////////////////////////////////////////////
    // In order to get the position and dimension of an element we need to
    // have a browser object that owns the document we will work on. If you
    // create and use a IHTMLDocument object through CoCreateInstance it does
    // not have any rendering capabilities by default.
    ///////////////////////////////////////////////////////////////////////////
    HRESULT hr;

    hr = CoInitialize(NULL);
    if(SUCCEEDED(hr))
    {
        // Make sure these two items are scoped so CoUninitialize doesn't gump
        // us up.
        CComPtr<IWebBrowser2> browser;
        CComPtr<IHTMLDocument3> document;

        hr = CreateBrowserDocument(exampleHtml, browser, document);

        if(SUCCEEDED(hr))
        {
            CComPtr<IHTMLElement> element;

            ///////////////////////////////////////////////////////////////////
            // We grab the element by id to make the example easier. in some
            // cases you may need to iterate through all of the elements of the
            // document or parent element to find the one you want the
            // dimensions for.
            ///////////////////////////////////////////////////////////////////

            hr = document->getElementById(_bstr_t(L"someid"), &element);
            if(SUCCEEDED(hr) && element != NULL)
            {
                ///////////////////////////////////////////////////////////////
                // Now that we have the browser object, document object and the
                // element we want to get the dimensions for .... do it the
                // easy way.
                ///////////////////////////////////////////////////////////////
                _bstr_t contents;
                long left, top, width, height;

                // I skip the error checking here. Add it when you implement
                // your solution.
                hr = element->get_innerHTML(contents.GetAddress());
                hr = element->get_offsetLeft(&left);
                hr = element->get_offsetTop(&top);
                hr = element->get_offsetWidth(&width);
                hr = element->get_offsetHeight(&height);

                std::cout
                    << "Rect = "
                    << "x=" << left << " "
                    << "y=" << top << "  "
                    << "width=" << width << " "
                    << "height=" << height << std::endl
                    << "contents=" << contents << std::endl;
            }
        }
    }

    CoUninitialize();

    return 0;
}


// Here we create web browser and document objects. The additional browser
// object is required for layout management. I have taken a shortcut here and
// create an instance Internet Explorer instead. This allows the browser to
// create and initializes a HTMLDocument when we call IWebBrowser::Navigate.
HRESULT CreateBrowserDocument(
    const std::wstring& html,
    CComPtr<IWebBrowser2>& returnBrowser,
    CComPtr<IHTMLDocument3>& returnDoc)
{
    CComPtr<IHTMLDocument2> document;
    CComPtr<IWebBrowser2> browser;
    HRESULT hr;

    hr = CoCreateInstance(
        CLSID_InternetExplorer,
        NULL,
        CLSCTX_SERVER,
        IID_IWebBrowser2,
        reinterpret_cast<void**>(&browser));
    if(SUCCEEDED(hr))
    {
        // The browser does not contain a document by default. We can force
        // one though by navigating to the `about` page. This is fast and
        // does not require an internet connection.
        VARIANT empty;

        VariantInit(&empty);

        hr = browser->Navigate(
            _bstr_t(L"about:"), &empty, &empty, &empty, &empty);

        //  Wait for the load.
        if(SUCCEEDED(hr))
        {
            READYSTATE state;

            while(SUCCEEDED(hr = browser->get_ReadyState(&state)))
            {
                if(state == READYSTATE_COMPLETE) break;
            }
        }

        // The browser now has a document object. Grab it.
        if(SUCCEEDED(hr))
        {
            CComPtr<IDispatch> dispatch;

            hr = browser->get_Document(&dispatch);
            if(SUCCEEDED(hr) && dispatch != NULL)
            {
                hr = dispatch.QueryInterface<IHTMLDocument2>(&document);
            }
            else
            {
                hr = E_FAIL;
            }
        }
    }

    if(SUCCEEDED(hr))
    {
        // Since the about page is empty we can just write out our test HTML
        // directly to the document. Takes some effort since we need to
        // use a safe array to send it to the document.
        SAFEARRAY *pString = SafeArrayCreateVector(VT_VARIANT, 0, 1);
        if (pString != NULL)
        {
            VARIANT *param;

            hr = SafeArrayAccessData(pString, reinterpret_cast<void**>(&param));
            if(SUCCEEDED(hr))
            {
                const _bstr_t htmlString(SysAllocString(html.c_str()));

                param->vt = VT_BSTR;
                param->bstrVal = htmlString;

                hr = SafeArrayUnaccessData(pString);
                if(SUCCEEDED(hr))
                {
                    hr = document->write(pString);
                    document->close();
                }
            }

            SafeArrayDestroy(pString);
        }

        //  Set the return values
        if(SUCCEEDED(hr) && document != NULL && browser != NULL)
        {
            CComPtr<IHTMLDocument3> temp;
            hr = document.QueryInterface<IHTMLDocument3>(&temp);
            if(SUCCEEDED(hr) && temp != NULL)
            {
                document = temp;
            }
            else 
            {
                hr = E_FAIL;
            }

            CComPtr<IHTMLDocument3> tempDoc;
            if(SUCCEEDED(hr))
            {
                hr = document.QueryInterface<IHTMLDocument3>(&tempDoc);
            }

            if(SUCCEEDED(hr) && tempDoc != NULL)
            {
                returnDoc = tempDoc;
                returnBrowser = browser;
            }
        }
        else if(!FAILED(hr))
        {
            hr = E_FAIL;
        }
    }

    return hr;
}



[EDIT 1: Removed unnecessary call to IWebBrowser::put_RegisterAsBrowser]

[EDIT 2: Simplified getting dimensions by using IHTMLElement::get_OffsetXXX instead of IHTMLElement::get_clientXXX]

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74