pygame code organization - python

I've started to learn making games with python/pygame and as though it is easy to make a working game quickly in pygame, there's no real tutorial on how to organize the code in a sensible way.
On the pygame tutorials page, I've found 3 ways to do it.
1- No use of classes, for small projects
2- MVC ruby-on-rails kind of structure but without the rails framework which results in something overly complicated and obscure (even with OO programming and rails knowledge)
3- C++-like structure as follows: (clean and intuitive but maybe not very much python-like?)
import pygame
from pygame.locals import *
class MyGame:
def __init__(self):
self._running = True
self._surf_display = None
self.size = self.width, self.height = 150, 150
def on_init(self):
pygame.init()
self._display_surf = pygame.display.set_mode(self.size)
pygame.display.set_caption('MyGame')
#some more actions
pygame.display.flip()
self._running = True
def on_event(self, event):
if event.type == pygame.QUIT:
self._running = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
self._running = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1:
print event.pos
def on_loop(self):
pass
def on_render(self):
pass
def on_cleanup(self):
pygame.quit()
def on_execute(self):
if self.on_init() == False:
self._running = False
while( self._running ):
for event in pygame.event.get():
self.on_event(event)
self.on_loop()
self.on_render()
self.on_cleanup()
if __name__ == "__main__" :
mygame = MyGame()
mygame.on_execute()
I'm used to make SDL games in C++ and I use this exact structure but I'm wondering if it's usable for both small and large projects or if there's a cleaner way in pygame.
For example I found a game organized like this:
imports
def functionx
def functiony
class MyGameObject:
class AnotherObject:
class Game: #pygame init, event handler, win, lose...etc
while True: #event loop
display update
It also looks very well organized and easy to follow.
Which structure should I use consistently in all my projects so as to have a clean code usable in small and large games?

I'd also suggest maybe using comments (as dreary as it seems as first) for dividing your work. As an example:
import pygame, random, math
## CLASSES ----------------------------------------------
class Ball():
def __init__(self, (x,y), size):
"""Setting up the new instance"""
self.x = x
self.y = y
self.size = size
## FUNCTIONS --------------------------------------------
def addVectors((angle1, length1), (angle2, length2)):
"""Take two vectors and find the resultant"""
x = math.sin(angle1) * length1 + math.sin(angle2) * length2
y = math.cos(angle1) * length1 + math.cos(angle2) * length2
## INIT -------------------------------------------------
width = 600
height = 400
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("S-kuru")
And so on.
As another option to consider, have you thought about using sub-modules? They're just other Python files (.py) where you place commonly-used functions.
def showText(passedVariable):
print passedVariable
return
This new file is the imported, just as math or random would be and the function used as such:
import mySubModule
mySubModule.showText("Hello!")
But that's just how I work. Absolutely follow what you can understand, not just now but next week or year also.

Do what you can follow. If you can make sense of the code you posted, then that's what you should use. If a different structure feels more natural, use that instead.

Related

pygame image.blit not working from one class but is working from the other [duplicate]

