Paint background image in any wxWindow, an elegant way

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Paulsen
Experienced Solver
Experienced Solver
Posts: 53
Joined: Wed May 24, 2006 1:56 pm
Location: Germany

Paint background image in any wxWindow, an elegant way

Post by Paulsen »

It is truth universally acknowledged, that background images in wxWindows need to be painted in the erase event handler.

Rather than deriving individually from all those classes that may be suitable to display a background image, to setup the event table etc., it is far simpler to create one event handler class once and for all, which can be used with any wxWindow derived class.

Code: Select all

class wxBackgroundBitmap : public wxEvtHandler {
    typedef wxEvtHandler Inherited;
public:
    wxBackgroundBitmap(const wxBitmap &B) : Bitmap(B), wxEvtHandler() { }
    virtual bool        ProcessEvent(wxEvent &Event);
protected:
    wxBitmap            Bitmap;
};

Code: Select all

bool                wxBackgroundBitmap::ProcessEvent(wxEvent &Event)
{
    if (Event.GetEventType() == wxEVT_ERASE_BACKGROUND) {
        wxEraseEvent &EraseEvent = dynamic_cast<wxEraseEvent &>(Event);
        wxDC *DC = EraseEvent.GetDC();
        DC->DrawBitmap(Bitmap, 0, 0, false);
        return true;
    } else return Inherited::ProcessEvent(Event);
}
To change the background of e.g. a tool bar to an image, use this class like this:

Code: Select all

ToolBarBackground = new wxBackgroundBitmap(wxBITMAP("test"));
ToolBar->PushEventHandler(ToolBarBackground);
Paulsen
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

