Drawing with a buffered bitmap. A complete sample code.

Hi all,

There are regulary posts asking how to draw under wxPython.
But, most of the questions are about "flickering" and slow
redrawing. The problem is due to the fact, most users are
preparing their drawing directly in an OnPaint event and are
not using a buffered bitmap.
I propose here a complete application explaining how to draw
using a buffered bitmap.I tried to comment it a lot.
The demo has a nice example on how to do this, but it
is a lillte dry for newbies.
I wrote this code under my win98se platform, but it should
be ok under linux. python 2.2.3. Tested with wxPython
2.4.0.7 and 2.4.1.2.

Notes for experimented wxPython users:
- Correction(s) are welcome
- I'm using a dc.DrawBitmap() instead of dc.Blit() method.
I think the time penalty is small, and it makes the code
clearer.
- I'm not using wxBufferedPaintDC(). In that code, it may
not work properly.
- I'm still using the old wx style.
- If somebody want to correct my english and create a wiki
page. Nice.

Now the code (~200 lines)

···

#-----------------------------------------------------------
--------
# CommentedDrawing.py
# Development: windows 98se, Python 2.2.3, wxPython 2.4.0.7
# also ok under wxPython 2.4.1.2
# 17 July 2003
# Jean-Michel Fauth, Switzerland
#-----------------------------------------------------------
--------

from wxPython.wx import *
from time import asctime

#-----------------------------------------------------------
--------

def jmtime():
    return '[' + asctime()[11:19] + '] '

#-----------------------------------------------------------
--------

# The control/widget containing the drawing. It is white and
has no border. It
# is not necessary to defined its positon and size, since
these parameters are
# set up by the layout constraints mechanism. However, I
forced the control
# to have no border.
class MyDrawingArea(wxWindow):

    def __init__(self, parent, id):
        sty = wxNO_BORDER
        wxWindow.__init__(self, parent, id, style=sty)
        self.parent = parent
        self.SetBackgroundColour(wxWHITE)
        self.SetCursor(wxCROSS_CURSOR)

        # Some initalisation, just to reminds the user that
a variable
        # called self.BufferBmp exists. See self.OnSize().
        self.BufferBmp = None

        EVT_SIZE(self, self.OnSize)
        EVT_PAINT(self, self.OnPaint)

    # OnSize is fired at the application start or when the
frame is resized.
    # The OnSize event is fired BEFORE the OnPaint event.
wxWindows
    # handles the events in this order. This is not due to
the fact,
    # that the code line EVT_SIZE(...) is placed before the
line
    # EVT_PAINT(...).
    # The procedure OnSize() is the right place to define
the
    # BufferBmp and its size. self.BufferBmp is the picture
in memory,
    # which contains your drawing. self.BufferBmp is also
used as a flag,
    # a None value indicates no picture.
    #
    def OnSize(self, event):
        print jmtime() + 'OnSize in MyDrawingArea'
        # Get the size of the drawing area in pixels.
        self.wi, self.he = self.GetSizeTuple()
        # Create BufferBmp and set the same size as the
drawing area.
        self.BufferBmp = wxEmptyBitmap(self.wi, self.he)
        memdc = wxMemoryDC()
        memdc.SelectObject(self.BufferBmp)
        # Drawing job
        ret = self.DoSomeDrawing(memdc)
        if not ret: #error
            self.BufferBmp = None
            wxMessageBox('Error in drawing',
'CommentedDrawing', wxOK | wxICON_EXCLAMATION)
        #memdc.SelectObject(wxNullBitmap) #is this
important?

    # OnPaint is executed at the app start, when resizing or
when
    # the application windows becomes active. OnPaint copies
the
    # buffered picture, instead of preparing (again) the
drawing job.
    # This is the trick, copying is very fast.
    # Note: if you forget to define the dc in this
procedure,
    # (no dc = ... code line), the application will run in
    # an infinite loop. This is a common beginner's error.
(I think)
    # BeginDrawing() and EndDrawing() are for windows
platforms (see doc).
    def OnPaint(self, event):
        print jmtime() + 'OnPaint in MyDrawingArea'
        dc = wxPaintDC(self)
        dc.BeginDrawing()
        if self.BufferBmp != None:
            print jmtime() + '...drawing'
            dc.DrawBitmap(self.BufferBmp, 0, 0, True)
        else:
            print jmtime() + '...nothing to draw'
        dc.EndDrawing()

    # The function defines the drawing job. Everything is