This question already has answers here:
Why is nothing drawn in PyGame at all?
(2 answers)
Why is my PyGame application not running at all?
(2 answers)
Closed 11 months ago.
I have wrecked my mental health going over and over and over this code. I have created two classes that are nearly identical yet my images from the second class are not rendering on the screen even though my first class's images are rendering perfectly fine. I've went over and over them to see if I can spot the difference between them that could be causing this but so far nothing. I've also tried swapping class one's image for class two's image and it renders the image perfectly fine from class one, so not an issue with the image itself. I'm lost. Any help from the good people here on stack would be beyond greatly appreciated. I'm sure its something stupidly simple that I'm overlooking but if I look over this code anymore I will go permanently cross-eyed.
Here is the snippet. TIA.
class one:
def __init__(self, num_value, bool, x, y):
self.num_value = num_value
self.bool = bool
self.x = x
self.y = y
self.one_image = pygame.image.load(os.path.join('Program_Assets', 'one.png'))
self.image_size = (250, 350)
self.one_image = pygame.transform.scale(self.one_image, self.image_size)
if bool == False:
self.one_image = pygame.transform.rotate(self.one_image, 90)
_VARS['surf'].blit(self.one_image, (self.x, self.y))
if (self.num_value == "1k"):
self.num_value = 1000
elif (self.num_value == "2k"):
self.num_value = 2000
elif (self.num_value == "10k"):
self.num_value = 10000
def setxy(self, x, y):
self.x, self.y = x, y
_VARS['surf'].blit(self.one_image, (self.x, self.y))
def getxy(self):
return self.x, self.y
class two:
def __init__(self, bool, x, y):
self.bool = bool
self.x = x
self.y = y
self.two_image = pygame.image.load(os.path.join('Program_Assets', 'two.png'))
self.image_size = (265, 175)
self.two_image = pygame.transform.scale(self.two_image, self.image_size)
if bool == True:
self.two_image = pygame.image.load(os.path.join('Program_Assets', 'two_alt.png'))
self.two_image = pygame.transform.scale(self.two_image, self.image_size)
_VARS['surf'].blit(self.two_image, (self.x, self.y))
def setxy(self, x, y):
self.x, self.y = x, y
_VARS['surf'].blit(self.two_image, (self.x, self.y))
def getxy(self):
return self.x, self.y
# GLOBAL VAR, Using a Dictionary.
_VARS = {'surf': False}
# This is the main loop, it constantly runs until you press the Q KEY
# or close the window.
# CAUTION: This will run as fast as your computer allows,
# if you need to set a specific FPS look at tick methods.
def main():
pygame.init() # Initial Setup
_VARS['surf'] = pygame.display.set_mode(SCREENSIZE)
is_running = True
# The loop proper, things inside this loop will
# be called over and over until you exit the window
clock = pygame.time.Clock()
t1 = two(True, 250, 350)
o1 = resistor("10k", True, 10, 10)
o2 = resistor("10k", False, 10, 200)
o1_selected = False
o2_selected = False
t1_activated = False
while is_running:
_VARS['surf'].fill(WHITE)
drawGrid(8)
o1_x, o1_y = o1.getxy()
o2_x, o2_y = o2.getxy()
t1_x, t1_y = t1.getxy()
clock.tick(30)
pygame.display.update()
pygame.quit()
if __name__ == '__main__':
main()
EDIT: just posted the loop this time -- all I did here was add your setxy()'s so that the blit ran -- again I get all the images...
Of course to get it work work on my system I had to use different images, add a SCREENSIZE, and WHITE. I commented out the drawGrid. I also have an event handler to QUIT -- but that should have nothing to do with displaying the images.
I did not make any intentional changes to your logic - just what was needed to get a display.
while is_running:
_VARS['surf'].fill(WHITE)
#drawGrid(8)
o1_x, o1_y = o1.getxy()
o2_x, o2_y = o2.getxy()
t1_x, t1_y = t1.getxy()
o1.setxy(o1_x, o1_y)
o2.setxy(o2_x, o2_y)
t1.setxy(t1_x, t1_y)
clock.tick(30)
pygame.display.update()
for event in pygame.event.get():
# print(pygame.event.event_name(event.type))
if event.type == pygame.QUIT:
is_running = False
print("Exit...")
I finally figured it out. The program was calling the other two images further down in my while loop via:
o1.setxy(o1_x, o1_y)
o2.setxy(o2_x, o2_y)
My first clue at this was when I set my pygame.display.update() nearer the top of my while loop based on some of the comments and suggestions I received here. When I ran my code with pygame.display.update() at the top of my while loop I lost my o1 and o2 images. Based on this finding I suspected that the images were being rendered somewhere further down in my while loop. It was at this point I noticed the o1.setxy & o2.setxy and finally had my Eureka! moment. I promptly added a
t1.setxy(t1_x, t1_y)
and voila! I now have a third image for my instance of my two class!!! Mental health is partially intact now and my eyes are no longer crossed. Thank you all so much for your help in flushing this out and achieving my renders.

Run 2 pygame windows with 2 different processes [duplicate]

