1
votes

I'm writing a Solitaire GUI using wxPython, and I'm on Windows 7. I've only written one GUI before (in Java Swing), so I'm not as familiar as I could be with all the different types of widgets and controls. I'm faced with the challenge of having resizable, cascading piles of cards in the Tableaux of the Solitaire board. To me, using BitmapButtons for each card (or at least for face-up cards) and having a panel contain a pile of cards seemed natural, since it is legal to move sub-piles of cards in the Tableau from pile to pile in Solitaire. I'm sure there is a better way to do this, but for now I've been fiddling with a smaller GUI (not my main GUI) to try and achieve this. I've attached the code for the test GUI below.

Note: My main GUI uses a GridBagSizer with 14 cells. I haven't tried using the following panel/buttons in the GridBagSizer, or even know if a GridBagSizer is the best way to go about this.

import wx

class MyFrame(wx.Frame): 
    def __init__(self, parent, id_, title): 
        wx.Frame.__init__(self, parent, id_, title, size=(810, 580))
        self.panel = wx.Panel(self, size=(72, 320), pos=(20,155))
        self.buttons = []
        self.init_buttons()

    def init_buttons(self):
        for i in range(6):
            face_down = wx.Image('img/cardback.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
            wid = face_down.GetWidth()
            hgt = face_down.GetHeight()
            bmpbtn = wx.BitmapButton(self.panel, -1, bitmap=face_down, pos=(20,155+7*i), size=(wid, hgt))
            bmpbtn.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
            self.buttons.append(bmpbtn)
        for i in range(1,14):
            rank = 14 - i
            if i % 2 == 0:
                filename = 'img/%sC.png' % rank
            else:
                filename = 'img/%sH.png' % rank
            img = wx.Image(filename, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
            wid = img.GetWidth()
            hgt = img.GetHeight()
            bmpbtn = wx.BitmapButton(self.panel, -1, bitmap=img, pos=(20, 177+20*i), size=(wid, hgt))
            bmpbtn.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
            self.buttons.append(bmpbtn)

    def onMouseOver(self, event):
        #event.Skip()
        pass

class MyApp(wx.App): 
    def OnInit(self): 
        wx.InitAllImageHandlers() 
        self.frame = MyFrame(None, -1, "Solitaire")         
        self.frame.Show(True) 
        self.SetTopWindow(self.frame) 
        return True 

app = MyApp(0) 
app.MainLoop()

This is what results from running:

http://oi44.tinypic.com/1zv4swj.jpg

Which I was satisfied with, until I moved my mouse over some of the buttons:

http://oi44.tinypic.com/2rdupmq.jpg

This must have to do with the EVT_ENTER_WINDOW event. I attempted to write an event handler, but realized I didn't really know how to achieve what I need. According to the docs, a BitmapButton has different bitmaps for each of its states - hover, focus, selected, inactive, etc. However, I do not want to change the Bitmap on a mouseover event. I simply want the button to stay put, and to not display itself on top of other buttons.

Any help would be greatly appreciated. Incidentally, if anybody has advice for a better way (than GridBagSizer and these panels of buttons) to implement this GUI, I would love that!

1

1 Answers

0
votes

I would recommend against using actual window controls for each of the cards. I would instead have a single canvas upon which you render the card bitmaps in their appropriate locations. You'll have to do a little extra math to determine what cards are being clicked on, but this is definitely the way to go.

Use a wx.Panel with a EVT_PAINT handler to do your drawing.

Here's a starting point that is written to use double-buffering to avoid flickering.

P.S. You can use bitmap = wx.Bitmap(path) to load an image, instead of bothering with wx.Image and converting it to a bitmap object.

import wx

class Panel(wx.Panel):
    def __init__(self, parent):
        super(Panel, self).__init__(parent)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_PAINT, self.on_paint)
        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
        self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
    def on_left_down(self, event):
        print 'on_left_down', event.GetPosition()
    def on_left_up(self, event):
        print 'on_left_up', event.GetPosition()
    def on_paint(self, event):
        dc = wx.AutoBufferedPaintDC(self)
        # Use dc.DrawBitmap(bitmap, x, y) to draw the cards here

class Frame(wx.Frame):
    def __init__(self):
        super(Frame, self).__init__(None)
        self.SetTitle('My Title')
        Panel(self)

def main():
    app = wx.App()
    frame = Frame()
    frame.Center()
    frame.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()