drawn on the dc.
    # In that application, the dc corresponds to the
BufferBmp.
    # Three things are drawn, a square with a fixed size,
and two
    # rectangles with sizes determined by the size of the
dc, that
    # means the size of the drawing area. Keep in mind, the
size
    # of the drawing area depends on the size of the main
frame,
    # which can be resized on the fly with your mouse.
    # At this point, I will introduce a small complication,
that is
    # in fact quite practical. It may happen, the drawing is
not
    # working correctly. Either there is an error in the
drawing job
    # or the data you want to plot can not be drawn
correctly. A typical
    # example is the plotting of 'scientific data'. The data
are not (or
    # may not be) scaled correctly, that leads to errors,
generally integer
    # overflow errors.
    # To circumvent this, the procedure returns True, if the
drawing succeed.
    # It returns False, if the drawing fails. The returned
value may be used
    # later.
    def DoSomeDrawing(self, dc):
        try:
            print jmtime() + 'DoSomeDrawing in
MyDrawingArea'

            dc.BeginDrawing()

            #~ raise OverflowError #for test

            # Clear everything
            dc.SetBrush(wxBrush(wxWHITE, wxSOLID))
            dc.Clear()

            # Draw the square with a fixed size.
            dc.SetBrush(wxBrush(wxCYAN, wxSOLID))
            dc.SetPen(wxPen(wxBLUE, 1, wxSOLID))
            dc.DrawRectangle(10, 10, 200, 200)

            # Draw a transparent rectangle with a red
border, proportional to
            # the dc size.
            dcwi, dche = dc.GetSizeTuple()
            dc.SetBrush(wxBrush(wxCYAN, wxTRANSPARENT))
            dc.SetPen(wxPen(wxRED, 1, wxSOLID))
            dc.DrawRectangle(0, 0, dcwi, dche)

            # Draw one another rectangle, a rectangle with a
size proportional
            # to the dc size.
            gap = 50
            dc.SetBrush(wxBrush(wxWHITE, wxTRANSPARENT))
            dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
            dc.DrawRectangle(0 + gap, 0 + gap, dcwi - 2 *
gap, dche - 2 * gap)

            # These 2 next line2 will raise an overflow
error.
            #~ largeval = 1e10
            #~ dc.DrawLine(dcwi // 2, dche // 2, largeval,
largeval)

            dc.EndDrawing()
            return True

        except:
            return False

#-----------------------------------------------------------
--------

# Panel in the main frame. It covers automatically the
client area of
# its parent frame. The panel contains a single control
(class MyDrawingArea),
# on which the drawing takes place. The position and size of
this control is
# set up with layout constraints, so that the user see what
happens to the
# drawing when the main frame is resized.
class MyPanel(wxPanel):

    def __init__(self, parent, id):
        wxPanel.__init__(self, parent, id,
wxDefaultPosition, wxDefaultSize)

        self.drawingarea = MyDrawingArea(self, -1)

        self.SetAutoLayout(True)

        gap = 30 #in pixels
        lc = wxLayoutConstraints()
        lc.top.SameAs(self, wxTop, gap)
        lc.left.SameAs(self, wxLeft, gap)
        lc.right.SameAs(self, wxWidth, gap)
        lc.bottom.SameAs(self, wxBottom, gap)
        self.drawingarea.SetConstraints(lc)

#-----------------------------------------------------------
--------

# Usual frame. Can be resized, maximized and minimized.
# The frame contains one panel.
class MyFrame(wxFrame):

    def __init__(self, parent, id):
        wxFrame.__init__(self, parent, id,
'CommentedDrawing', wxPoint(0, 0), wxSize(500, 400))
        self.panel = MyPanel(self, -1)

        EVT_CLOSE(self, self.OnCloseWindow)

    def OnCloseWindow(self, event):
        print jmtime() + 'OnCloseWindow in MyFrame'
        self.Destroy()

#-----------------------------------------------------------
--------

class MyApp(wxApp):

    def OnInit(self):
        frame = MyFrame(None, -1)
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

#-----------------------------------------------------------
--------

def main():
    print 'main is running...'
    app = MyApp(0)
    app.MainLoop()

#-----------------------------------------------------------
--------

if __name__ == "__main__" :
    main()