I need to to build an application that has multiple windows. In one of these windows, I need to be able to play a simple game and another window has to display questions and get response from a user that influences the game.
(1) I was wanting to use pygame in order to make the game. Is there a simple way to have pygame operate with multiple windows?
(2) If there is no easy way to solve (1), is there a simple way to use some other python GUI structure that would allow for me to run pygame and another window simultaneously?
The short answer is no, creating two pygame windows in the same process is not possible (as of April 2015). If you want to run two windows with one process, you should look into pyglet or cocos2d.
An alternative, if you must use pygame, is to use inter-process communication. You can have two processes, each with a window. They will relay messages to each other using sockets. If you want to go this route, check out the socket tutorial here.
Internally set_mode() probably sets a pointer that represents the memory of a unique display. So if we write:
screenA = pygame.display.set_mode((500,480), 0, 32)
screenB = pygame.display.set_mode((500,480), 0, 32)
For instance we can do something like that later:
screenA.blit(background, (0,0))
screenB.blit(player, (100,100))
both blit() calls will blit on the same surface. screenA and screenB are pointing to the same memory address. Working with 2 windows is quite hard to achieve in pygame.
Yes, that is possible. SDL2 is able to open multiple windows. In the example folder you can take a look at "video.py".
https://github.com/pygame/pygame/blob/main/examples/video.py
"This example requires pygame 2 and SDL2. _sdl2 is experimental and will change."
I've been trying to do this for a few days now, and I'm finally making progress. Does this count as simple? Or even as "being in" pygame. In this exampe I never even call pygame.init() That just seems to get in the way. The event pump is running (for mouse and keyboard) but not all the normal events seem to be coming thru (FOCUSGAINED and LOST in particular). In this example each window renders it status (size, position, etc) to it's self. I also have versions where I mix SDL windows with the pygame display. But those involve encapsulating a Window rather than extending it.
In order to draw on these windows you can draw on a vanilla surface as usually and then use the Renderer associated with the window to create a texture that will update the the window. (texture.draw(), renderer.present). You dont't use display.update() or flip() because you you aren't using the pygame display surface.
The X11 package is just my experimental windowing stuff and has nothing to do with X11. I think all my imports are explicit so it should be easy to figure out what the missing pieces are.
from typing import List
from pygame import Rect, Surface, Color
import pygame.event
from pygame.event import Event
from pygame.freetype import Font
from pygame._sdl2.video import Window, Renderer, Texture
from X11.windows import DEFAULT_PAD, default_font, window_info
from X11.text import prt
class MyWindow(Window):
def __init__(self, font: Font=None):
super().__init__()
self._font = font if font else default_font()
self.resizable = True
self._renderer = None
def destroy(self) -> None:
super().destroy()
def update(self):
r = self.renderer
r.draw_color = Color('grey')
r.clear()
#self.render_line(f"TICKS: {pg.time.get_ticks()}", 5, size=16.0)
txt: List[str] = window_info(self)
self.render_text(txt, lineno=0)
r.present()
#property
def renderer(self):
if self._renderer is None:
try:
self._renderer = Renderer.from_window(self)
except:
self._renderer = Renderer(self)
return self._renderer
def render_text(self, txt: List[str], lineno: int=0):
for line in txt:
self.render_line(line, lineno, size=16.0)
lineno += 1
def render_line(self, txt: str, lineno: int = 0, size: float = 0.0):
font = self._font
line_spacing = font.get_sized_height(size) + DEFAULT_PAD
x = DEFAULT_PAD
y = DEFAULT_PAD + lineno * line_spacing
# compute the size of the message
src_rect = font.get_rect(txt, size=size)
# create a new surface (image) of text
l_surf = Surface((src_rect.width, src_rect.height))
src_rect = font.render_to(l_surf, (0, 0), txt, size=size)
# get ready to draw
texture = Texture.from_surface(self.renderer, l_surf)
dst = Rect(x, y, src_rect.width, src_rect.height)
texture.draw(None, dst)
_running: bool = False
def test():
global _running
win1 = MyWindow()
win2 = MyWindow()
my_windows = {win1.id: win1, win2.id: win2}
win = win1
rnd = win1.renderer
print("pygame.get_init():", pygame.get_init())
print("pygame.display.get_init():", pygame.display.get_init())
print("pygame.mouse.get_pos():", pygame.mouse.get_pos())
clock = pygame.time.Clock()
_running = True
while _running:
events = pygame.event.get()
for event in events:
if event.type != pygame.MOUSEMOTION:
print(event)
if event.type == pygame.QUIT:
_running = False
elif event.type == pygame.WINDOWENTER:
win = my_windows[event.window.id]
print(f"Enter Window ({event.window.id}")
elif event.type == pygame.WINDOWLEAVE:
pass
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
_running = False
if event.key == pygame.K_1:
win = my_windows[1]
rnd = win.renderer
if event.key == pygame.K_2:
win = my_windows[2]
rnd = win.renderer
elif event.key == pygame.K_b:
rnd.draw_color = Color('blue')
rnd.clear()
elif event.key == pygame.K_g:
rnd.draw_color = Color('grey')
rnd.clear()
elif event.key == pygame.K_t:
win.render_line("Hello, world")
elif event.key == pygame.K_s:
surface = pygame.display.get_surface()
print("surface: ", surface)
elif event.key == pygame.K_f:
pygame.display.flip()
# pygame.error: Display mode not set
elif event.key == pygame.K_u:
pygame.display.update()
# pygame.error: Display mode not set
for win in my_windows.values():
win.update()
clock.tick(40)
if __name__ == '__main__':
test()