Looks nice (haven't tested it though).

But to make it usable in more situations, it'd definitely need a tile- and stretchmode for cases when the client-area is bigger than the bitmap. Both should be trivial, tiling is simple and could be taken from htmlwin.cpp, stretching should be possible with wxDC::SetUserScale()

Personally i think it would be even better if it was part of the wxWindow class, like:
wxWindow::SetBackgroundBitmap(wxBitmap &bitmap, ...)
Use the source, Luke!
User avatar
tierra
Site Admin
Site Admin
Posts: 1355
Joined: Sun Aug 29, 2004 7:14 pm
Location: Salt Lake City, Utah, USA
Contact:

Post by tierra »

doublemax wrote:Personally i think it would be even better if it was part of the wxWindow class, like:
wxWindow::SetBackgroundBitmap(wxBitmap &bitmap, ...)
Though that would either require patching wxWidgets or require deriving new wxWindow classes for every type of wxWindow you want to use it with. I don't honestly think it would ever be applied in CVS, the devs are already annoyed with the number of existing wxWindow methods. Also, this encourages all sorts of human interface guideline violations and falls under the category of skinning which is heavily discouraged by wxWidgets. Paulsen's version is probably the best way it could/should of been implemented.
dibridagoe
In need of some credit
In need of some credit
Posts: 2
Joined: Wed Oct 18, 2006 5:27 pm

Post by dibridagoe »

I tried implementing this in c#

Code: Select all

    public class wxBackgroundBitmap : wx.EvtHandler
    {
        protected wx.Bitmap bitmap;
        public wxBackgroundBitmap(wx.Bitmap bitmap)
        {
            this.bitmap = bitmap;
        }
        public new bool ProcessEvent(wx.Event e){
            if(e.EventType == wx.Event.wxEVT_ERASE_BACKGROUND){
                wx.EraseEvent eraseEvent = (wx.EraseEvent)e;
                eraseEvent.DC.DrawBitmap(bitmap,0,0,false);
                return true;
            }
            else return base.ProcessEvent(e);
        }
    }
There is however one problem, which should perhaps be resolved by the developpers of wx.NET
The EvtHandler class has an internal constructor. in the c++ version it's a public constructor, so I think this might have been an error.
Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

Re: Paint background image in any wxWindow, an elegant way

Post by Troels »

Elegant indeed. It inspired the code below, an event handler that fires only once, just when a new window is shown (becomes "stable").

2006-10-14: Stop method (RemoveEventHandler)
2006-10-16: Overloaded constructor (PushEventHandler)
2008-02-13: New version here
http://forums.wxwidgets.org/viewtopic.php?t=17989

Code: Select all

// wxIdleOnceEvtHandler by Troels K 2006
// Thanks to Volker Bartheld and Belgabor.
// define a new handler type that will deal with the wxEVT_IDLE event when it arrives in the queue for the *FIRST* time

extern const wxEventType wxEVT_IDLE_ONCE;
#define EVT_IDLE_ONCE(func) wx__DECLARE_EVT0(wxEVT_IDLE_ONCE, wxIdleEventHandler(func))

class wxIdleOnceEvtHandler : public wxEvtHandler
{
public:
   wxEvtHandler* m_target; 
   wxWindow    * m_target_wnd; 
   wxIdleOnceEvtHandler(wxEvtHandler* target);
   wxIdleOnceEvtHandler(wxWindow    * target);
   virtual ~wxIdleOnceEvtHandler();
   virtual bool ProcessEvent(wxEvent&);
   bool IsFired() const { return !GetEvtHandlerEnabled(); }
   void Stop(void);
};

Code: Select all

const wxEventType wxEVT_IDLE_ONCE = wxNewEventType();

wxIdleOnceEvtHandler::wxIdleOnceEvtHandler(wxEvtHandler* target) : 
   wxEvtHandler(), m_target(target), m_target_wnd(NULL)
{
}

wxIdleOnceEvtHandler::wxIdleOnceEvtHandler(wxWindow* target) : 
   wxEvtHandler(), m_target(target), m_target_wnd(target)
{
   target->PushEventHandler(this); // Push/RemoveEventHandler methods only in wxWindow, not in wxEvtHandler :(
}

wxIdleOnceEvtHandler::~wxIdleOnceEvtHandler()
{
   Stop(); // stop the handler (if not already stopped)
}

// event handle "hook"
bool wxIdleOnceEvtHandler::ProcessEvent(wxEvent& event)
{
   // is it the wxEVT_IDLE and it arrived for the first time (event handler still enabled)?
   if (   (event.GetEventType() == wxEVT_IDLE) 
       && GetEvtHandlerEnabled())
   {
      Stop(); // stop the handler
      wxEvent* temp = event.Clone(); // duplicate the event
      temp->SetEventType(wxEVT_IDLE_ONCE); // set correct event type
      wxPostEvent(m_target, *temp); // and reinsert it into parent's queue
      delete temp;
   } 
   return wxEvtHandler::ProcessEvent(event);
}

void wxIdleOnceEvtHandler::Stop()
{
   if (m_target_wnd && GetEvtHandlerEnabled())
   {
      m_target_wnd->RemoveEventHandler(this);
   }
   SetEvtHandlerEnabled(false);
}

...

MyWindow::MyWindow()
{
   m_idleonce = new wxIdleOnceEvtHandler(this);
}

MyWindow::~MyWindow()
{
   delete m_idleonce;
}

BEGIN_EVENT_TABLE(MyWindow, wxScrolledWindow)
   EVT_IDLE_ONCE(MyWindow::OnIdleOnce)
END_EVENT_TABLE()

void MyWindow::OnIdleOnce(wxIdleEvent&)
{
   OnInitialUpdate(); // or whatever
}
Last edited by Troels on Wed Feb 13, 2008 7:09 pm, edited 8 times in total.
Belgabor
I live to help wx-kind
I live to help wx-kind
Posts: 173
Joined: Mon Sep 25, 2006 1:12 pm

Post by Belgabor »

Wouldn't it be even more elegant if the handler would remove itself once its event has been fired?
Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

Post by Troels »

Belgabor wrote:Wouldn't it be even more elegant if the handler would remove itself once its event has been fired?
Yes, right you are. I've revisited the code (above).

Regards
Troels
mscert
In need of some credit
In need of some credit
Posts: 1
Joined: Sat Dec 02, 2006 6:47 pm

Post by mscert »

I am new to wxWidgets programing.
Using the code Paulsen posted no image seems to be drawn.
Any suggestions?

Thanks in advance!
C

Code: Select all

#include<wx/wx.h>
#include<wx/image.h>
#include"HolyGrail196.xpm"

class MPGame: public wxApp {
   public:
      virtual bool OnInit();
};

class MainFrame: public wxFrame {
   public:
      MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size);
   private:
      DECLARE_EVENT_TABLE()
}; 