#eof--------------------------------------------------------
-----------

Have fun.
Jean-Michel Fauth, Switzerland

Jean-Michel Fauth wrote:

Hi all,

There are regulary posts asking how to draw under wxPython.
But, most of the questions are about "flickering" and slow
redrawing. The problem is due to the fact, most users are
preparing their drawing directly in an OnPaint event and are
not using a buffered bitmap.
I propose here a complete application explaining how to draw
using a buffered bitmap.I tried to comment it a lot.
The demo has a nice example on how to do this, but it
is a lillte dry for newbies.
I wrote this code under my win98se platform, but it should
be ok under linux. python 2.2.3. Tested with wxPython
2.4.0.7 and 2.4.1.2.

Notes for experimented wxPython users:
- Correction(s) are welcome
- I'm using a dc.DrawBitmap() instead of dc.Blit() method.
I think the time penalty is small, and it makes the code
clearer.
- I'm not using wxBufferedPaintDC(). In that code, it may
not work properly.
- I'm still using the old wx style.
- If somebody want to correct my english and create a wiki
page. Nice.

Now the code (~200 lines)

Please send again as an attachment so the lines don't get wrapped.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Jean-Michel Fauth wrote:

Hi all,

There are regulary posts asking how to draw under wxPython.
But, most of the questions are about "flickering" and slow
redrawing. The problem is due to the fact, most users are
preparing their drawing directly in an OnPaint event and are
not using a buffered bitmap.

Yes and no. Often the flickering is due to re-drawing with a PaintDC
outside of a Paint event, when a ClientDC should be used. However,
double buffering is nice anyway.

I propose here a complete application explaining how to draw
using a buffered bitmap.

Did you see the existing wiki page on double buffered drawing?

http://wiki.wxpython.org/index.cgi/DoubleBufferedDrawing

It does much of the same thing. The difference is that my version is
designed to be subclassed, and your version provides a nice demo fo the
drawing updateing itself to the size of the DC that it is passed.

I see that Robin put yours in the Wiki. It doesn't hurt to have multiple
demos, but perhaps it would be better to merge the two for a less
confusing presentation.

Also, if you didn't see the existing example, it points out that we need
to better advertise the Wiki, or better organize it, or something!

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Chris Barker wrote:

I see that Robin put yours in the Wiki. It doesn't hurt to have multiple
demos, but perhaps it would be better to merge the two for a less
confusing presentation.

Feel free. Or perhaps add links that point them at each other.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

I'm not sure if this is a bug or not.

I have:

wx.Frame, holding
   wx.SplitterWindow (horiz), holding (on right hand side)
    wx.Notebook (w/attached wxNotebookSizer), holding 1 or more pages of
      wx.Panel (with wxBoxSizer) holding
        wx.StyledTextControl

Overall, it works good. However, if I create the wxNotebook with the
NB_MULTILINE flag, things get a little weird when I select the second
tier of tabs. The STC covers everything but the very top frame.

To ME, this looks like maybe a glitch in wxNotebookSizer. I *have* tried
some novel things to force a refresh and resize of the STC upon changing
tabs, but nothing's helped so far.

Any thoughts? Need source? (it's a bit involved but I can probably
weed out the extraneous stuff if absolutely needed)

Jeff Grimmett wrote:

Overall, it works good. However, if I create the wxNotebook with the
NB_MULTILINE flag, things get a little weird when I select the second
tier of tabs. The STC covers everything but the very top frame.

To ME, this looks like maybe a glitch in wxNotebookSizer.

It's a known bug in wxNotebook that can happen even without the notebook sizer. So far there is no known solution, other than to not use the wxNB_MULTILINE style.

BTW, the wxNotebookSizer does not do anything with the size/position of the notebook pages, other than to query their minsize for use if the notebook sizer is to be embedded in another sizer.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

It's a known bug in wxNotebook that can happen even without
the notebook
sizer. So far there is no known solution, other than to not use the
wxNB_MULTILINE style.

Gah, I suspected that was going to be the answer, but one can only hope!
Pity that. It looks really good right up to the point that you click on
that second tier :slight_smile:

BTW, the wxNotebookSizer does not do anything with the
size/position of
the notebook pages, other than to query their minsize for use if the
notebook sizer is to be embedded in another sizer.

Yeah, but I figured if I didn't mention it I'd hear about it :slight_smile: