wxHtmlWindow

From WxWiki
Jump to: navigation, search
Official Classes SmallBlocks.png Archive Containers Controls Data Structures Database Date & Time Debug Device Contexts Dialogs Document & Views Drag & Drop Events Filesystem Frames Graphics Grid Cell Help HTML Logging Miscellaneous Networking Printing Sizers Streams Threading Windows

wxHtmlWindow is probably the only class you will directly use unless you want to do something special (like adding new tag handlers or MIME filters).

The purpose of this class is to display rich content pages (either local file or downloaded via HTTP protocol) in a window based on a subset of the HTML standard. The width of the window is constant - given in the constructor - and virtual height is changed dynamically depending on page size. Once the window is created you can set its content by calling SetPage() with raw HTML, LoadPage() with a wxFileSystem location or LoadFile() with a filename.

Note: If you want complete HTML/CSS support as well as a Javascript engine, see instead wxWebView. wxHtmlWindow uses the wxImage class for displaying images, as such you need to initialize the handlers for any image formats you use before loading a page. See wxInitAllImageHandlers and wxImage::AddHandler.

Problems with background image

There seems to be a problem with rendering the background image while scrolling down a wxHtmlWindow object.

Without patching: creating a derived class

Here is a solution how to get it working without patching the original wxHtmlWindow class:

#ifndef WXHTMLWINDOWMY_H
#define WXHTMLWINDOWMY_H

#include "wx/wxhtml.h"
#include "wx/dcclient.h"
#include "wx/dcmemory.h"

class wxHtmlWindowMy : public wxHtmlWindow
{
	public:
		wxHtmlWindowMy(wxWindow *parent, wxWindowID id = wxID_ANY,
                 const wxPoint& pos = wxDefaultPosition,
                 const wxSize& size = wxDefaultSize,
                 long style = wxHW_DEFAULT_STYLE,
                 const wxString& name = wxT("htmlWindow"))
		: wxHtmlWindow(parent, id, pos, size, style, name)
		{
			m_backBuffer = NULL;
		}

		~wxHtmlWindowMy() {}

		void SetBackgroundImage(const wxBitmap& bmp) {m_bmpBg = bmp;}
	protected:
		wxBitmap m_bmpBg;
		bool m_eraseBgInOnPaint;
		wxBitmap *m_backBuffer;

		void OnEraseBackground(wxEraseEvent& event);
		void OnPaint(wxPaintEvent& event);
		void OnSize(wxSizeEvent& event);

		DECLARE_EVENT_TABLE()
};

#endif
#include "htmlwinmy.h"

BEGIN_EVENT_TABLE(wxHtmlWindowMy, wxHtmlWindow)
    EVT_ERASE_BACKGROUND(wxHtmlWindowMy::OnEraseBackground)
    EVT_PAINT(wxHtmlWindowMy::OnPaint)
    EVT_SIZE(wxHtmlWindowMy::OnSize)
END_EVENT_TABLE()

void wxHtmlWindowMy::OnEraseBackground(wxEraseEvent& event)
{
    if ( !m_bmpBg.Ok() )
    {
        // don't even skip the event, if we don't have a bg bitmap we're going
        // to overwrite background in OnPaint() below anyhow, so letting the
        // default handling take place would only result in flicker, just set a
        // flag to erase the background below
        m_eraseBgInOnPaint = true;
        return;
    }

    wxDC& dc = *event.GetDC();

    // if the image is not fully opaque, we have to erase the background before
    // drawing it, however avoid doing it for opaque images as this would just
    // result in extra flicker without any other effect as background is
    // completely covered anyhow
    if ( m_bmpBg.GetMask() )
    {
        dc.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
        dc.Clear();
    }
	dc.DrawBitmap(m_bmpBg, 0, 0, true /* use mask */);
}

void wxHtmlWindowMy::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    wxPaintDC dc(this);

    if (/*m_tmpCanDrawLocks > 0 || */m_Cell == NULL)
        return;

    int x, y;
    GetViewStart(&x, &y);
    wxRect rect = GetUpdateRegion().GetBox();
    wxSize sz = GetSize();

    wxMemoryDC dcm;
    if ( !m_backBuffer )
		m_backBuffer = new wxBitmap(sz.x, sz.y);
    dcm.SelectObject(*m_backBuffer);

    if ( m_eraseBgInOnPaint )
    {
        dcm.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
        dcm.Clear();
        m_eraseBgInOnPaint = false;
    }
    else // someone has already erased the background, keep it
    {
        // preserve the existing background, otherwise we'd erase anything the
        // user code had drawn in its EVT_ERASE_BACKGROUND handler when we do
        // the Blit back below
		dcm.Blit(0, rect.GetTop(),
				sz.x, rect.GetBottom() - rect.GetTop() + 1,
				&dc,
				0, rect.GetTop());
		dcm.DrawBitmap(m_bmpBg, 0, -(y * wxHTML_SCROLL_STEP), true /* use mask */);
    }

    PrepareDC(dcm);
    dcm.SetMapMode(wxMM_TEXT);
    dcm.SetBackgroundMode(wxTRANSPARENT);

    wxHtmlRenderingInfo rinfo;
    wxDefaultHtmlRenderingStyle rstyle;
    rinfo.SetSelection(m_selection);
    rinfo.SetStyle(&rstyle);
    m_Cell->Draw(dcm, 0, 0,
                 y * wxHTML_SCROLL_STEP + rect.GetTop(),
                 y * wxHTML_SCROLL_STEP + rect.GetBottom(),
                 rinfo);

//#define DEBUG_HTML_SELECTION
#ifdef DEBUG_HTML_SELECTION
    {
    int xc, yc, x, y;
    wxGetMousePosition(&xc, &yc);
    ScreenToClient(&xc, &yc);
    CalcUnscrolledPosition(xc, yc, &x, &y);
    wxHtmlCell *at = m_Cell->FindCellByPos(x, y);
    wxHtmlCell *before =
        m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_BEFORE);
    wxHtmlCell *after =
        m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_AFTER);

    dcm.SetBrush(*wxTRANSPARENT_BRUSH);
    dcm.SetPen(*wxBLACK_PEN);
    if (at)
        dcm.DrawRectangle(at->GetAbsPos(),
                          wxSize(at->GetWidth(),at->GetHeight()));
    dcm.SetPen(*wxGREEN_PEN);
    if (before)
        dcm.DrawRectangle(before->GetAbsPos().x+1, before->GetAbsPos().y+1,
                          before->GetWidth()-2,before->GetHeight()-2);
    dcm.SetPen(*wxRED_PEN);
    if (after)
        dcm.DrawRectangle(after->GetAbsPos().x+2, after->GetAbsPos().y+2,
                          after->GetWidth()-4,after->GetHeight()-4);
    }
#endif

    dcm.SetDeviceOrigin(0,0);
    dc.Blit(0, rect.GetTop(),
            sz.x, rect.GetBottom() - rect.GetTop() + 1,
            &dcm,
            0, rect.GetTop());
}

void wxHtmlWindowMy::OnSize(wxSizeEvent& event)
{
    wxDELETE(m_backBuffer);
    wxHtmlWindow::OnSize(event);
}
wxHtmlWindowMy* ctrl = new wxHtmlWindowMy(parent);
ctrl->SetBackgroundImage(wxBitmap("images/grass.png"));

Patching: modifying the original wxHtmlWindow class

There is only 1 line added to the original wxHtmlWindow::OnPaint() method. If you want to patch the original source code, you just need to add the line:

dcm.DrawBitmap(m_bmpBg, 0, -(y * wxHTML_SCROLL_STEP), true /* use mask */);

to src/html/htmlwin.cpp file into the method OnPaint() after the line:

if ( m_eraseBgInOnPaint )
{
  ...
}
else
{
    dcm.Blit(0, rect.GetTop(),
        sz.x, rect.GetBottom() - rect.GetTop() + 1,
        &dc,
        0, rect.GetTop());
    /* put the line here: */
    dcm.DrawBitmap(m_bmpBg, 0, -(y * wxHTML_SCROLL_STEP), true /* use mask */);
    /* ------------------ */
}

Don't forget to recompile your wxWidgets.

If you have any questions, please send me an email: [email protected]

Footnote: Bug in SetPage

The patch above works fine except if you use the SetPage method. In the current implementation (as of version 2.8.8) the SetPage method will replace any background bitmap that was previously set with a null bitmap. Bug #9833 has been raised (see http://trac.wxwidgets.org/ticket/9833) and it contains a patch.

See Also