Pygame through SSH does not register keystrokes (Raspberry Pi 3)

So I got raspi 3 and simple 8x8 LED matrix. After some playing with it I decided to make a simple snake game (displaying on that matrix) with pygame's events, I have no prior experience with pygame. There is no screen/display connected besides the led matrix.
So the problem at first was "pygame.error: video system not initialized", though I think i got it fixed by setting an env variable:
os.putenv('DISPLAY', ':0.0')
Now that I got it working I run it...and nothing happens, like no keystrokes are registered. Just this "junk", I don't know how to call it The dot on LED matrix is not moving. If i alter the snake's x or y position somewhere in the loop it moves as intended.
My code:
#!/usr/bin/python2
import pygame
import max7219.led as led
from max7219.font import proportional, SINCLAIR_FONT, TINY_FONT, CP437_FONT
import numpy as nqp
import os
SIZE = (8, 8)
class Board:
def __init__(self, size, snake):
"Board object for snake game"
self.matrix = np.zeros(size, dtype=np.int8)
self.device = led.matrix()
self.snake = snake
def draw(self):
#add snake
self.matrix = np.zeros(SIZE, dtype=np.int8)
self.matrix[self.snake.x][self.snake.y] = 1
for x in range(8):
for y in range(8):
self.device.pixel(x, y, self.matrix[x][y], redraw=False)
self.device.flush()
def light(self, x, y):
"light specified pixel"
self.matrix[x][y] = 1
def dim(self, x, y):
"off specified pixel"
self.matrix[x][y] = 0
class Snake:
def __init__(self):
"Object representing an ingame snake"
self.length = 1
self.x = 3
self.y = 3
if __name__=="__main__":
os.putenv('DISPLAY', ':0.0')
pygame.init()
snake = Snake()
board = Board(SIZE, snake)
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
snake.y -= 1
elif event.key == pygame.K_DOWN:
snake.y += 1
elif event.key == pygame.K_LEFT:
snake.x -= 1
elif event.key == pygame.K_RIGHT:
snake.x += 1
board.draw()
I'm using pygame because I don't know anything else (Well I can't use pygame either but I just don't know of any alternatives). If it can be done simpler I will be happy to do it. Thank You in advance!
You should be able to use curses. Here's a simple example:
import curses
def main(screen):
key = ''
while key != 'q':
key = screen.getkey()
screen.addstr(0, 0, 'key: {:<10}'.format(key))
if __name__ == '__main__':
curses.wrapper(main)
You'll see that your key presses are registered - they're just strings.
However, this runs in blocking mode. Assuming that your code needs to do other things, you can turn nodelay on:
def main(screen):
screen.nodelay(True)
key = ''
while key != 'q':
try:
key = screen.getkey()
except curses.error:
pass # no keypress was ready
else:
screen.addstr(0, 0, 'key: {:<10}'.format(key))
In your scenario you probably would put this inside your game loop that's drawing out to your 8x8 display, so it would look something like this:
game = SnakeGame()
while game.not_done:
try:
key = screen.getkey()
except curses.error:
key = None
if key == 'KEY_UP':
game.turn_up()
elif key == 'KEY_DOWN':
game.turn_down()
elif key == 'KEY_LEFT':
game.turn_left()
elif key == 'KEY_RIGHT':
game.turn_right()
game.tick()
One thing to note - this approach will take 100% of your CPU, so if you don't have some other way to limit what your app is doing it can cause you some problems. You could extend this approach using threading/multiprocessing, if you find that to be something that you need.

Pygame Menu: calling another .py file

I'm learning pygame by helping a friend make a Menu for his interactive TicTacToe game. It's simple enough, a title and 2 buttons for starting and exiting the game. He has the game done, I just need to link my menu to his game.
After fiddling with pygame, I finally finished making the images appear in the pygame window(I never thought that seeing text appear in a blank window for the first time could look so beautiful! T^T). I believe that the next step is to make the images act like buttons to call another python file.
Here is my simple code for the menu:
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
resX, resY = 640, 480
windowObj = pygame.display.set_mode((resX,resY))
titleImg = pygame.image.load("title.png")
startImg = pygame.image.load("start.png")
exitImg = pygame.image.load("exit.png")
def width_center(img):
"""For horizontally setting an image."""
return resX/2 - x(img)/2
def height_center(img):
"""For vertically setting an image."""
return resY/2 - y(img)/2
def x(img):
"""Width of object."""
return img.get_width()
def y(img):
"""Height of object."""
return img.get_height()
while True:
pygame.display.update()
windowObj.fill((255,255,255))
windowObj.blit(titleImg,(width_center(titleImg), 30))
#This I want to link:
windowObj.blit(startImg,(width_center(startImg),height_center(startImg)-10))
windowObj.blit(exitImg,(width_center(exitImg),height_center(exitImg)+y(startImg)))
for i in pygame.event.get():
if i.type == QUIT:
exit()
Simply put, I want to know how to make the startImg call TTTClick.py. Should there also be a certain format for his TTTClick.py?
Thanks :)
If it's one project, you can have 'title screen' state, and 'game' states.
Psuedo code:
class Game():
def __init__(self):
self.done = False
self.state = "title"
def game_draw(self):
# game draw
def game_update(self):
# game event handling, and physics
def title_draw(self):
# title draw
def title_update(self):
# on event click
self.state = "game"
def loop(self):
# main loop
while not self.done:
if state == 'title':
title_update()
title_draw()
elif state == 'game':
game_update()
game_draw()
if __name__ == "__main__":
game = Game()
game.loop()
Note: x, height_center ect. already exist in pygame.Rect
# if Surface
titleImage.get_rect().width
# if Sprite
titleImage.rect.width
There's more, rect.center, rect.centerx, see the full listing at http://www.pygame.org/docs/ref/rect.html

How do you call an instance method from Class "B" from within Class "A"?

