Introduction
This recipe provides a simple double buffered window that can be sub-classed to do any customized drawing you might need, without worrying about Paint events, etc.
What Is Double Buffering?
Double buffering is storing the contents of a window in memory (the buffer), so that the screen can be easily refreshed without having to re-draw the whole thing.
Why double Buffer?
Whenever a window displayed on the screen gets damaged, by, for instance, a dialog box being displayed over it, the system asks the program to re-draw it. This is indicated by a Paint event, and is handled in a Paint event handler. Usually the Paint event handler creates a wx.PaintDC, and calls code that re-draws the window. See CustomisedDrawing and WxHowtoDrawing for an example, as well as assorted code in the wxPython demo.
This works fine for simple drawings, but if you have a complex drawing, it can take a while for the program to draw, and the result is that the user has to sit there and wait, while they watch the screen re-draw itself. Personally, I find it really annoying to watch a window slowly redraw itself, just because I moved a dialog box or something.
Double buffering solves this problem, because all a window needs to do to re-draw itself is to copy the buffer to the screen. This is a very fast operation.
What Objects are Involved
This recipe will help you figure out how to use a number of DeviceContexts (DCs), including:
wx.PaintDC -- drawing to the screen, during EVT_PAINT
wx.ClientDC -- drawing to the screen, outside EVT_PAINT
wx.BufferedPaintDC -- drawing to a buffer, then the screen, during EVT_PAINT
wx.BufferedDC -- drawing to a buffer, then the screen, outside EVT_PAINT
wx.MemoryDC -- drawing to a bitmap
See CustomisedDrawing for more on using DC's in general.
This recipe also covers the basics of using a
wx.Bitmap
Process Overview
The basic process is to create a class derived from wx.Window, that keeps a wx.Bitmap around with a copy of the image on the screen. The OnPaint handler simply copies that bitmap to the screen, and when the image needs to be updated, it is drawn to the bitmap, and the bitmap is re-copied to the screen.
The BufferedWindow class
(warning: drawing not yet updated to the wx namespace)
import wx import random
Import the usual wx module.
Import the random module, too; We'll need it for random data later in the demo.
#USE_BUFFERED_DC = False USE_BUFFERED_DC = True
This example can optionally use the wx.BufferedDC.
If USE_BUFFERED_DC is true, it will be used. Otherwise, the program uses the raw wx.MemoryDC, etc.
The wx.BufferedDC is a relatively recent addition that makes it a little easier to double buffer.
With this switch, you can see how it works, both ways.
1 class BufferedWindow(wx.Window):
2
3 def __init__(self, *args, **kwargs):
4 # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
5 kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
6 wx.Window.__init__(self, *args, **kwargs)
7
8 wx.EVT_PAINT(self, self.OnPaint)
9 wx.EVT_SIZE(self, self.OnSize)
10
11 # OnSize called to make sure the buffer is initialized.
12 # This might result in OnSize getting called twice on some
13 # platforms at initialization, but little harm done.
14 self.OnSize(None)
This is the class init function. The class subclasses from wx.Window.
The init binds a couple events.
Note the style: wx.NO_FULL_REPAINT_ON_RESIZE. This style can reduce flickering when resizing windows on Microsoft Windows. I'm not sure it makes a difference on other platforms (thanks to KevinAltis for that hint).
self.OnSize() is called lastly. It's called last, because OnSize will initialize the buffer, and we want to make sure it's the right size. It may end up getting called twice during initialization on some platforms, but there is little harm done.
Draw() is just here as a place holder.
You'll override it in sub-classes.
It will inspect your system, find out what needs to be drawn, and then draw it.
UpdateDrawing will call Draw. UpdateDrawing will take care of boring details, to make sure that Draw only has to work with a nice drawing context.
This function, Draw, is responsible for all of your drawing code. It is responsible for the scene creation. No other functions will be drawing on the buffer; only this one.
1 def OnSize(self,event):
2 # The Buffer init is done here, to make sure the buffer is always
3 # the same size as the Window
4 Size = self.ClientSize
5
6 # Make new offscreen bitmap: this bitmap will always have the
7 # current drawing in it, so it can be used to save the image to
8 # a file, or whatever.
9 self._Buffer = wx.EmptyBitmap(*Size)
10 self.UpdateDrawing()
The OnSize() Method.
When the window is resized, or on first __init__, this is called.
It's responsible for:
- Making a buffer the right size.
- Drawing to the buffer.
- Copying the buffer to the display.
OnSize will fill the first (making the buffer the right size,) and then delegating the other two to UpdateDrawing.
When OnSize creates the buffer, it's completely blank. UpdateDrawing will fill it (with the help of Draw,) and then blit the buffer to the screen.
UpdateDrawing is a method that we invented, for our convenience; it's not a built-in part of wxPython.
OnSize does not trigger a call to OnPaint(). UpdateDrawing() will make sure that the buffer is drawn to (via Draw()), and UpdateDrawing() will make sure that the buffer is drawn to the screen.
Now the OnPaint() method. It's called whenever ther is a pain event sent by the system: i.e. whenever part of the window gets dirty.
Since the image is stored in the buffer, all we have to do here is copy the buffer to the screen.
Ideally, you'd only copy the damaged region to the screen. But I've found I can't tell the difference, so I don't bother. In fact, on Windows at least, only the damaged region is copied, anyways, with this code: the system has set the update region appropriately.
There are two approaches here, depending on USE_BUFFERED_DC.
USE_BUFFERED_DC is True.
This way is a little easier.
The wx.BufferedPaintDC takes a bitmap as input (self._Buffer,) and creates a DC that you can use to draw to the bitmap. We're not going to draw to the bitmap; we've already drawn to the bitmap. So all we do is create the wx.BufferedPaintDC.
Note that a PaintDC or BufferedPaintDC is unique -- it must be used only inside a paint event, and (on some systems, at least) a PaintDC must be created when there is a paint event.
Now the object is somewhat unique, in that the bitmap is copied to the screen when the object goes out of scope. That is, when the OnPaint method is done, the object is no longer needed, and it is deleted. Just before it's deleted, though, it copies itself to the screen.
USE_BUFFERED_DC is False.
Without the BufferedPaintDC, the process is similar, except that we have to make a call to copy the bitmap to the wx.PaintDC.
Note that we use dc.DrawBitmap(), rather than dc.Blit(). DrawBitmap accomplishes essentially the same thing, but with an easier interface, for our purpose here.
UpdateDrawing() is a method we've invented here, that you should call whenever the drawing needs to update. The drawing is generated from data found elsewhere in the system. If those data change, the drawing needs to be updated, so be sure to call this function.
This function does all the pre-drawing and post-drawing work. The Draw() function we created earlier, does the actual drawing work. A wx.MemoryDC is created to draw to the bitmap with. You have to remember: a bitmap is not a drawing context! You can't use our neat drawing context functions, when all you hold is a bitmap. You have to actually put the bitmap into the drawing context, and then you can do your neat drawing things on it.
After the drawing is done, the MemoryDC is destroyed, so that the Bitmap is freed to be used elsewhere (in the paint handler). The Refresh(eraseBackground=False)is called to tell the system that the Window needs to be redrawn (eraseBackground=False prevents the system from erasing the background before re-drawing -- which could cause flicker). Update() is called to force a paint event. The actual drawing to the screen happens in the paint event.
The next method saves the contents of the buffer to a file. The buffer always contains the same image as the screen, so this makes it very easy to take a screenshot.
Using the `BufferedWindow` class
In order to use the BufferedWindow class, you must create a subclass, and define a Draw() method appropriate to your application.
The init method is very straight forward. Note that any data needed by your Draw() method must be defined before calling BufferedWindow.__init__(), because the Draw() method is called when it is initialized. In this case, I have created an empty dictionary that will cause the Draw() method to do nothing.
Here is the Draw() method. In this case, it examines a dictionary of data, and creates the drawing defined by that data. The data itself is generated by the application using this window.
1 def Draw(self, dc):
2 dc.SetBackground( wx.Brush("White") )
3 dc.Clear() # make sure you clear the bitmap!
4
5 # Here's the actual drawing code.
6 for key, data in self.DrawData.items():
7 if key == "Rectangles":
8 dc.SetBrush(wx.BLUE_BRUSH)
9 dc.SetPen(wx.Pen('VIOLET', 4))
10 for r in data:
11 dc.DrawRectangle(*r)
12 elif key == "Ellipses":
13 dc.SetBrush(wx.Brush("GREEN YELLOW"))
14 dc.SetPen(wx.Pen('CADET BLUE', 2))
15 for r in data:
16 dc.DrawEllipse(*r)
17 elif key == "Polygons":
18 dc.SetBrush(wx.Brush("SALMON"))
19 dc.SetPen(wx.Pen('VIOLET RED', 4))
20 for r in data:
21 dc.DrawPolygon(r)
Using the newly defined buffered Window.
Next is a sample application that uses the buffered draw window defined above.
A main frame for the app, with a simple menu bar:
1 class TestFrame(wx.Frame):
2 def __init__(self, parent=None):
3 wx.Frame.__init__(self, parent,
4 size = (500,500),
5 title="Double Buffered Test",
6 style=wx.DEFAULT_FRAME_STYLE)
7
8 ## Set up the MenuBar
9 MenuBar = wx.MenuBar()
10
11 file_menu = wx.Menu()
12
13 item = file_menu.Append(wx.ID_EXIT, text="&Exit")
14 self.Bind(wx.EVT_MENU, self.OnQuit, item)
15 MenuBar.Append(file_menu, "&File")
16
17 draw_menu = wx.Menu()
18 item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
19 self.Bind(wx.EVT_MENU, self.NewDrawing, item)
20 item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
21 self.Bind(wx.EVT_MENU, self.SaveToFile, item)
22 MenuBar.Append(draw_menu, "&Draw")
23
24 self.SetMenuBar(MenuBar)
25 self.Window = DrawWindow(self)
26 self.Show()
27 # Initialize a drawing -- it has to be done after Show() is called
28 # so that the Windows has teh right size.
29 self.NewDrawing()
30
31 def OnQuit(self,event):
32 self.Close(True)
33
34 def NewDrawing(self, event=None):
35 self.Window.DrawData = self.MakeNewData()
36 self.Window.UpdateDrawing()
37
38 def SaveToFile(self,event):
39 dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
40 defaultDir = "",
41 defaultFile = "",
42 wildcard = "*.png",
43 style = wx.SAVE)
44 if dlg.ShowModal() == wx.ID_OK:
45 self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
46 dlg.Destroy()
47
48 def MakeNewData(self):
49 ## This method makes some random data to draw things with.
50 MaxX, MaxY = self.Window.GetClientSizeTuple()
51 DrawData = {}
52
53 # make some random rectangles
54 l = []
55 for i in range(5):
56 w = random.randint(1,MaxX/2)
57 h = random.randint(1,MaxY/2)
58 x = random.randint(1,MaxX-w)
59 y = random.randint(1,MaxY-h)
60 l.append( (x,y,w,h) )
61 DrawData["Rectangles"] = l
62
63 # make some random ellipses
64 l = []
65 for i in range(5):
66 w = random.randint(1,MaxX/2)
67 h = random.randint(1,MaxY/2)
68 x = random.randint(1,MaxX-w)
69 y = random.randint(1,MaxY-h)
70 l.append( (x,y,w,h) )
71 DrawData["Ellipses"] = l
72
73 # Polygons
74 l = []
75 for i in range(3):
76 points = []
77 for j in range(random.randint(3,8)):
78 point = (random.randint(1,MaxX),random.randint(1,MaxY))
79 points.append(point)
80 l.append(points)
81 DrawData["Polygons"] = l
82
83 return DrawData
A simple wx.App to create the frame.
Special Concerns
If your image is big, you may want to have some way to scroll around it. One method is to use a wx.ScrolledWindow, but remember that the buffer has to be updated as you scroll, which can be pretty slow. Another method is to have a really big bitmap, and just blit the appropriate part to the screen as you scroll. This works well, but can only accommodate a moderate sized bitmap. Memory use can get big very fast!
Code Sample
Here's all the code in one piece, so that you can try the sample app:
1 # -*- coding: iso-8859-1 -*-#
2
3 #!/usr/bin/env python
4
5 import wx
6 import random
7
8 # This has been set up to optionally use the wx.BufferedDC if
9 # USE_BUFFERED_DC is True, it will be used. Otherwise, it uses the raw
10 # wx.Memory DC , etc.
11
12 #USE_BUFFERED_DC = False
13 USE_BUFFERED_DC = True
14
15 class BufferedWindow(wx.Window):
16
17 """
18
19 A Buffered window class.
20
21 To use it, subclass it and define a Draw(DC) method that takes a DC
22 to draw to. In that method, put the code needed to draw the picture
23 you want. The window will automatically be double buffered, and the
24 screen will be automatically updated when a Paint event is received.
25
26 When the drawing needs to change, you app needs to call the
27 UpdateDrawing() method. Since the drawing is stored in a bitmap, you
28 can also save the drawing to file by calling the
29 SaveToFile(self, file_name, file_type) method.
30
31 """
32 def __init__(self, *args, **kwargs):
33 # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
34 kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
35 wx.Window.__init__(self, *args, **kwargs)
36
37 wx.EVT_PAINT(self, self.OnPaint)
38 wx.EVT_SIZE(self, self.OnSize)
39
40 # OnSize called to make sure the buffer is initialized.
41 # This might result in OnSize getting called twice on some
42 # platforms at initialization, but little harm done.
43 self.OnSize(None)
44 self.paint_count = 0
45
46 def Draw(self, dc):
47 ## just here as a place holder.
48 ## This method should be over-ridden when subclassed
49 pass
50
51 def OnPaint(self, event):
52 # All that is needed here is to draw the buffer to screen
53 if USE_BUFFERED_DC:
54 dc = wx.BufferedPaintDC(self, self._Buffer)
55 else:
56 dc = wx.PaintDC(self)
57 dc.DrawBitmap(self._Buffer, 0, 0)
58
59 def OnSize(self,event):
60 # The Buffer init is done here, to make sure the buffer is always
61 # the same size as the Window
62 #Size = self.GetClientSizeTuple()
63 Size = self.ClientSize
64
65 # Make new offscreen bitmap: this bitmap will always have the
66 # current drawing in it, so it can be used to save the image to
67 # a file, or whatever.
68 self._Buffer = wx.EmptyBitmap(*Size)
69 self.UpdateDrawing()
70
71 def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
72 ## This will save the contents of the buffer
73 ## to the specified file. See the wxWindows docs for
74 ## wx.Bitmap::SaveFile for the details
75 self._Buffer.SaveFile(FileName, FileType)
76
77 def UpdateDrawing(self):
78 """
79 This would get called if the drawing needed to change, for whatever reason.
80
81 The idea here is that the drawing is based on some data generated
82 elsewhere in the system. If that data changes, the drawing needs to
83 be updated.
84
85 This code re-draws the buffer, then calls Update, which forces a paint event.
86 """
87 dc = wx.MemoryDC()
88 dc.SelectObject(self._Buffer)
89 self.Draw(dc)
90 del dc # need to get rid of the MemoryDC before Update() is called.
91 self.Refresh()
92 self.Update()
93
94 class DrawWindow(BufferedWindow):
95 def __init__(self, *args, **kwargs):
96 ## Any data the Draw() function needs must be initialized before
97 ## calling BufferedWindow.__init__, as it will call the Draw
98 ## function.
99 self.DrawData = {}
100 BufferedWindow.__init__(self, *args, **kwargs)
101
102 def Draw(self, dc):
103 dc.SetBackground( wx.Brush("White") )
104 dc.Clear() # make sure you clear the bitmap!
105
106 # Here's the actual drawing code.
107 for key, data in self.DrawData.items():
108 if key == "Rectangles":
109 dc.SetBrush(wx.BLUE_BRUSH)
110 dc.SetPen(wx.Pen('VIOLET', 4))
111 for r in data:
112 dc.DrawRectangle(*r)
113 elif key == "Ellipses":
114 dc.SetBrush(wx.Brush("GREEN YELLOW"))
115 dc.SetPen(wx.Pen('CADET BLUE', 2))
116 for r in data:
117 dc.DrawEllipse(*r)
118 elif key == "Polygons":
119 dc.SetBrush(wx.Brush("SALMON"))
120 dc.SetPen(wx.Pen('VIOLET RED', 4))
121 for r in data:
122 dc.DrawPolygon(r)
123
124
125 class TestFrame(wx.Frame):
126 def __init__(self, parent=None):
127 wx.Frame.__init__(self, parent,
128 size = (500,500),
129 title="Double Buffered Test",
130 style=wx.DEFAULT_FRAME_STYLE)
131
132 ## Set up the MenuBar
133 MenuBar = wx.MenuBar()
134
135 file_menu = wx.Menu()
136
137 item = file_menu.Append(wx.ID_EXIT, text="&Exit")
138 self.Bind(wx.EVT_MENU, self.OnQuit, item)
139 MenuBar.Append(file_menu, "&File")
140
141 draw_menu = wx.Menu()
142 item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
143 self.Bind(wx.EVT_MENU, self.NewDrawing, item)
144 item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
145 self.Bind(wx.EVT_MENU, self.SaveToFile, item)
146 MenuBar.Append(draw_menu, "&Draw")
147
148 self.SetMenuBar(MenuBar)
149 self.Window = DrawWindow(self)
150 self.Show()
151 # Initialize a drawing -- it has to be done after Show() is called
152 # so that the Windows has teh right size.
153 self.NewDrawing()
154
155 def OnQuit(self,event):
156 self.Close(True)
157
158 def NewDrawing(self, event=None):
159 self.Window.DrawData = self.MakeNewData()
160 self.Window.UpdateDrawing()
161
162 def SaveToFile(self,event):
163 dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
164 defaultDir = "",
165 defaultFile = "",
166 wildcard = "*.png",
167 style = wx.SAVE)
168 if dlg.ShowModal() == wx.ID_OK:
169 self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
170 dlg.Destroy()
171
172 def MakeNewData(self):
173 ## This method makes some random data to draw things with.
174 MaxX, MaxY = self.Window.GetClientSizeTuple()
175 DrawData = {}
176
177 # make some random rectangles
178 l = []
179 for i in range(5):
180 w = random.randint(1,MaxX/2)
181 h = random.randint(1,MaxY/2)
182 x = random.randint(1,MaxX-w)
183 y = random.randint(1,MaxY-h)
184 l.append( (x,y,w,h) )
185 DrawData["Rectangles"] = l
186
187 # make some random ellipses
188 l = []
189 for i in range(5):
190 w = random.randint(1,MaxX/2)
191 h = random.randint(1,MaxY/2)
192 x = random.randint(1,MaxX-w)
193 y = random.randint(1,MaxY-h)
194 l.append( (x,y,w,h) )
195 DrawData["Ellipses"] = l
196
197 # Polygons
198 l = []
199 for i in range(3):
200 points = []
201 for j in range(random.randint(3,8)):
202 point = (random.randint(1,MaxX),random.randint(1,MaxY))
203 points.append(point)
204 l.append(points)
205 DrawData["Polygons"] = l
206
207 return DrawData
208
209 class DemoApp(wx.App):
210 def OnInit(self):
211 frame = TestFrame()
212 self.SetTopWindow(frame)
213
214 return True
215
216 if __name__ == "__main__":
217 app = DemoApp(0)
218 app.MainLoop()
See Also
BufferedCanvas -- a class that does what this does; it works on the same principles
RecipesImagesAndGraphics -- more drawing recipies
Comments
Updated for more "modern" method: - Chris Barker 3/15/11 Chris.Barker@noaa.gov
Introduction
This recipe provides a simple double buffered window that can be sub-classed to do any customized drawing you might need, without worrying about Paint events, etc.
What Is Double Buffering?
Double buffering is storing the contents of a window in memory (the buffer), so that the screen can be easily refreshed without having to re-draw the whole thing.
Why double Buffer?
Whenever a window displayed on the screen gets damaged, by, for instance, a dialog box being displayed over it, the system asks the program to re-draw it. This is indicated by a Paint event, and is handled in a Paint event handler. Usually the Paint event handler creates a wx.PaintDC, and calls code that re-draws the window. See CustomisedDrawing and WxHowtoDrawing for an example, as well as assorted code in the wxPython demo.
This works fine for simple drawings, but if you have a complex drawing, it can take a while for the program to draw, and the result is that the user has to sit there and wait, while they watch the screen re-draw itself. Personally, I find it really annoying to watch a window slowly redraw itself, just because I moved a dialog box or something.
Double buffering solves this problem, because all a window needs to do to re-draw itself is to copy the buffer to the screen. This is a very fast operation.
What Objects are Involved
This recipe will help you figure out how to use a number of DeviceContexts (DCs), including:
wx.PaintDC -- drawing to the screen, during EVT_PAINT
wx.ClientDC -- drawing to the screen, outside EVT_PAINT
wx.BufferedPaintDC -- drawing to a buffer, then the screen, during EVT_PAINT
wx.BufferedDC -- drawing to a buffer, then the screen, outside EVT_PAINT
wx.MemoryDC -- drawing to a bitmap
See CustomisedDrawing for more on using DC's in general.
This recipe also covers the basics of using a
wx.Bitmap
Process Overview
The basic process is to create a class derived from wx.Window, that keeps a wx.Bitmap around with a copy of the image on the screen. The OnPaint handler simply copies that bitmap to the screen, and when the image needs to be updated, it is drawn to the bitmap, and the bitmap is re-copied to the screen.
The BufferedWindow class
(warning: drawing not yet updated to the wx namespace)
{{{#!/usr/bin/env python
import wx import random }}} Import the usual wx module.
Import the random module, too; We'll need it for random data later in the demo.
#USE_BUFFERED_DC = False USE_BUFFERED_DC = True
This example can optionally use the wx.BufferedDC.
If USE_BUFFERED_DC is true, it will be used. Otherwise, the program uses the raw wx.MemoryDC, etc.
The wx.BufferedDC is a relatively recent addition that makes it a little easier to double buffer.
With this switch, you can see how it works, both ways.
1 class BufferedWindow(wx.Window):
2
3 def __init__(self, *args, **kwargs):
4 # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
5 kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
6 wx.Window.__init__(self, *args, **kwargs)
7
8 wx.EVT_PAINT(self, self.OnPaint)
9 wx.EVT_SIZE(self, self.OnSize)
10
11 # OnSize called to make sure the buffer is initialized.
12 # This might result in OnSize getting called twice on some
13 # platforms at initialization, but little harm done.
14 self.OnSize(None)
This is the class init function. The class subclasses from wx.Window.
The init binds a couple events.
Note the style: wx.NO_FULL_REPAINT_ON_RESIZE. This style can reduce flickering when resizing windows on Microsoft Windows. I'm not sure it makes a difference on other platforms (thanks to KevinAltis for that hint).
self.OnSize() is called lastly. It's called last, because OnSize will initialize the buffer, and we want to make sure it's the right size. It may end up getting called twice during initialization on some platforms, but there is little harm done.
Draw() is just here as a place holder.
You'll override it in sub-classes.
It will inspect your system, find out what needs to be drawn, and then draw it.
UpdateDrawing will call Draw. UpdateDrawing will take care of boring details, to make sure that Draw only has to work with a nice drawing context.
This function, Draw, is responsible for all of your drawing code. It is responsible for the scene creation. No other functions will be drawing on the buffer; only this one.
1 def OnSize(self,event):
2 # The Buffer init is done here, to make sure the buffer is always
3 # the same size as the Window
4 Size = self.ClientSize
5
6 # Make new offscreen bitmap: this bitmap will always have the
7 # current drawing in it, so it can be used to save the image to
8 # a file, or whatever.
9 self._Buffer = wx.EmptyBitmap(*Size)
10 self.UpdateDrawing()
The OnSize() Method.
When the window is resized, or on first __init__, this is called.
It's responsible for:
- Making a buffer the right size.
- Drawing to the buffer.
- Copying the buffer to the display.
OnSize will fill the first (making the buffer the right size,) and then delegating the other two to UpdateDrawing.
When OnSize creates the buffer, it's completely blank. UpdateDrawing will fill it (with the help of Draw,) and then blit the buffer to the screen.
UpdateDrawing is a method that we invented, for our convenience; it's not a built-in part of wxPython.
OnSize does not trigger a call to OnPaint(). UpdateDrawing() will make sure that the buffer is drawn to (via Draw()), and UpdateDrawing() will make sure that the buffer is drawn to the screen.
Now the OnPaint() method. It's called whenever ther is a pain event sent by the system: i.e. whenever part of the window gets dirty.
Since the image is stored in the buffer, all we have to do here is copy the buffer to the screen.
Ideally, you'd only copy the damaged region to the screen. But I've found I can't tell the difference, so I don't bother. In fact, on Windows at least, only the damaged region is copied, anyways, with this code: the system has set the update region appropriately.
There are two approaches here, depending on USE_BUFFERED_DC.
USE_BUFFERED_DC is True.
This way is a little easier.
The wx.BufferedPaintDC takes a bitmap as input (self._Buffer,) and creates a DC that you can use to draw to the bitmap. We're not going to draw to the bitmap; we've already drawn to the bitmap. So all we do is create the wx.BufferedPaintDC.
Note that a PaintDC or BufferedPaintDC is unique -- it must be used only inside a paint event, and (on some systems, at least) a PaintDC must be created when there is a paint event.
Now the object is somewhat unique, in that the bitmap is copied to the screen when the object goes out of scope. That is, when the OnPaint method is done, the object is no longer needed, and it is deleted. Just before it's deleted, though, it copies itself to the screen.
USE_BUFFERED_DC is False.
Without the BufferedPaintDC, the process is similar, except that we have to make a call to copy the bitmap to the wx.PaintDC.
Note that we use dc.DrawBitmap(), rather than dc.Blit(). DrawBitmap accomplishes essentially the same thing, but with an easier interface, for our purpose here.
UpdateDrawing() is a method we've invented here, that you should call whenever the drawing needs to update. The drawing is generated from data found elsewhere in the system. If those data change, the drawing needs to be updated, so be sure to call this function.
This function does all the pre-drawing and post-drawing work. The Draw() function we created earlier, does the actual drawing work. A wx.MemoryDC is created to draw to the bitmap with. You have to remember: a bitmap is not a drawing context! You can't use our neat drawing context functions, when all you hold is a bitmap. You have to actually put the bitmap into the drawing context, and then you can do your neat drawing things on it.
After the drawing is done, the MemoryDC is destroyed, so that the Bitmap is freed to be used elsewhere (in the paint handler). The Refresh(eraseBackground=False)is called to tell the system that the Window needs to be redrawn (eraseBackground=False prevents the system from erasing the background before re-drawing -- which could cause flicker). Update() is called to force a paint event. The actual drawing to the screen happens in the paint event.
The next method saves the contents of the buffer to a file. The buffer always contains the same image as the screen, so this makes it very easy to take a screenshot.
Using the `BufferedWindow` class
In order to use the BufferedWindow class, you must create a subclass, and define a Draw() method appropriate to your application.
The init method is very straight forward. Note that any data needed by your Draw() method must be defined before calling BufferedWindow.__init__(), because the Draw() method is called when it is initialized. In this case, I have created an empty dictionary that will cause the Draw() method to do nothing.
Here is the Draw() method. In this case, it examines a dictionary of data, and creates the drawing defined by that data. The data itself is generated by the application using this window.
1 def Draw(self, dc):
2 dc.SetBackground( wx.Brush("White") )
3 dc.Clear() # make sure you clear the bitmap!
4
5 # Here's the actual drawing code.
6 for key, data in self.DrawData.items():
7 if key == "Rectangles":
8 dc.SetBrush(wx.BLUE_BRUSH)
9 dc.SetPen(wx.Pen('VIOLET', 4))
10 for r in data:
11 dc.DrawRectangle(*r)
12 elif key == "Ellipses":
13 dc.SetBrush(wx.Brush("GREEN YELLOW"))
14 dc.SetPen(wx.Pen('CADET BLUE', 2))
15 for r in data:
16 dc.DrawEllipse(*r)
17 elif key == "Polygons":
18 dc.SetBrush(wx.Brush("SALMON"))
19 dc.SetPen(wx.Pen('VIOLET RED', 4))
20 for r in data:
21 dc.DrawPolygon(r)
Using the newly defined buffered Window.
Next is a sample application that uses the buffered draw window defined above.
A main frame for the app, with a simple menu bar:
1 class TestFrame(wx.Frame):
2 def __init__(self, parent=None):
3 wx.Frame.__init__(self, parent,
4 size = (500,500),
5 title="Double Buffered Test",
6 style=wx.DEFAULT_FRAME_STYLE)
7
8 ## Set up the MenuBar
9 MenuBar = wx.MenuBar()
10
11 file_menu = wx.Menu()
12
13 item = file_menu.Append(wx.ID_EXIT, text="&Exit")
14 self.Bind(wx.EVT_MENU, self.OnQuit, item)
15 MenuBar.Append(file_menu, "&File")
16
17 draw_menu = wx.Menu()
18 item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
19 self.Bind(wx.EVT_MENU, self.NewDrawing, item)
20 item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
21 self.Bind(wx.EVT_MENU, self.SaveToFile, item)
22 MenuBar.Append(draw_menu, "&Draw")
23
24 self.SetMenuBar(MenuBar)
25 self.Window = DrawWindow(self)
26 self.Show()
27 # Initialize a drawing -- it has to be done after Show() is called
28 # so that the Windows has teh right size.
29 self.NewDrawing()
30
31 def OnQuit(self,event):
32 self.Close(True)
33
34 def NewDrawing(self, event=None):
35 self.Window.DrawData = self.MakeNewData()
36 self.Window.UpdateDrawing()
37
38 def SaveToFile(self,event):
39 dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
40 defaultDir = "",
41 defaultFile = "",
42 wildcard = "*.png",
43 style = wx.SAVE)
44 if dlg.ShowModal() == wx.ID_OK:
45 self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
46 dlg.Destroy()
47
48 def MakeNewData(self):
49 ## This method makes some random data to draw things with.
50 MaxX, MaxY = self.Window.GetClientSizeTuple()
51 DrawData = {}
52
53 # make some random rectangles
54 l = []
55 for i in range(5):
56 w = random.randint(1,MaxX/2)
57 h = random.randint(1,MaxY/2)
58 x = random.randint(1,MaxX-w)
59 y = random.randint(1,MaxY-h)
60 l.append( (x,y,w,h) )
61 DrawData["Rectangles"] = l
62
63 # make some random ellipses
64 l = []
65 for i in range(5):
66 w = random.randint(1,MaxX/2)
67 h = random.randint(1,MaxY/2)
68 x = random.randint(1,MaxX-w)
69 y = random.randint(1,MaxY-h)
70 l.append( (x,y,w,h) )
71 DrawData["Ellipses"] = l
72
73 # Polygons
74 l = []
75 for i in range(3):
76 points = []
77 for j in range(random.randint(3,8)):
78 point = (random.randint(1,MaxX),random.randint(1,MaxY))
79 points.append(point)
80 l.append(points)
81 DrawData["Polygons"] = l
82
83 return DrawData
A simple wx.App to create the frame.
Special Concerns
If your image is big, you may want to have some way to scroll around it. One method is to use a wx.ScrolledWindow, but remember that the buffer has to be updated as you scroll, which can be pretty slow. Another method is to have a really big bitmap, and just blit the appropriate part to the screen as you scroll. This works well, but can only accommodate a moderate sized bitmap. Memory use can get big very fast!
Code Sample
Here's all the code in one piece, so that you can try the sample app:
1 # -*- coding: iso-8859-1 -*-#
2
3 #!/usr/bin/env python
4
5 import wx
6 import random
7
8 # This has been set up to optionally use the wx.BufferedDC if
9 # USE_BUFFERED_DC is True, it will be used. Otherwise, it uses the raw
10 # wx.Memory DC , etc.
11
12 #USE_BUFFERED_DC = False
13 USE_BUFFERED_DC = True
14
15 class BufferedWindow(wx.Window):
16
17 """
18
19 A Buffered window class.
20
21 To use it, subclass it and define a Draw(DC) method that takes a DC
22 to draw to. In that method, put the code needed to draw the picture
23 you want. The window will automatically be double buffered, and the
24 screen will be automatically updated when a Paint event is received.
25
26 When the drawing needs to change, you app needs to call the
27 UpdateDrawing() method. Since the drawing is stored in a bitmap, you
28 can also save the drawing to file by calling the
29 SaveToFile(self, file_name, file_type) method.
30
31 """
32 def __init__(self, *args, **kwargs):
33 # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
34 kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
35 wx.Window.__init__(self, *args, **kwargs)
36
37 wx.EVT_PAINT(self, self.OnPaint)
38 wx.EVT_SIZE(self, self.OnSize)
39
40 # OnSize called to make sure the buffer is initialized.
41 # This might result in OnSize getting called twice on some
42 # platforms at initialization, but little harm done.
43 self.OnSize(None)
44 self.paint_count = 0
45
46 def Draw(self, dc):
47 ## just here as a place holder.
48 ## This method should be over-ridden when subclassed
49 pass
50
51 def OnPaint(self, event):
52 # All that is needed here is to draw the buffer to screen
53 if USE_BUFFERED_DC:
54 dc = wx.BufferedPaintDC(self, self._Buffer)
55 else:
56 dc = wx.PaintDC(self)
57 dc.DrawBitmap(self._Buffer, 0, 0)
58
59 def OnSize(self,event):
60 # The Buffer init is done here, to make sure the buffer is always
61 # the same size as the Window
62 #Size = self.GetClientSizeTuple()
63 Size = self.ClientSize
64
65 # Make new offscreen bitmap: this bitmap will always have the
66 # current drawing in it, so it can be used to save the image to
67 # a file, or whatever.
68 self._Buffer = wx.EmptyBitmap(*Size)
69 self.UpdateDrawing()
70
71 def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
72 ## This will save the contents of the buffer
73 ## to the specified file. See the wxWindows docs for
74 ## wx.Bitmap::SaveFile for the details
75 self._Buffer.SaveFile(FileName, FileType)
76
77 def UpdateDrawing(self):
78 """
79 This would get called if the drawing needed to change, for whatever reason.
80
81 The idea here is that the drawing is based on some data generated
82 elsewhere in the system. If that data changes, the drawing needs to
83 be updated.
84
85 This code re-draws the buffer, then calls Update, which forces a paint event.
86 """
87 dc = wx.MemoryDC()
88 dc.SelectObject(self._Buffer)
89 self.Draw(dc)
90 del dc # need to get rid of the MemoryDC before Update() is called.
91 self.Refresh()
92 self.Update()
93
94 class DrawWindow(BufferedWindow):
95 def __init__(self, *args, **kwargs):
96 ## Any data the Draw() function needs must be initialized before
97 ## calling BufferedWindow.__init__, as it will call the Draw
98 ## function.
99 self.DrawData = {}
100 BufferedWindow.__init__(self, *args, **kwargs)
101
102 def Draw(self, dc):
103 dc.SetBackground( wx.Brush("White") )
104 dc.Clear() # make sure you clear the bitmap!
105
106 # Here's the actual drawing code.
107 for key, data in self.DrawData.items():
108 if key == "Rectangles":
109 dc.SetBrush(wx.BLUE_BRUSH)
110 dc.SetPen(wx.Pen('VIOLET', 4))
111 for r in data:
112 dc.DrawRectangle(*r)
113 elif key == "Ellipses":
114 dc.SetBrush(wx.Brush("GREEN YELLOW"))
115 dc.SetPen(wx.Pen('CADET BLUE', 2))
116 for r in data:
117 dc.DrawEllipse(*r)
118 elif key == "Polygons":
119 dc.SetBrush(wx.Brush("SALMON"))
120 dc.SetPen(wx.Pen('VIOLET RED', 4))
121 for r in data:
122 dc.DrawPolygon(r)
123
124
125 class TestFrame(wx.Frame):
126 def __init__(self, parent=None):
127 wx.Frame.__init__(self, parent,
128 size = (500,500),
129 title="Double Buffered Test",
130 style=wx.DEFAULT_FRAME_STYLE)
131
132 ## Set up the MenuBar
133 MenuBar = wx.MenuBar()
134
135 file_menu = wx.Menu()
136
137 item = file_menu.Append(wx.ID_EXIT, text="&Exit")
138 self.Bind(wx.EVT_MENU, self.OnQuit, item)
139 MenuBar.Append(file_menu, "&File")
140
141 draw_menu = wx.Menu()
142 item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
143 self.Bind(wx.EVT_MENU, self.NewDrawing, item)
144 item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
145 self.Bind(wx.EVT_MENU, self.SaveToFile, item)
146 MenuBar.Append(draw_menu, "&Draw")
147
148 self.SetMenuBar(MenuBar)
149 self.Window = DrawWindow(self)
150 self.Show()
151 # Initialize a drawing -- it has to be done after Show() is called
152 # so that the Windows has teh right size.
153 self.NewDrawing()
154
155 def OnQuit(self,event):
156 self.Close(True)
157
158 def NewDrawing(self, event=None):
159 self.Window.DrawData = self.MakeNewData()
160 self.Window.UpdateDrawing()
161
162 def SaveToFile(self,event):
163 dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
164 defaultDir = "",
165 defaultFile = "",
166 wildcard = "*.png",
167 style = wx.SAVE)
168 if dlg.ShowModal() == wx.ID_OK:
169 self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
170 dlg.Destroy()
171
172 def MakeNewData(self):
173 ## This method makes some random data to draw things with.
174 MaxX, MaxY = self.Window.GetClientSizeTuple()
175 DrawData = {}
176
177 # make some random rectangles
178 l = []
179 for i in range(5):
180 w = random.randint(1,MaxX/2)
181 h = random.randint(1,MaxY/2)
182 x = random.randint(1,MaxX-w)
183 y = random.randint(1,MaxY-h)
184 l.append( (x,y,w,h) )
185 DrawData["Rectangles"] = l
186
187 # make some random ellipses
188 l = []
189 for i in range(5):
190 w = random.randint(1,MaxX/2)
191 h = random.randint(1,MaxY/2)
192 x = random.randint(1,MaxX-w)
193 y = random.randint(1,MaxY-h)
194 l.append( (x,y,w,h) )
195 DrawData["Ellipses"] = l
196
197 # Polygons
198 l = []
199 for i in range(3):
200 points = []
201 for j in range(random.randint(3,8)):
202 point = (random.randint(1,MaxX),random.randint(1,MaxY))
203 points.append(point)
204 l.append(points)
205 DrawData["Polygons"] = l
206
207 return DrawData
208
209 class DemoApp(wx.App):
210 def OnInit(self):
211 frame = TestFrame()
212 self.SetTopWindow(frame)
213
214 return True
215
216 if __name__ == "__main__":
217 app = DemoApp(0)
218 app.MainLoop()
See Also
BufferedCanvas -- a class that does what this does; it works on the same principles
RecipesImagesAndGraphics -- more drawing recipies
Comments
Updated for more "modern" method: - Chris Barker 3/15/11 Chris.Barker@noaa.gov
Code for a new "newly defined buffered Window"
The code is taken from Code Sample but revised to be compatible with Python 3.6 and the Phoenix implementation of wxPython. Try it, you might like it
1 # -*- coding: iso-8859-1 -*-#
2 #!/usr/bin/env python
3 # The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
4 # coding:utf-8
5 """
6 Purpose: Show how to draw with Phoenix (wxPython)
7 Features:
8 * Usage of wx.BufferedPaintDC() to reduce flicker
9 * Update a drawing
10 * Override a method
11 * Save a bitmap (drawing) to a file
12 * Use super() for initialization of parent class
13 Note:
14 1. DC is an acronym for Device Context (where graphics and text can be drawn)
15 2. Original code taken from: https://wiki.wxpython.org/DoubleBufferedDrawing
16 (for earlier versions of wxPython). Updated for Python 3 and Phoenix 4 by
17 Virsto (vs@it.uu.se)
18 """
19 import wx
20 import random
21
22 # This has been set up to use the wx.BufferedDC if
23 # USE_BUFFERED_DC is True. Alternatively, it can use the raw
24 # wx.MemoryDC, etc.
25
26 #USE_BUFFERED_DC = False # use UpdateDrawing() method
27 USE_BUFFERED_DC = True # use Draw() method
28
29 class BufferedWindow(wx.Window):
30 '''
31 A Buffered window class.
32
33 To use it, subclass it and define a Draw() method that takes a DC
34 to draw to. In that method, put the code needed to draw the picture
35 you want. The window will automatically be double buffered, and the
36 screen will be automatically updated when a Paint event is received
37 (USE_BUFFERED_DC = True).
38
39 When the drawing needs to change, your app needs to call the
40 UpdateDrawing() method. Since the drawing is stored in a bitmap, you
41 can also save the drawing to file by calling the SaveToFile() method.
42 '''
43 def __init__(self, *args, **kwargs):
44 # Make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
45 # And define a new kwargs entry for wx.python
46 kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
47 super().__init__( *args, **kwargs)
48
49 # Setup event handlers for drawing
50 self.Bind(wx.EVT_PAINT,self.OnPaint)
51 self.Bind(wx.EVT_SIZE, self.OnSize)
52
53 # OnSize called to make sure the buffer is initialized.
54 # This might result in OnSize getting called twice on some
55 # platforms at initialization, but little harm done.
56 self.OnSize(None)
57 self.paint_count = 0
58
59 def Draw(self, dc):
60 '''
61 just here as a place holder.
62 This method must be over-ridden when subclassed
63 '''
64 pass
65
66 def OnPaint(self, event):
67 '''
68 All that is needed here is to move the buffer to the screen
69 '''
70 if USE_BUFFERED_DC:
71 dc = wx.BufferedPaintDC(self, self._Buffer)
72 else:
73 dc = wx.PaintDC(self)
74 dc.DrawBitmap(self._Buffer, 0, 0)
75
76 def OnSize(self,event):
77 '''
78 The Buffer init is done here, to make sure the buffer is always
79 the same size as the Window
80 '''
81 Size = self.ClientSize
82
83 # Make new offscreen bitmap: this bitmap will always have the
84 # current drawing in it, so it can be used to save the image to
85 # a file, or whatever.
86 self._Buffer = wx.Bitmap(*Size)
87 self.UpdateDrawing()
88
89 def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
90 '''
91 This will save the contents of the buffer
92 to the specified file. See the wx.Windows docs for
93 wx.Bitmap::SaveFile for the details
94 '''
95 self._Buffer.SaveFile(FileName, FileType)
96
97 def UpdateDrawing(self):
98 '''
99 This would get called if the drawing is changed, for whatever reason.
100
101 The idea here is that the drawing is based on some data generated
102 elsewhere in the system. If that data changes, the drawing needs to
103 be updated.
104
105 This code re-draws the buffer, then calls Update, which forces a paint event.
106 '''
107 dc = wx.MemoryDC()
108 dc.SelectObject(self._Buffer)
109 self.Draw(dc)
110 del dc # need to get rid of the MemoryDC before Update() is called.
111 self.Refresh()
112 self.Update()
113
114 class DrawWindow(BufferedWindow):
115 '''
116 Purpose: Initialization for Draw()
117 '''
118 def __init__(self, *args, **kwargs):
119 '''
120 Any data the Draw() function needs must be initialized before
121 initialization of BufferedWindow, since this will trigger the Draw
122 function.
123 '''
124 self.DrawData = {}
125 super().__init__(*args, **kwargs) # initialize parent (BufferedWindow)
126
127 def Draw(self, dc):
128 '''
129 Purpose: to generate all the cmds for drawing
130 Note:
131 1. Overrides Draw() in BufferedWindow()
132 '''
133 dc.SetBackground( wx.Brush("White") )
134 dc.Clear() # make sure you clear the bitmap!
135
136 # Here's the actual drawing code.
137 for key, data in self.DrawData.items():
138 if key == "Rectangles":
139 dc.SetBrush(wx.BLUE_BRUSH)
140 dc.SetPen(wx.Pen('VIOLET', 4))
141 for r in data:
142 dc.DrawRectangle(*r)
143 elif key == "Ellipses":
144 dc.SetBrush(wx.Brush("GREEN YELLOW"))
145 dc.SetPen(wx.Pen('CADET BLUE', 2))
146 for r in data:
147 dc.DrawEllipse(*r)
148 elif key == "Polygons":
149 dc.SetBrush(wx.Brush("SALMON"))
150 dc.SetPen(wx.Pen('VIOLET RED', 4))
151 for r in data:
152 dc.DrawPolygon(r)
153
154 class MyFrame(wx.Frame):
155 '''
156 Purpose: initialize a frame to hold DC and setup menu bar
157 '''
158 def __init__(self, parent=None):
159 wx.Frame.__init__(self, parent,
160 size = (500,500),
161 title = "Double Buffered Test",
162 style = wx.DEFAULT_FRAME_STYLE)
163
164 ## Set up the MenuBar
165 MenuBar = wx.MenuBar()
166
167 file_menu = wx.Menu()
168
169 # Define each item in menu bar and their event handlers
170 item = file_menu.Append(wx.ID_EXIT, "&Exit")
171 self.Bind(wx.EVT_MENU, self.OnQuit, item)
172 MenuBar.Append(file_menu, "&File")
173
174 draw_menu = wx.Menu()
175 item = draw_menu.Append(wx.ID_ANY, "&New Drawing","Update the Drawing Data")
176 self.Bind(wx.EVT_MENU, self.NewDrawing, item)
177 item = draw_menu.Append(wx.ID_ANY,'&Save Drawing\tAlt-I','')
178 self.Bind(wx.EVT_MENU, self.SaveToFile, item)
179 MenuBar.Append(draw_menu, "&Draw")
180
181 self.SetMenuBar(MenuBar)
182 self.Window = DrawWindow(self) # initialize window for drawing
183 self.Show()
184 # Initialize a drawing -- it has to be done after Show() is called
185 # so that the Window is correctly sized.
186 self.NewDrawing()
187
188 def OnQuit(self,event):
189 self.Close(True)
190
191 def NewDrawing(self, event=None):
192 '''
193 Pupose: setup for updating drawing
194 '''
195 self.Window.DrawData = self.MakeNewData()
196 self.Window.UpdateDrawing()
197
198 def SaveToFile(self,event):
199 '''
200 Purpose: output image to a *.png file
201 '''
202 dlg = wx.FileDialog(self, "Choose a name for file to contain image",
203 defaultDir = "",
204 defaultFile = "",
205 wildcard = "*.png",
206 style = wx.FD_SAVE)
207 if dlg.ShowModal() == wx.ID_OK:
208 self.Window.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG)
209 dlg.Destroy()
210
211 def MakeNewData(self):
212 '''
213 Purpose: generate some data for randomly shaped geometric
214 structures (rectangles, ellipses and polygons)
215 '''
216 MaxX, MaxY = self.Window.GetClientSize() # to limit the size of these structures
217
218 DrawData = {}
219 # Define some random rectangles
220 lst = []
221 for i in range(5):
222 w = random.randint(1,MaxX//2)
223 h = random.randint(1,MaxY//2)
224 x = random.randint(1,MaxX-w)
225 y = random.randint(1,MaxY-h)
226 lst.append( (x,y,w,h) )
227 DrawData["Rectangles"] = lst
228
229 # Define some random ellipses
230 lst = []
231 for i in range(5):
232 w = random.randint(1,MaxX//2)
233 h = random.randint(1,MaxY//2)
234 x = random.randint(1,MaxX-w)
235 y = random.randint(1,MaxY-h)
236 lst.append( (x,y,w,h) )
237 DrawData["Ellipses"] = lst
238
239 # Define some random Polygons
240 lst = []
241 for i in range(3):
242 points = []
243 for j in range(random.randint(3,8)):
244 point = (random.randint(1,MaxX),random.randint(1,MaxY))
245 points.append(point)
246 lst.append(points)
247 DrawData["Polygons"] = lst
248
249 return DrawData
250
251 class DemoApp(wx.App):
252 '''
253 Purpose: initialize frame to hold DC
254 Note:
255 1. Uses the default OnInit method in wx.App
256 '''
257 def OnInit(self):
258 frame = MyFrame()
259 self.SetTopWindow(frame)
260 return True
261
262 if __name__ == "__main__":
263 # Ok, let's run it ...
264 app = DemoApp(0)
265 app.MainLoop()
Comments
Special thanks to Robin Dunn (creator of wxPython) and Chris Barker (creator of this page and developer of wxPython applications). I hope that this minor revision will encourage others to develop more advanced Phoenix apps - Virgil Stokes 2018/12/08 vs@it.uu.se.