// sourced from: http://wxforum.shadonet.com/viewtopic.php?t=10019
class wxBackgroundBitmap : public wxEvtHandler {
   typedef wxEvtHandler Inherited;
   public:
      wxBackgroundBitmap(const wxBitmap &B) : Bitmap(B), wxEvtHandler() { }
      virtual bool ProcessEvent(wxEvent &Event);
   protected:
      wxBitmap Bitmap;
};

// sourced from: http://wxforum.shadonet.com/viewtopic.php?t=10019
bool wxBackgroundBitmap::ProcessEvent(wxEvent &Event) {
   if (Event.GetEventType() == wxEVT_ERASE_BACKGROUND) {
      wxEraseEvent &EraseEvent = dynamic_cast<wxEraseEvent &>(Event);
      wxDC *DC = EraseEvent.GetDC();
      DC->DrawBitmap(Bitmap, 0, 0, false);
      return true;
   } 
   else 
      return Inherited::ProcessEvent(Event);
}

bool MPGame::OnInit() {
   MainFrame *MainWin = new MainFrame("Monty Python Game", wxDefaultPosition, wxSize(800,441));
   MainWin->Show(true);
   SetTopWindow(MainWin);
   return true;
}

MainFrame:: MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
   : wxFrame(  (wxFrame *) NULL, 
	       -1, 
	       title, 
	       pos, 
	       size, 
	       wxDEFAULT_FRAME_STYLE & ~ (wxRESIZE_BORDER | wxRESIZE_BOX | wxMAXIMIZE_BOX))  // frame cannot be resized
{
   wxPanel *panel = new wxPanel(this, wxID_ANY);

   const wxBitmap pic(HolyGrail196_xpm);
   wxBackgroundBitmap *Background = new wxBackgroundBitmap( pic );
   panel->PushEventHandler(Background);
}

BEGIN_EVENT_TABLE( MainFrame, wxFrame )
END_EVENT_TABLE()

IMPLEMENT_APP(MPGame)
flag
In need of some credit
In need of some credit
Posts: 6
Joined: Tue Feb 20, 2007 7:07 pm

set background image

Post by flag »

Yes, this is typical. I also try to set a background image in a wxFrame.
But it does not work. I noticed, that when I make the background image much more greater in size, then, the image will blink for a short time (less then 1second). But then disappeared for ever.

I used code direct from the example, which works on my machine. Sample: "sample\dragimag".

* How to debug such things (GUI)?
* whats wrong?


Code fragments:

Code: Select all


class MyApp : public wxApp
{
public:

  bool TileBitmap(const wxRect& rect, wxDC& dc, wxBitmap& bitmap);
  wxBitmap& GetBackgroundBitmap() const { return (wxBitmap&) m_background; }

  //virtual void OnInitCmdLine(wxCmdLineParser& parser);

  protected:
    wxBitmap m_background;
};



void MyFrame::OnEraseBackground(wxEraseEvent& event)
{
  cerr << "MyFrame::OnEraseBackground...\n";
  if (wxGetApp().GetBackgroundBitmap().Ok()) {
    wxSize sz = GetClientSize();
    wxRect rect(0, 0, sz.x, sz.y);

    if (event.GetDC()) {
      wxGetApp().TileBitmap(rect, *(event.GetDC()), wxGetApp().GetBackgroundBitmap());
    } else {
      wxClientDC dc(this);
      wxGetApp().TileBitmap(rect, dc, wxGetApp().GetBackgroundBitmap());
    }
  } else {
    event.Skip(); // The official way of doing it
  }
}



bool MyApp::TileBitmap(const wxRect& rect, wxDC& dc, wxBitmap& bitmap)
{
  //cerr << "MyApp::TileBitmap...\n";
  int w = bitmap.GetWidth();
  int h = bitmap.GetHeight();

  int i, j;
  for (i = rect.x; i < rect.x + rect.width; i += w) {
    for (j = rect.y; j < rect.y + rect.height; j+= h) {
      dc.DrawBitmap(bitmap, i, j);
    }
  }
  return true;
}



