Paint event usage

Thank you for reading this.

I'm attempting to convert some code that I wrote some time ago to wxPython and what follows is just the bare bones, mostly lifted from a demo example.

My Display() procedure will be called from a timer event (haven't sorted that out yet) periodically. How do I link the OnPaint event with Display()? Is there an Update() procedure?

import wx
import numpy

board_size = 20

class Mywin(wx.Frame):

 def \_\_init\_\_\(self, parent, title\):
     super\(Mywin, self\)\.\_\_init\_\_\(parent, title = title,size = \(500,300\)\)
     self\.InitUI\(\)

 def InitUI\(self\):
     self\.Bind\(wx\.EVT\_PAINT, self\.OnPaint\)
     self\.Centre\(\)
     self\.Show\(True\)

 def OnPaint\(self, e\):
     dc = wx\.PaintDC\(self\)
     brush = wx\.Brush\("white"\)
     dc\.SetBackground\(brush\)
     dc\.Clear\(\)

board = numpy.zeros(board_size * board_size, dtype='i').reshape(board_size, board_size)

board [1][1] = 1
board [1][3] = 1

def Display():
for x in range(board_size):
for y in range(board_size):

         if \(board\[y\]\[x\]\) == 0:
             \#draw a green box
             wx\.pen = wx\.Pen\(wx\.Colour\(0,0,0\)\)
             wx\.dc\.SetPen\(pen\)
             wx\.dc\.SetBrush\(wx\.Brush\(wx\.Colour\(0,255,0\), wx\.SOLID\)\)
             wx\.dc\.DrawRectangle\(10, 10, 10, 10\)

         else:
             \#draw a white box
             wx\.pen = wx\.Pen\(wx\.Colour\(0,0,0\)\)
             wx\.dc\.SetPen\(pen\)
             wx\.dc\.SetBrush\(wx\.Brush\(wx\.Colour\(0,0,0\), wx\.SOLID\)\)
             wx\.dc\.DrawRectangle\(10, 10, 10, 10\)

ex = wx.App()
Mywin(None,'Drawing demo')
ex.MainLoop()

Display()

···

--

Regards,
Phil

Your code has a couple of problems. First, your Display function is not going to get called; wx.App,MainLoop does not return until the last window is closed and the application is exiting. Second, code like
   wx.pen = wx.Pen(wx.Colour(0,0,0))
is not correct. That's creating a variable inside the wx module, which is not attached to any window. You want to make the Display function a method within your Mywin class. Next, you are always drawing your rectangles at x=10, y=10, whereas I suspect you wanted them to be laid out in a checkerboard. Consider:

    def Display(self):
        dc = wx.ClientDC(self)
        green = wx.GREEN_BRUSH
        white = wx.WHITE_BRUSH
        dc.SetPen( wx.BLACK_PEN )
        for x in range(board_size):
            for y in range(board_size):
                dc.SetBrush( green if board[y] == 0 else white )
                dc.DrawRectangle( x*10, y*10, 10, 10 ).

You'll still have to decide what's going to trigger that call. Do you want that to be done during every paint? If so, then just call self.Display() at the end of Paint.

···

On May 22, 2019, at 9:47 PM, Phil <phillor9@gmail.com> wrote:

I'm attempting to convert some code that I wrote some time ago to wxPython and what follows is just the bare bones, mostly lifted from a demo example.

My Display() procedure will be called from a timer event (haven't sorted that out yet) periodically. How do I link the OnPaint event with Display()? Is there an Update() procedure?


Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Your code has a couple of problems. First, your Display function is not going to get called; wx.App,MainLoop does not return until the last window is closed and the application is exiting.

Thank you Tim for your informative reply. I had Display() (not the real thing just a test case) originally within the class structure but then I wondered how a timer event would trigger it: I think I can see how to do that now.

You'll still have to decide what's going to trigger that call. Do you want that to be done during every paint? If so, then just call self.Display() at the end of Paint.

No, the timer event will call Display() and I was wondering how the paint event will know that the window needs to be repainted. Other GUI libraries that I've played with in the past use an Update() procedure.

···

On 23/5/19 4:37 pm, Tim Roberts wrote:

--

Regards,
Phil

"Update" usually demands that the window be painted immediately. "Refresh" marks the window as dirty, so that a repaint event will be generated.

···

On May 23, 2019, at 6:07 PM, Phil <phillor9@gmail.com> wrote:

No, the timer event will call Display() and I was wondering how the paint event will know that the window needs to be repainted. Other GUI libraries that I've played with in the past use an Update() procedure.


Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

"Update" usually demands that the window be painted immediately. "Refresh" marks the window as dirty, so that a repaint event will be generated.

Thank you for your indulgence, just one last question.

Play() is called every five seconds, no problems there. The problem is that Display() runs independently of the timer and displays the board at the system's maximum speed instead of every five seconds.

I've tried restarting the timer after display() has finished and I've tried Update() and Refresh(). Something's missing but I just cannot see what it is.

I've removed the board update calculations so that what remain is readable. The code runs perfectly except that the display is updated too quickly.

import wx
import numpy
import time

class Mywin(wx.Frame):

 def \_\_init\_\_\(self, parent, title\):
     super\(Mywin, self\)\.\_\_init\_\_\(parent, title = title,size = \(500,300\)\)

     self\.timer = wx\.Timer\(self\)
     self\.Bind\(wx\.EVT\_TIMER, self\.Play\)
     self\.timer\.Start\(5000, oneShot = False\)

     self\.InitUI\(\)

 def InitUI\(self\):
     self\.Bind\(wx\.EVT\_PAINT, self\.OnPaint\)
     self\.Centre\(\)
     self\.Show\(True\)

 def OnPaint\(self, e\):
     dc = wx\.PaintDC\(self\)
     brush = wx\.Brush\(&quot;white&quot;\)
     dc\.SetBackground\(brush\)
     dc\.Clear\(\)

 def Play\(self, event\):
     self\.Display\(self\)

 def Display\(self, event\):
     dc = wx\.ClientDC\(self\)
     green = wx\.GREEN\_BRUSH
     white = wx\.WHITE\_BRUSH
     dc\.SetPen\(wx\.BLACK\_PEN\)

     for x in range\(board\_size\):
         for y in range\(board\_size\):
             dc\.SetBrush\(white if self\.board\[y\]\[x\] == 0 else green\)
             dc\.DrawRectangle\(x \* 10, y \* 10, 10, 10\)

ex = wx.App()
Mywin(None,'Drawing demo')
ex.MainLoop()

···

On 24/5/19 4:47 pm, Tim Roberts wrote:


Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

--
Regards,
Phil

Phil wrote:

Play() is called every five seconds, no problems there. The problem is that Display() runs independently of the timer and displays the board at the system's maximum speed instead of every five seconds.

I've tried restarting the timer after display() has finished and I've tried Update() and Refresh(). Something's missing but I just cannot see what it is.

I've removed the board update calculations so that what remain is readable. The code runs perfectly except that the display is updated too quickly.

That certainly doesn't happen with the code you posted. After I defined "board_size" and added code to create self.board, it all seemed to do what you want. Play is called every 5 seconds, it calls Display, and the app goes idle. This code isn't what I suggested, however. You're doing nothing in OnPaint except erasing the window.

There is one way you could trigger the behavior you describe. IF your OnPaint handler just called Display instead of the code you showed here, it would trigger infinitely fast repaints. Why? Because Display uses wx.ClientDC. OnPaint is called any time the window's "dirty" flag is set. In order to clear the "dirty" flag, you have to create a wx.PaintDC. If you don't clear the "dirty" flag, when OnPaint returns it will immediately be called again. So if you had:

 def OnPaint\( self, e \):
     Display\( self, e \)

That would do what you describe. What you'd need to do is something like this:

 def OnPaint\( self, e \):
     dc = wx\.PaintDC\(self\)
     self\.Display\( dc \)

 def Display\( self, dc=None \):
     if dc is None:
         dc = wx\.ClientDC\(self\)
     \.\.\.
···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Please forgive my slowness in this matter.

The code that I previously posted definitely did work except that the display wasn't updated at the five second rate. I have included the code provided but now the board is never displayed and Display() is only called (but nothing is displayed) if the window is resized.

This is the most recent version, this time showing the definition of the board:

import wx
import numpy
import time

board_size = 20

dead = 0
live = 1

class Mywin(wx.Frame):

 def \_\_init\_\_\(self, parent, title\):
     super\(Mywin, self\)\.\_\_init\_\_\(parent, title = title,size = \(500,300\)\)

     self\.timer = wx\.Timer\(self\)
     self\.Bind\(wx\.EVT\_TIMER, self\.Play\)
     self\.timer\.Start\(5000, oneShot = False\)

     self\.board = numpy\.zeros\(board\_size \* board\_size, dtype=&#39;i&#39;\)\.reshape\(board\_size, board\_size\)
     self\.next\_board = numpy\.zeros\(board\_size \* board\_size, dtype=&#39;i&#39;\)\.reshape\(board\_size, board\_size\)

     self\.board \[1\]\[1\] = dead
     self\.board \[1\]\[2\] = live
     self\.board \[1\]\[3\] = dead

     self\.board \[2\]\[1\] = dead
     self\.board \[2\]\[2\] = dead
     self\.board \[2\]\[3\] = live

     self\.board \[3\]\[1\] = live
     self\.board \[3\]\[2\] = live
     self\.board \[3\]\[3\] = live

     self\.InitUI\(\)

  def InitUI\(self\):
     self\.Bind\(wx\.EVT\_PAINT, self\.OnPaint\)
     self\.Centre\(\)
     self\.Show\(True\)

 def OnPaint\(self, e\):
     dc = wx\.PaintDC\(self\)
     brush = wx\.Brush\(&quot;white&quot;\)
     dc\.SetBackground\(brush\)
     dc\.Clear\(\)
     self\.Display\(dc\)

 def Play\(self, event\):

'''
code that manipulates the board has been removed to aid readability.
The board should be displayed each time the timer event is fired.
'''
#self.Display(self)
self.Refresh()
self.Update()

 \#def Display\(self, event\):
 def Display\(self, dc = None\):
     if dc is None:
         dc = wx\.ClientDC\(self\)
     \#dc = wx\.ClientDC\(self\)
         green = wx\.GREEN\_BRUSH
         white = wx\.WHITE\_BRUSH
         dc\.SetPen\(wx\.BLACK\_PEN\)

         for x in range\(board\_size\):
             for y in range\(board\_size\):
                 dc\.SetBrush\(white if self\.board\[y\]\[x\] == 0 else green\)
                 dc\.DrawRectangle\(x \* 10, y \* 10, 10, 10\)

ex = wx.App()
Mywin(None,'Drawing demo')
ex.MainLoop()

···

On 25/5/19 11:27 am, Tim Roberts wrote:

That would do what you describe. What you'd need to do is something like this:

def OnPaint\( self, e \):
    dc = wx\.PaintDC\(self\)
    self\.Display\( dc \)

def Display\( self, dc=None \):
    if dc is None:
        dc = wx\.ClientDC\(self\)
    \.\.\.

--
Regards,
Phil

Right. I think you meant to add this to Mywin.__init__:

        self.Bind( wx.EVT_PAINT, self.OnPaint )

···

On May 25, 2019, at 10:29 PM, Phil <phillor9@gmail.com> wrote:

The code that I previously posted definitely did work except that the display wasn't updated at the five second rate. I have included the code provided but now the board is never displayed and Display() is only called (but nothing is displayed) if the window is resized.


Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Thank you Tim for your generosity.

That has the effect of now calling Display() twice when the Window starts but still nothing is displayed. If I add self.Refresh and self.Update() (I have to add both or else nothing happens) to the end of Play() then Display() is called continuously but still nothing is is displayed.

There now seem to be an error of some kind in Display().

···

On 26/5/19 4:29 pm, Tim Roberts wrote:

self.Bind( wx.EVT_PAINT, self.OnPaint )

--
Regards,
Phil

Making a “Game of Life”?

First – when you include code (more than a snipped) it’s better to include it as an attached file – putting it inline in the email can make a mess of indentation,etc.

Second: it is recommended to do all drawing in a Paint Event ( use of ClientDC is reserved for special occasions when you really need to draw NOW). So your Paint event handler should call your actual drawing code, passing in a DC to draw to.

When you want to trigger a re-draw for some other reason than a system Paint event, you can call:

.Refresh() # lets the Window know it needs to be repainted

.Update() # forces a Paint event.

My linter was screaming at me – lots of code that doesn’t conform to PEP8 – that’s not critical, but it is good to pick a style and use it consistently. I recommend PEP8 and having a linter in your IDE / Editor.

Enclosed is a refactored version that works and has much cleaner style :slight_smile: – I cleaned up a number of other code structure / style issues, including numpy ones :slight_smile: – I couldn’t help myself.

You may want to take a look at these examples:

https://github.com/PythonCHB/wxPythonDemos

Some of them are pretty old an need cleaning up (PR’s accepted!) – but there’s some good stuff in there.

The Sudoku one may give you ideas for this project.

-CHB

display_test.py (1.69 KB)

···

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (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

Thank you Chris for your reply I was beginning that I'd done my dash by asking too many questions.

Anyway, yes my question did relate to my version of Conway's game of life. I found the reason why OnPaint() was being repeatedly called and that was because I had a while loop counting the generations. I feel a bit silly that I didn't see that sooner.

I now have Wing IDE installed and have used pep8 to tidy my poor coding style.

I didn't know that attachments were allowed until a few days ago.

Finally, I found that I didn't need to call Refresh() or Update() to have the window update smoothly, although there is an occasional flicker.

···

On 3/6/19 8:39 am, 'Chris Barker' via wxPython-users wrote:

Making a "Game of Life"?

--
Regards,
Phil