wxwidgets / wxpython drawing problems with onPaint event
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
wxwidgets / wxpython drawing problems with onPaint event
Hi there
This is a question for both the wxwidgets and wxpythong crowd, since my problem probably stems from not understanding how wxwidgets work (and because wxpython is based on wxwidgets)
I am writing a python program in which I want to draw several circles (filled, actually musical notes) to a wx.PaintDC, then wait some seconds and then display some other circles and so on.
Now the problem is of course that the frame gets wiped out by the refresh immediately, so I need to use (and register) the onPaint event, which I do. (I am pretty sure that is the same in Python and C for WX) But putting the code in there creates a total mess. The onPaint should only refresh what is currently displayed, not run the code displaying some circles, waiting 3 seconds, then erasing everything, displaying some other circles and then wait another 3 and go on.
How can I create a frame (and panel) that I can draw to, erase it as wanted and that stays displayed (meaning that it automatically gets refreshed?)
I have tried out working with a memoryDC, in which I draw it there and then blit it to the normal dc with the onPaint function. It works fine, if I only draw one set of circles, but when I use time.sleep(3) to wait, nothing gets drawn (even though I call the blit manually) and only the last version gets displayed.
I would very much like to avoid onPaint at all, since I don't see the need for it and separating the event for painting and the code generating the drawing is complicated and error prone as I found.
Markus
Last edited by browny_amiga; 02-11-2009 at 01:31 PM.
AFAICS you cannot do without an onPaint event handler, since it gets called by the system when your window is shown again after e.g. some other window had partly covered it.
So to solve this drawing-with-delays, I think you could maintain a python list where you append the circle positions as (x,y) tuples or so. And then in the onPaint iterate through the list and draw the circles.
Then instead of sleep(3) use a timer with 3 secs delay. The timer will call you OnTimer method and there you append the new circle positions to the list, and call self.refresh().
that is somewhere to start. I have not worked with timers yet, so I so far fail to understand what they are for at all. (main purpose) But will look into it.
I am having another issue with that I have a frame that has buttons and stuff inside (main menu) and then I need to clean it all up to go to the section that the user chose with the button. So far I am doing this with frame.DestroyChildren(), but this might not be the proper way to do it. After I destroy everything like this, I add on the menus and relevant buttons again.
that is somewhere to start. I have not worked with timers yet, so I so far fail to understand what they are for at all. (main purpose) But will look into it.
With a sleep() your program really pauses and does not continue to run. Within the sleep-time the user might want to click some button or menu. The action your program should then take is delayed at least until the sleep() is done. Imagine you click a menu-item or button, but the program takes 3 seconds to react to that...
Or your window is covered and revealed within the sleep time. The contents of your window will then remain 'damaged' or not visible at all until the sleep() is over and your program runs again and the onpaint() function is called.
Not so with a timer. By setting a timer you tell the system: "Call this function in 3 seconds". After just telling that, your program continues to run: reacting to user actions, repaint the windows and so on. Then after 3 seconds the function will be run (make sure it does not take too long) automatically.
See?
Another way of achieving the same effect is running another thread which is sleeping and take some action after that. But threads are less easy to handle and understand correctly and it gets messy more easily.
Quote:
Originally Posted by browny_amiga
I am having another issue with that I have a frame that has buttons and stuff inside (main menu) and then I need to clean it all up to go to the section that the user chose with the button. So far I am doing this with frame.DestroyChildren(), but this might not be the proper way to do it. After I destroy everything like this, I add on the menus and relevant buttons again.
You could use two or more panels in the same window and hide one behind the other.
Here's a complete example program that implements a demo of the idea:
Code:
#!/usr/bin/env python
import wx
import random
class MainWindow(wx.Frame):
def __init__(self, parent, title, size=wx.DefaultSize):
wx.Frame.__init__(self, parent, wx.ID_ANY, title, wx.DefaultPosition, size)
self.circles = list()
self.displaceX = 30
self.displaceY = 30
self.timer = wx.Timer(self)
self.timer.Start(1000) # 1000 milliseconds = 1 second
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.Bind(wx.EVT_LEFT_UP, self.OnClick)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, e):
print "OnPaint called"
dc = wx.PaintDC(self)
dc.SetPen(wx.Pen(wx.BLUE))
dc.SetBrush(wx.Brush(wx.BLUE))
# Go through the list of circles to draw all of them
for circle in self.circles:
dc.DrawCircle(circle[0], circle[1], 10)
def OnTimer(self, e):
print "OnTimer called"
circlePos = (self.displaceX, self.displaceY)
self.circles.append(circlePos)
# Change position of the next circle that
# we want to append to the list next time
windowSize = self.GetClientSizeTuple()
maxX = windowSize[0] - 30
maxY = windowSize[1] - 30
self.displaceX += 40
if self.displaceX > maxX:
self.displaceX = 30
self.displaceY += 40
if self.displaceY > maxY:
self.timer.Stop()
print "Timer Stopped"
self.Refresh()
def OnClick(self, e):
print "Window clicked"
# Do something here to show the click was received.
# Here we remove a random circle.
n = len(self.circles)
if n <= 1: # then dont do it
return
i = random.randrange(n)
del self.circles[i]
print "Removed %dth circle" % (i,)
self.Refresh()
def main():
app = wx.App()
win = MainWindow(None, "Draw delayed circles", size=(620,460))
win.Show()
app.MainLoop()
if __name__ == "__main__":
random.seed()
main()
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.