mscert wrote:I am new to wxWidgets programing.
Using the code Paulsen posted no image seems to be drawn.
Any suggestions?

Thanks in advance!
C

Code: Select all

#include<wx/wx.h>
#include<wx/image.h>
#include"HolyGrail196.xpm"

class MPGame: public wxApp {
   public:
      virtual bool OnInit();
};

class MainFrame: public wxFrame {
   public:
      MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size);
   private:
      DECLARE_EVENT_TABLE()
}; 

// sourced from: http://wxforum.shadonet.com/viewtopic.php?t=10019
class wxBackgroundBitmap : public wxEvtHandler {
   typedef wxEvtHandler Inherited;
   public:
      wxBackgroundBitmap(const wxBitmap &B) : Bitmap(B), wxEvtHandler() { }
      virtual bool ProcessEvent(wxEvent &Event);
   protected:
      wxBitmap Bitmap;
};

// sourced from: http://wxforum.shadonet.com/viewtopic.php?t=10019
bool wxBackgroundBitmap::ProcessEvent(wxEvent &Event) {
   if (Event.GetEventType() == wxEVT_ERASE_BACKGROUND) {
      wxEraseEvent &EraseEvent = dynamic_cast<wxEraseEvent &>(Event);
      wxDC *DC = EraseEvent.GetDC();
      DC->DrawBitmap(Bitmap, 0, 0, false);
      return true;
   } 
   else 
      return Inherited::ProcessEvent(Event);
}

bool MPGame::OnInit() {
   MainFrame *MainWin = new MainFrame("Monty Python Game", wxDefaultPosition, wxSize(800,441));
   MainWin->Show(true);
   SetTopWindow(MainWin);
   return true;
}

MainFrame:: MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
   : wxFrame(  (wxFrame *) NULL, 
	       -1, 
	       title, 
	       pos, 
	       size, 
	       wxDEFAULT_FRAME_STYLE & ~ (wxRESIZE_BORDER | wxRESIZE_BOX | wxMAXIMIZE_BOX))  // frame cannot be resized
{
   wxPanel *panel = new wxPanel(this, wxID_ANY);

   const wxBitmap pic(HolyGrail196_xpm);
   wxBackgroundBitmap *Background = new wxBackgroundBitmap( pic );
   panel->PushEventHandler(Background);
}

BEGIN_EVENT_TABLE( MainFrame, wxFrame )
END_EVENT_TABLE()

IMPLEMENT_APP(MPGame)
priyank_bolia
wxWorld Domination!
wxWorld Domination!
Posts: 1339
Joined: Wed Aug 03, 2005 8:10 am
Location: BANGALORE, INDIA
Contact:

Post by priyank_bolia »

* How to debug such things (GUI)?
What I use to do is to close all the unnecessary panels of the debugger, i.e. Visual Studio then resize it to take the first vertical half of the screen, and then resize the application to take the other half of the screen. This way, when a break point is reached in the debugger the application screen will not be overlapped and when you come out of the debugger, a paint event will not be fired by the application. I think this should be clear to understand, though my English is not that good.
flag
In need of some credit
In need of some credit
Posts: 6
Joined: Tue Feb 20, 2007 7:07 pm

Post by flag »

Yes, but I dont have and I dont use VisualC/Debugger.

Anyway, the Problem is solved now. What I did is draw the background, then a panel, on this panel are buttons. So, the background was removed / not seen on the window because of the panel.
The idea was, to have a full-screen-panel for wxFrame or wxWindows just to draw a background (can also use setBackgroundColour.)
:shock:

priyank_bolia wrote:
* How to debug such things (GUI)?
What I use to do is to close all the unnecessary panels of the debugger, i.e. Visual Studio then resize it to take the first vertical half of the screen, and then resize the application to take the other half of the screen. This way, when a break point is reached in the debugger the application screen will not be overlapped and when you come out of the debugger, a paint event will not be fired by the application. I think this should be clear to understand, though my English is not that good.
jett
Earned a small fee
Earned a small fee
Posts: 10
Joined: Mon Jul 09, 2007 10:18 am

