How to create a game for wxPython - Part 3 (Phoenix)
Keywords : Minesweeper, Pentomino, Sudoku.
Contents
Demonstrating :
Tested py3.x, wx4.x and Win10.
Are you ready to use some samples ?
Test, modify, correct, complete, improve and share your discoveries !
Minesweeper
1 # minesweeper.py
2
3 """
4
5 Author : ???
6 Website : https://www.programmersought.com/article/50683537740/
7
8 """
9
10 import wx
11 from random import randint
12
13 # class MyFrame
14
15 #---------------------------------------------------------------------------
16
17 class MyFrame(wx.Frame):
18 def __init__(self):
19 # Set the window to a fixed size.
20 wx.Frame.__init__(self, None, -1,
21 'Minesweeper',
22 size=(515, 503),
23 style=wx.SYSTEM_MENU |
24 wx.MINIMIZE_BOX |
25 wx.CLOSE_BOX |
26 wx.CAPTION)
27
28 self.time_count = 0 # Initialize the timing to 0.
29 self.mine_last = 100 # Initialize the number of mines to 100.
30 self.get_mine_set() # Get Ray's index collection.
31 self.get_info_set() # Get prompt information (index and mine number) collection.
32 self.del_index_set = set() # Store the deleted button index.
33 self.get_bmp_list() # Get bitmap object list.
34 self.SetIcon(wx.Icon(self.bmp_list[9])) # Set title icon.
35 self.create_menu() # Generate menu bar.
36 self.create_panel() # Generate artboard.
37 self.create_timer() # Generate timer.
38 self.create_text_info() # Generate prompt information (thunder and time).
39 self.create_state_btn() # Generate status button.
40 self.create_btn_list() # Generate all buttons.
41
42 #-----------------------------------------------------------------------
43
44 # Menu bar events.
45 def e_menu_new(self, event):
46 pass
47
48
49 def e_menu_level1(self, event):
50 pass
51
52
53 def e_menu_level2(self, event):
54 pass
55
56
57 def e_menu_level3(self, event):
58 pass
59
60
61 def e_menu_custom(self, event):
62 pass
63
64
65 def e_menu_mark(self, event):
66 pass
67
68
69 def e_menu_color(self, event):
70 pass
71
72
73 def e_menu_sound(self, event):
74 pass
75
76
77 def e_menu_list(self, event):
78 pass
79
80
81 def e_menu_exit(self, event):
82 self.Destroy()
83
84
85 def e_menu_catalogue(self, event):
86 pass
87
88
89 def e_menu_search(self, event):
90 pass
91
92
93 def e_menu_help(self, event):
94 pass
95
96
97 def e_menu_about(self, event):
98 dlg = wx.MessageDialog(self,
99 "This is a small game.\n",
100 "About Minesweeper",
101 wx.OK | wx.CENTRE | wx.ICON_INFORMATION)
102 dlg.ShowModal()
103 dlg.Destroy()
104
105
106 def create_menu(self):
107 menu_bar = wx.MenuBar() # Create menu bar.
108
109 # Game menu.
110 gm_menu = wx.Menu() # Create a menu object.
111 new = gm_menu.Append(-1,"Start (N) \ tF2") # Add submenu to menu object.
112 gm_menu.AppendSeparator() # Insert a dividing line.
113 level1 = gm_menu.Append(-1,"Elementary (B)")
114 level2 = gm_menu.Append(-1,"Intermediate (I)")
115 level3 = gm_menu.Append(-1,"Advanced (E)")
116 custom = gm_menu.Append(-1, "Custom (C) ...")
117 gm_menu.AppendSeparator() # Insert a dividing line.
118 mark = gm_menu.Append(-1,"Mark (?) (M)")
119 color = gm_menu.Append(-1,"Color (L)")
120 sound = gm_menu.Append(-1,"Sound (S)")
121 gm_menu.AppendSeparator()
122 rs_list = gm_menu.Append(-1, "League of Minesweeper (T) ...")
123 exit_gm = gm_menu.Append(-1, "Exit (X)")
124 menu_bar.Append(gm_menu,"& Game") # Add the File menu to the menu bar.
125
126 # Help menu.
127 self.help_menu = wx.Menu()
128 catalogue = self.help_menu.Append(2,"Directory (C) \ tF1")
129 self.help_menu.AppendSeparator()
130 search_help = self.help_menu.Append(3,"Find Help Topic (S) ...")
131 use_help = self.help_menu.Append(4,"Use Help (H)")
132 self.help_menu.AppendSeparator()
133 about = self.help_menu.Append(9,"About mine clearance (A) ...")
134 menu_bar.Append(self.help_menu,"& Help (H)")
135
136 # Setup menu bar.
137 self.SetMenuBar(menu_bar)
138
139 # Bind menu events.
140 self.Bind(wx.EVT_MENU, self.e_menu_new, new)
141 self.Bind(wx.EVT_MENU, self.e_menu_level1, level1)
142 self.Bind(wx.EVT_MENU, self.e_menu_level2, level2)
143 self.Bind(wx.EVT_MENU, self.e_menu_level3, level3)
144 self.Bind(wx.EVT_MENU, self.e_menu_custom, custom)
145 self.Bind(wx.EVT_MENU, self.e_menu_mark, mark)
146 self.Bind(wx.EVT_MENU, self.e_menu_color, color)
147 self.Bind(wx.EVT_MENU, self.e_menu_sound, sound)
148 self.Bind(wx.EVT_MENU, self.e_menu_list, rs_list)
149 self.Bind(wx.EVT_MENU, self.e_menu_exit, exit_gm)
150 self.Bind(wx.EVT_MENU, self.e_menu_catalogue, catalogue)
151 self.Bind(wx.EVT_MENU, self.e_menu_search, search_help)
152 self.Bind(wx.EVT_MENU, self.e_menu_help, use_help)
153 self.Bind(wx.EVT_MENU, self.e_menu_about, about)
154
155
156 def create_panel(self):
157 self.panel = wx.Panel(self)
158 self.panel.Bind(wx.EVT_PAINT, self.e_paint)
159
160
161 def get_mine_set(self):
162 self.mine_set = set()
163 while(len(self.mine_set)<100):
164 self.mine_set.add(randint(0, 719))
165
166
167 def get_info_set(self):
168 self.info_set = set()
169 info_set = set()
170 for i in self.mine_set:
171 info_set.update({i-31, i-30, i-29, i-1, i+1, i+29, i+30, i+31})
172 for i in info_set:
173 if i not in self.mine_set and i in range(0,720):
174 count = 0
175 for j in [i-31, i-30, i-29, i-1, i+1, i+29, i+30, i+31]:
176 if j in self.mine_set:
177 count += 1
178 self.info_set.add((i,count))
179
180
181 def get_bmp_list(self):
182 image_list = ['smile.png', 'oh.png', 'cry.png', 'com.png', 'flag.png', 'ques.png',
183 'mine.png', 'emine.png', 'redmine.png','icon.png']
184 self.bmp_list = [wx.Image(i, wx.BITMAP_TYPE_PNG).ConvertToBitmap() for i in image_list]
185
186
187 def create_state_btn(self):
188 self.state_btn = wx.BitmapButton(self.panel, -1, self.bmp_list[0], (240, 10), (25, 25))
189 self.state_btn.Bind(wx.EVT_BUTTON, self.e_restart)
190
191
192 def create_text_info(self):
193 self.mine_text = wx.StaticText(self.panel, -1, '100', (25, 10))
194 self.mine_text.SetFont(wx.Font(20, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.NORMAL, faceName="Bold Body"))
195 self.mine_text.SetForegroundColour(wx.RED)
196 self.time_text = wx.StaticText(self.panel, -1, '000', (440, 10))
197 self.time_text.SetFont(wx.Font(20, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.NORMAL, faceName="Bold Body"))
198 self.time_text.SetForegroundColour(wx.RED)
199
200
201 def create_timer(self):
202 self.timer = wx.Timer(self)
203 self.Bind(wx.EVT_TIMER, self.e_time, self.timer)
204
205
206 def draw_panel(self):
207 self.dc = wx.ClientDC(self.panel)
208 self.dc.SetBrush(wx.Brush(wx.Colour(192, 192, 192))) # Set fill color.
209 self.dc.SetPen(wx.GREY_PEN) # Set line color.
210 self.dc.DrawRectangle(9, 50, 480, 384) # Draw a frame.
211 for i in range(1, 24):
212 self.dc.DrawLine(9, 16 * i + 50, 480 + 9, 16 * i + 50) # Draw horizontal lines.
213 for i in range(1, 30):
214 self.dc.DrawLine(16 * i + 9, 50, 16 * i + 9, 384 + 50) # Draw a vertical line.
215 for i in self.mine_set:
216 self.dc.DrawBitmap(self.bmp_list[6],i % 30 * 16 + 10, i // 30 * 16 + 1 + 50) # Paint mine.
217 text_color_dict = {1: '#0000ff', 2: '#008000', 3: '#ff0000', 4: '#000080', 5: '#800000',
218 6: '#e4007f', 7: '#6a3906', 8: '#000000'} # Quantity and color dictionary.
219 for i in self.info_set:
220 self.dc.SetTextForeground(text_color_dict[i[1]]) # Find the corresponding color.
221 self.dc.DrawText(str(i[1]), i[0] % 30 * 16 + 5 + 9, i[0] // 30 * 16 + 1 + 50) # Show thunder.
222
223
224 def create_btn_list(self):
225 self.btn_list = []
226 index = 0
227 for i in range(0, 24):
228 for j in range(0, 30):
229 btn = wx.BitmapButton(self.panel,-1,self.bmp_list[3],(j * 16+9, i * 16+50),(17, 17))
230 btn.index = index
231 btn.state = 0
232 self.btn_list.append(btn)
233 index += 1
234 for i in self.btn_list:
235 i.Bind(wx.EVT_LEFT_DOWN,self.e_left_down)
236 i.Bind(wx.EVT_LEFT_UP,self.e_left_up)
237 i.Bind(wx.EVT_RIGHT_DOWN,self.e_right_down)
238
239
240 def stop_game(self,index):
241 self.state_btn.SetBitmap(self.bmp_list[2])
242 self.timer.Stop()
243 error_marked_set = set()
244 for i, j in enumerate(self.btn_list):
245 if i not in self.del_index_set:
246 if i in self.mine_set:
247 if j.state != 1:
248 j.Destroy()
249 self.del_index_set.add(i)
250 else:
251 if j.state == 1:
252 j.Destroy()
253 self.del_index_set.add(i)
254 error_marked_set.add(i)
255 if i not in self.del_index_set:
256 j.Unbind(wx.EVT_LEFT_UP, handler=self.e_left_up) # The remaining buttons unbind all events.
257 j.Unbind(wx.EVT_LEFT_DOWN, handler=self.e_left_down)
258 j.Unbind(wx.EVT_RIGHT_DOWN, handler=self.e_right_down)
259 self.panel.Unbind(wx.EVT_PAINT, handler=self.e_paint) # Unbind drawing events.
260 self.draw_panel() # Regenerate the panel.
261 self.dc.DrawBitmap(self.bmp_list[8],index % 30 * 16 + 10, index // 30 * 16 + 1 + 50) # Draw red fried mine.
262 self.dc.SetBrush(wx.TRANSPARENT_BRUSH)
263 self.dc.DrawRectangle(index % 30 * 16 + 9, index // 30 * 16 + 50, 16, 16)
264 for i in error_marked_set:
265 self.dc.DrawBitmap(self.bmp_list[7], i % 30 * 16 + 10, i // 30 * 16 + 1 + 50) # Paint mine.
266
267
268 def del_btn(self,btn,index):
269 self.state_btn.SetBitmap(self.bmp_list[0])
270 if index not in self.del_index_set and btn.state != 1:
271 if index in [i[0] for i in self.info_set]:
272 btn.Destroy()
273 self.del_index_set.add(index)
274 else:
275 if index not in self.mine_set:
276 btn.Destroy()
277 self.del_index_set.add(index)
278 for j in [index - 31, index - 30, index - 29, index - 1, index + 1, index + 29, index + 30,
279 index + 31]:
280 if j in range(0, 720) and j not in self.mine_set \
281 and j not in self.del_index_set \
282 and self.btn_list[j].state != 1:
283 self.btn_list[j].Destroy()
284 self.del_index_set.add(j)
285
286
287 def e_paint(self, event):
288 self.draw_panel()
289
290
291 def e_time(self, event):
292 self.time_count += 1
293 self.time_text.SetLabel('%03d'%self.time_count)
294
295
296 def e_left_up(self, event):
297 if not self.timer.IsRunning():
298 self.timer.Start(1000)
299 btn = event.GetEventObject()
300 index = btn.index
301 if btn.state == 1:
302 self.state_btn.SetBitmap(self.bmp_list[0])
303 else:
304 if index in self.mine_set:
305 self.stop_game(index)
306 else:
307 self.del_btn(btn,index)
308
309
310 def e_left_down(self, event):
311 self.state_btn.SetBitmap(self.bmp_list[1])
312
313
314 def e_right_down(self, event):
315 btn = event.GetEventObject()
316 index = btn.index
317 label = btn.state
318 if label == 0:
319 btn.SetBitmap(self.bmp_list[4])
320 btn.state = 1
321 self.mine_last -= 1
322 self.mine_text.SetLabel('%03d' %self.mine_last)
323 elif label == 1:
324 btn.SetBitmap(self.bmp_list[5])
325 btn.state = 2
326 self.mine_last += 1
327 self.mine_text.SetLabel('%03d' % self.mine_last)
328 else:
329 btn.SetBitmap(self.bmp_list[3])
330 btn.state = 0
331
332
333 def e_restart(self, event):
334 # End.
335 if self.timer.IsRunning():
336 self.timer.Stop() # Clear timer.
337 for i,j in enumerate(self.btn_list):
338 if i not in self.del_index_set:
339 j.Destroy() # Delete all buttons.
340 # Restart.
341 self.get_mine_set()
342 self.get_info_set()
343 self.panel.Bind(wx.EVT_PAINT, self.e_paint)
344 self.time_count = 0
345 self.mine_last = 100
346 self.del_index_set = set()
347 self.state_btn.SetBitmap(self.bmp_list[0])
348 self.mine_text.SetLabel('100')
349 self.time_text.SetLabel('000')
350 self.create_btn_list()
351
352 #---------------------------------------------------------------------------
353
354 if __name__=='__main__':
355 app = wx.App()
356 frame = MyFrame()
357 frame.Show()
358 app.MainLoop()
Pentomino
1 # pentomino.py
2
3 """
4
5 Pentomino puzzle solver
6 Author : Jean-Claude Rimbault (pynokio.org, 2005)
7 Modified for wx : 14-november-2011 by keiji imoto
8 Website : https://www.nips.ac.jp/huinfo/documents/python/python_ex02.html
9 Department of information physiology
10 National institute for physiological sciences
11 Okazaki, japan
12
13 """
14
15 import wx
16
17 # class MyPentomino
18 # class MyFrame
19 # class MyPanel
20
21 #---------------------------------------------------------------------------
22
23 class MyPentomino():
24 def __init__(self, parent):
25
26 self.parent = parent
27 self.w = 10
28 self.h = 6
29 self.board = ['#'] * 160
30
31 for row in range(self.w):
32 for col in range(self.h):
33 self.board[row*10+col+11] = ' '
34
35 self.runmode = 0
36 self.n = 0
37
38 self.pieces = [ 'C', 'X', 'T', 'Y', 'F', 'W',
39 'P', 'I', 'S', 'L', 'V', 'N' ]
40 self.shapes = {
41 'C': ((0, 1, 10, 20, 21),
42 (0, 1, 11, 20, 21),
43 (0, -10, -9, -8, 2),
44 (0, 10, 11, 12, 2)),
45 'F': ((0, 1, -9, 2, 12),
46 (0, 1, 11, 2, -8),
47 (0, 10, 11, 21, 12),
48 (0, -10, -9, -19, -8),
49 (0, 1, 11, 21, 12),
50 (0, 1, -9, -19, -8),
51 (0, 1, 11, -9, 12),
52 (0, 1, 11, -9, -8)),
53 'I': ((0, 1, 2, 3, 4),
54 (0, 10, 20, 30, 40)),
55 'L': ((0, 1, 2, 3, 13),
56 (0, 1, 2, 3, -7),
57 (0, 10, 20, 30, 1),
58 (0, -10, -20, -30, 1),
59 (0, 1, 11, 21, 31),
60 (0, 1, -9, -19, -29),
61 (0, 10, 1, 2, 3),
62 (0, -10, 1, 2, 3)),
63 'N': ((0, 1, 11, 12, 13),
64 (0, 1, -9, -8, -7),
65 (0, 1, 2, 12, 13),
66 (0, 1, 2, -8, -7),
67 (0, 10, 20, 21, 31),
68 (0, 10, 20, 19, 29),
69 (0, 10, 11, 21, 31),
70 (0, 10, 9, 19, 29)),
71 'P': ((0, 1, 2, 11, 12),
72 (0, 1, 2, -9, -8),
73 (0, 1, 2, 10, 11),
74 (0, 1, 2, -10, -9),
75 (0, 1, 10, 11, 20),
76 (0, 1, 10, 11, 21),
77 (0, 1, -10, -9, -20),
78 (0, 1, -10, -9, -19)),
79 'S': ((0, 1, 11, 21, 22),
80 (0, 1, -9, -19, -18),
81 (0, 10, 11, 12, 22),
82 (0, -10, -9, -8, -18)),
83 'T': ((0, 1, 11, 21, 2),
84 (0, 1, -9, -19, 2),
85 (0, 1, 2, -8, 12),
86 (0, 10, -10, 1, 2)),
87 'V': ((0, 1, 2, 12, 22),
88 (0, 1, 2, -8, -18),
89 (0, 1, 2, 10, 20),
90 (0, 1, 2, -10, -20)),
91 'W': ((0, 1, 11, 12, 22),
92 (0, 1, -9, -8, -18),
93 (0, 10, 11, 21, 22),
94 (0, -10, -9, -19, -18)),
95 'X': ((0, -9, 1, 11, 2),),
96 'Y': ((0, 1, 2, 3, 12),
97 (0, 1, 2, 3, -8),
98 (0, 10, 20, 30, 11),
99 (0, -10, -20, -30, -9),
100 (0, 1, 11, 21, -9),
101 (0, 1, -9, -19, 11),
102 (0, 11, 1, 2, 3),
103 (0, -9, 1, 2, 3))
104 }
105
106 #-----------------------------------------------------------------------
107
108 def display(self):
109 self.parent.statusbar.SetStatusText('n = ' + str(self.n))
110 self.panel.Refresh()
111 self.panel.Update()
112
113 #
114 # main loop
115 #
116 def solve(self):
117 for q in range(len(self.board)):
118 try:
119 if self.board[q] == ' ':
120 for p in self.pieces:
121 for s in self.shapes[p]:
122 for c in s:
123 for d in s:
124 if self.board[q+d-c] != ' ':
125 break
126 else:
127 for d in s:
128 self.board[q+d-c] = p
129 i = self.pieces.index(p)
130 self.pieces.remove(p)
131 if not self.pieces:
132 self.n += 1
133 self.display()
134 else:
135 if self.runmode:
136 self.display()
137 self.solve()
138 self.pieces.insert(i, p)
139 for d in s:
140 self.board[q+d-c] = ' '
141 return
142 except(KeyboardInterrupt, SystemExit):
143 raise
144
145
146 def start(self, event):
147 self.solve()
148
149 #---------------------------------------------------------------------------
150
151 class MyFrame(wx.Frame):
152 def __init__(self, parent, id, title, ww, wh):
153 wx.Frame.__init__(self, parent, id,
154 title, size=(ww, wh),
155 style=wx.SYSTEM_MENU |
156 wx.MINIMIZE_BOX |
157 wx.CLOSE_BOX |
158 wx.CAPTION)
159
160 self.SetIcon(wx.Icon('./icons/wxwin.ico', wx.BITMAP_TYPE_ICO))
161
162 #------------
163
164 self.pen = MyPentomino(self)
165
166 self.panel = MyPanel(self, self.pen)
167 self.pen.panel = self.panel
168
169 self.isRunning = False
170
171
172 self.statusbar = self.CreateStatusBar()
173 self.startButton = wx.Button(self.panel, wx.ID_ANY, '&Start', (140, 240))
174 self.Bind(wx.EVT_BUTTON, self.buttonControl)
175
176 self.Centre()
177 self.Show(True)
178
179 #-----------------------------------------------------------------------
180
181 def buttonControl(self, event):
182
183 if self.isRunning == False:
184 self.pen.start(event)
185 isRunning = True
186
187
188 #---------------------------------------------------------------------------
189
190 class MyPanel(wx.Panel):
191 def __init__(self, parent, pen):
192 wx.Panel.__init__(self, parent)
193
194 self.pen = pen
195 self.w = self.pen.w
196 self.h = self.pen.h
197
198 self.Bind(wx.EVT_PAINT, self.OnPaint)
199
200 self.SetFocus()
201
202 self.colors = {
203 ' ': (0,0,0),
204 'X': (0xEF, 0x84, 0x5c),
205 'T': (0xF9, 0xC2, 0x70),
206 'C': (0xFF, 0xF6, 0x7F),
207 'W': (0xC1, 0xDB, 0x81),
208 'I': (0x69, 0xBD, 0x83),
209 'S': (0x61, 0xC1, 0xBE),
210 'F': (0x54, 0xC3, 0xF1),
211 'P': (0x6C, 0x9B, 0xD2),
212 'L': (0x79, 0x6B, 0xAF),
213 'V': (0xBA, 0x79, 0xB1),
214 'Y': (0xEE, 0x87, 0xB4),
215 'N': (0xEF, 0x85, 0x8C),
216 }
217
218 self.light = {
219 ' ': (0,0,0),
220 'X': (0xF8, 0xC5, 0xAC),
221 'T': (0xFC, 0xE2, 0xBA),
222 'C': (0xFF, 0xFB, 0xC7),
223 'W': (0xE2, 0xEE, 0xC5),
224 'I': (0xBE, 0xDF, 0xC2),
225 'S': (0xBC, 0xE1, 0xDF),
226 'F': (0xBA, 0xE3, 0xF9),
227 'P': (0xBB, 0xCC, 0xE9),
228 'L': (0xBB, 0xB3, 0xD8),
229 'V': (0xDB, 0xBE, 0xDA),
230 'Y': (0xF7, 0xC9, 0xDD),
231 'N': (0xF7, 0xC7, 0xC6),
232 }
233
234 self.dark = {
235 ' ': (0, 0, 0),
236 'X': (0xE6, 0x00, 0x12),
237 'T': (0xF3, 0x98, 0x00),
238 'C': (0xFF, 0xF1, 0x00),
239 'W': (0x8F, 0xC3, 0x1F),
240 'I': (0x00, 0x99, 0x44),
241 'S': (0x00, 0x9E, 0x96),
242 'F': (0x00, 0xA0, 0xE9),
243 'P': (0x00, 0x68, 0xB7),
244 'L': (0x1D, 0x20, 0x88),
245 'V': (0x92, 0x07, 0x83),
246 'Y': (0xE4, 0x00, 0x7F),
247 'N': (0xE5, 0x00, 0x4F),
248 }
249
250 #-----------------------------------------------------------------------
251
252 def OnPaint(self, event):
253 dc = wx.PaintDC(self)
254 for col in range(self.h):
255 for row in range(self.w):
256 x = 30*row + 20
257 y = 30*col + 40
258 c = self.pen.board[row*10+col+11]
259
260 dc.SetBrush(wx.Brush(self.colors[c]))
261 dc.DrawRectangle(x, y, 30, 30)
262
263 dc.SetPen(wx.Pen(self.light[c]))
264 dc.DrawLine(x, y + 29, x, y)
265 dc.DrawLine(x, y, x + 29, y)
266
267 dc.SetPen(wx.Pen(self.dark[c]))
268 dc.DrawLine(x + 1, y + 29, x + 29, y + 29)
269 dc.DrawLine(x + 29, y + 29, x + 29, y + 1)
270
271 #---------------------------------------------------------------------------
272
273 def main():
274 app = wx.App()
275 frame = MyFrame(None, -1, 'Pentomino puzzle solver', 355, 340)
276 app.MainLoop()
277
278 #---------------------------------------------------------------------------
279
280 if __name__ == '__main__':
281 main()
Sudoku
Download source
Additional Information
Link :
https://wiki.python.org/moin/GameProgramming
http://mientki.ruhosting.nl/data_www/pylab_works/pw_bricks_2d_scene.html
- - - - -
https://wiki.wxpython.org/TitleIndex
Thanks to
??? (minesweeper.py coding), Jean-Claude Rimbault (pentomino.py coding), Reverend Jim & Daniweb (sudoku.py coding), the wxPython community...
About this page
Date(d/m/y) Person (bot) Comments :
12/02/21 - Ecco (Created page for wxPython Phoenix).
Comments
- blah, blah, blah...