I'm not sure if I'm using classes correctly. I'm attempting to build a simple menu using pygame. This is my first foray into gui stuff. I have no idea how to structure my code.
I found that I could make a general Button class to handle all of the mouse over/mouse click stuff, and then I can subclass that for each button, and override the do_action method to give each button a specific action.
class Button(pygame.sprite.Sprite):
global screen_flags
def __init__(self, images, pos):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = images[0]
self.rect = self.image.get_rect()
self.rect.move_ip(pos)
def update(self, events, surface):
# if not screen_flags['config']:
for event in events:
if event.type == MOUSEMOTION:
if self.rect.collidepoint(event.pos):
self.image = self.images[1]
else:
self.image = self.images[0]
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1 and self.rect.collidepoint(event.pos):
self.image = self.images[-1]
screen_flags['config'] = 1
self.do_action()
self.set_flag()
elif event.type == MOUSEBUTTONUP:
self.image = self.images[0]
screen.blit(self.image, self.rect)
def do_action(self):
pass
def set_flag(self):
pass
class CheckBox(Button):
def __init__(self, images, pos):
Button.__init__(self, images, pos)
self.is_clicked = False
def update(self, events, surface):
for event in events:
if event.type == MOUSEMOTION:
if not self.is_clicked:
if self.rect.collidepoint(event.pos):
self.image = self.images[1]
else:
self.image = self.images[0]
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1 and self.rect.collidepoint(event.pos):
if not self.is_clicked:
self.image = self.images[-1]
self.is_clicked = True
else:
self.image = self.images[0]
self.is_clicked = False
screen.blit(self.image, self.rect)
class Cancel(Button):
def do_action(self):
screen_flags['config'] = 0
So, as you can see,they don't really do anything yet. I can toggle the check box on and off, and open and close one 'config' window, but that's as far as I've gotten. The rest of the code:
global count
global sTime
config_button_img = load_sliced_images(25, 25, c_buttons)
config_button = Button(config_button_img, (608,4))
input_bar = load_sliced_images(351,33, inpt_bar)
text_box = Textbox(input_bar, (144,155))
s_button = load_sliced_images(110,32, sbmt_bttn)
submit = Button(s_button, (241,301))
c_button = load_sliced_images(110,32, cncl_bttn)
cancel = Cancel(c_button, (385, 301))
c_boxes = load_sliced_images(20,19, chk_box)
check_box = CheckBox(c_boxes, (200,200))
try:
while True:
# ****************************
# Pygame section
events = pygame.event.get()
for event in events:
if event.type == QUIT:
screen_flags['alert'] = 1
ding.play(0,1000)
elif event.type == MOUSEBUTTONDOWN:
text_box.set_focus(event.button, event.pos)
screen.blit(background, (0,0))
screen.blit(client_logo, (0,30))
screen.blit(tag, (174,462))
if screen_flags['config']:
screen.blit(config_window_img, (0,0))
submit.update(events, screen)
cancel.update(events, screen)
check_box.update(events, screen)
if screen_flags['alert']:
screen.blit(alert_dialog, (0,0))
config_button.update(events, screen)
pygame.display.update()
except KeyboardInterrupt:
try:
pygame.quit()
except:
pass
So this works kind of as expected. I'm unsure where to go from here. Do I continue to wrap up logic inside of the classes? For instance, the next thing I'm attempting to do is make it so that when the "cancel" button is clicked it unchecks the check box.
I tried changing the Cancel class to do just that.
class Cancel(Button):
def do_action(self):
screen_flags['config'] = 0
check_box.is_clicked=False
However, this is giving me a GlobalName Error. How do I target an instance method from within a different class? Is this even the correct way to go about it? Or have only some logic, like the update() stuff to take care of the mouse, in then handle what the classes do by passing variables to and from the different classes fro main()? Should I have all of the classes use global variables?
Is there any good articles on gui practices. Things like how to structure code etc..?
Hopefully the above makes sense.
Personally, I would make it so that each of my classes accepted screen_flags as an argument to the constructor (__init__). Then each of your classes would have a handle on the "global" data that they need. A real easy way to do this is...
class Cancel(Button):
def __init__(self,*args,**kwargs):
self.screen_flags=kwargs.pop('screen_flags')
Button.__init__(self,*args,**kwargs) #Some might advise you to use super here...
def do_action(self):
self.screen_flags['config'] = 0
#now use the cancel class
cancel=Cancel(c_button, (385, 301),screen_flags=screen_flags)
Of course, depending on what the shared data looks like (how many different variables you have, etc), you might want to pass a dictionary, or other object to make it so you don't need to pass 5000 different pieces of shared data around.
Another way of dealing with this is to define your "global" data in a class as "class variables" and then inherit from that class.
class GlobalData(object):
stuff=None
class FooButton(Button,GlobalData):
def do_action(self):
print self.stuff
#If you do something in here, don't do: self.stuff = blah
#instead, you *should* do: GlobalData.stuff = blah
#However, it is fine to change mutable objects in place.
# e.g. self.stuff["dict_key"]=blah
#Now we can assign to GlobalData and instances of
#FooButton will see the differences immediately.
cancel=FooButton(c_button, (385, 301))
cancel.do_action()
GlobalData.stuff="Cows say Moo"
cancel.do_action()
I hope this helps. There was a lot in you post, so sorting through it all was a little difficult.
EDIT
In case you don't understand how class variables are handled, see the comments in do_action. Basically, you need to be careful that you don't lose a handle on your data...
GUI stuff can be done nicer.
Yes, do wrap your control in classes.
I'd recommend to try this.
First, define logical interface for your controls. Forget about implementation details for a minute. Any control can be clicked; define method onClick(pos). Check boxes can be checked or unchecked; define setChecked(bool). Windows can be shown or hidden, define setVisible(bool), etc.
Create a common ancestor for clickable controls. Within its event handler, call onClick(event.pos). The default implementation would do nothing. Now you can call onClick() of a control when you want to imitate a click. You will want onMouseDown and onMouseUp to do things like click animation, onMouseIn and onMouseOut for hover events, etc. The only place where you'll care about the gory details of event dispatch will be your common ancestor.
Don't refer directly to global state; it leads to all sorts of unpleasant things. Instead, let every control know its state and how to change it (we're doing OOP anyway). So, the check box gets a isChecked() method, etc.
Now Cancel button would only have to override its onClick method and constructor. In constructor, pass the CheckBox instance; in cancel_button.onClick just call Button.onClick and then self.check_box.setChecked(False).

Categories