Post by jett »

I'm trying out Paulsen's code above but upon running it, I am getting a Segmentation fault error on the following line?

wxEraseEvent &EraseEvent = dynamic_cast<wxEraseEvent &>(Event);

I called the function after the GUI controls were created on the frame constructor

wxBackgroundBitmap *ToolBarBackground = new wxBackgroundBitmap(wxBitmap("C:\\lcdres.bmp", wxBITMAP_TYPE_BMP));

WxToolBar1->PushEventHandler(ToolBarBackground);

Any ideas on what is going on?

Thanks in advance
Sof_T
Can't get richer than this
Can't get richer than this
Posts: 864
Joined: Thu Jul 28, 2005 9:48 pm
Location: New Forest, United Kingdom
Contact:

Post by Sof_T »

After a long time of meaning to implement this code I have finally tried it and I also had problems with the dynamic cast. I found that the following code works for me.

Code: Select all

bool wxBackgroundBitmap::ProcessEvent(wxEvent &Event)
{
    if (Event.GetEventType() == wxEVT_ERASE_BACKGROUND)
    {
        wxEraseEvent *EraseEvent = wxDynamicCast(&Event,wxEraseEvent);
        if(EraseEvent)
        {
            wxDC *DC = EraseEvent->GetDC();
            DC->DrawBitmap(Bitmap, 0, 0, false);
            return true;
        }
        else
            return Inherited::ProcessEvent(Event);
    }
    else
        return Inherited::ProcessEvent(Event);
}
Sof.T
The home of Sof.T http://www.sof-t.site88.net/
Author of Programming with wxDevC++
http://sourceforge.net/projects/wxdevcpp-book/
priyank_bolia
wxWorld Domination!
wxWorld Domination!
Posts: 1339
Joined: Wed Aug 03, 2005 8:10 am
Location: BANGALORE, INDIA
Contact:

Post by priyank_bolia »

One question, why not handling the wxEraseEvent event directly.
Sof_T
Can't get richer than this
Can't get richer than this
Posts: 864
Joined: Thu Jul 28, 2005 9:48 pm
Location: New Forest, United Kingdom
Contact:

Post by Sof_T »

Because this gives you one class that can easily be plugged into standard wxWindow derived classes such as wxPanel with two lines of code rather than rewriting a new function or deriving a new class every time.

I have revisited the code above to implement stretching

Code: Select all

bool wxBackgroundBitmap::ProcessEvent(wxEvent &Event)
{
    if(Event.GetEventType() == wxEVT_ERASE_BACKGROUND)
    {
        if(Bitmap.IsOk())
        {
        }
        else
            Event.Skip();
    }
    else if(Event.GetEventType() == wxEVT_PAINT)
    {
        bool TransactionIsOk = false;
        if(Bitmap.IsOk())
        {
            wxWindow * TempWindow = wxDynamicCast(Event.GetEventObject(),wxWindow);
            if(TempWindow)
            {
                wxBufferedPaintDC DC(TempWindow);
                int w, h;
                TempWindow->GetClientSize(&w, &h);
                wxImage TempImage = Bitmap.ConvertToImage();
                TempImage.Rescale(w,h);
                DC.DrawBitmap(wxBitmap(TempImage), 0, 0, false);
                TransactionIsOk = true;
            }
        }
        if(TransactionIsOk == false)
            Event.Skip();
    }
    else if(Event.GetEventType() ==  wxEVT_SIZE)
    {
        wxWindow * TempWindow = wxDynamicCast(Event.GetEventObject(),wxWindow);
        if(TempWindow)
        {
            TempWindow->Refresh();
        }
        Event.Skip();
    }
    else
        return Inherited::ProcessEvent(Event);
    return true;
}
Does anyone know if this should be destroyed with PopEventHandler(true); when the window is closed. I guess it should.

Sof.T
The home of Sof.T http://www.sof-t.site88.net/
Author of Programming with wxDevC++
http://sourceforge.net/projects/wxdevcpp-book/
Post Reply