TkInter:销毁一些按钮,然后重新创建(可能不同的)按钮数量

时间:2011-02-10 02:29:49

标签: python user-interface button callback tkinter

我是Tkinter的新手,正在编写一个简单的瓷砖翻转游戏来学习基础知识。我的想法是,当我启动应用程序时,我会得到一个“开始新游戏”按钮,退出按钮和网格大小滑块。然后我可以选择一个网格大小并点击开始,它会显示一个n * n网格的红色或绿色按钮(图块)。通过点击一个,我改变了瓷砖的颜色和每个4路连接的瓷砖。一旦所有瓷砖都是绿色,瓷砖就会消失。允许我开始一个新游戏。

所以这就是问题所在,当我赢得游戏并开始新游戏或开始新游戏时,新游戏会出现,但是当我点击其中一个游戏时,我会得到以下内容:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python2.6/lib-tk/Tkinter.py", line 1413, in __call__
    return self.func(*args)
  File "/home/adejong/workspace/test2/view.py", line 116, in on_click
    self.view.update()
  File "/home/adejong/workspace/test2/view.py", line 84, in update
    button.set_colour(View.UP)
  File "/home/adejong/workspace/test2/view.py", line 112, in set_colour
    self.gridButton.config(bg=colour)
  File "/usr/lib/python2.6/lib-tk/Tkinter.py", line 1205, in configure
    return self._configure('configure', cnf, kw)
  File "/usr/lib/python2.6/lib-tk/Tkinter.py", line 1196, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TclError: invalid command name ".33325424.33325640.33327872"

我很确定它与我如何清除网格有关,但我无法弄清楚问题是什么。当我开始新游戏时,我最初无法显示按钮,所以可能会有这样的事情。

以下是发生这种情况的模块:

# @todo make wrappers for all Tkinter widgets
from Tkinter import *
from observerPattern import Observer
# USE Grid to organise widgets into a grid
class View(Observer):
    UP = "red"
    DOWN = "green"

    ## @brief initialises the GUI
    # @param master ie the root
    #@ model, a pointer to the model
    def __init__(self, master, model):

        View.instance = self
        self.model = model
        self.master = master

        self.frame = Frame(self.master)
        self.frame.grid() #size frame to fit the given text, and make itself visibl
        self.optionsGrid = Frame(self.frame)
        self.gameGrid = Frame(self.frame)
        self.optionsGrid.grid(row = 0, column = 0)
        self.gameGrid.grid(row = 1, column = 0)

        self.gridSizeScale = Scale(self.optionsGrid, label = "grid size", from_ = 2, to = 20, bigincrement = 1, 
                                   showvalue = True, orient = HORIZONTAL) 

        self.quit = Button(self.optionsGrid, text="QUIT", fg="red", command=self.frame.quit)


        self.newGame = Button(self.optionsGrid, text="Start New Game", command=self.init_game)
        self.objective = Label(self.optionsGrid, text="Make all the tiles green")

        self.newGame.grid(row=0, column=0)
        self.quit.grid(row=0, column=3)
        self.gridSizeScale.grid(row=0, column=1, columnspan=2)
        self.objective.grid(row=1, column=1, columnspan=2)
        self.gameButtons = []
        self.update()

    # start a new game by re building the grid
    def init_game(self):
        size = self.gridSizeScale.get()
        self.model.init_model(size)
        self.objective.config(text = "Make all the tiles green")
        print "MODEL INITIALISED"

        #for i in range(len(self.gameButtons)):
        #    self.gameButtons[i].grid_forget()
        for button in self.gameButtons:
                #button.destroy()
                #self.gameGrid.grid_forget(button)
                button.__del__()

        for button in range(size * size):
            buttonRow = int(button / size)
            buttonCol = button % size

            state = self.model.getTileState(buttonRow, buttonCol)
            if state == self.model.UP:
                initialColour = View.UP
            else:
                initialColour = View.DOWN

            newButton = GridButton(self.gameGrid, butRow = buttonRow, butCol = buttonCol, initColour = initialColour, model = self.model, view = self )

            self.gameButtons.append(newButton)
            print self.gameButtons
        self.gameGrid.grid()




    ## @brief gets the only View instance. A static method. Dont really need this
    # @param None
    # @returns the singleton View object     
    @staticmethod
    def getInstance():
        if hasattr(View, 'instance') and View.instance != None:
            return View.instance
        else:
            return View()

    # make sure the tiles are the right colour etc    
    def update(self):
        for button in self.gameButtons:
            state = self.model.getTileState(button.row, button.col)
            if state == self.model.UP:
                button.set_colour(View.UP)
            else:
                button.set_colour(View.DOWN)
        if self.model.check_win() == True and self.model.tilesInitialised:
            for button in self.gameButtons:
                #button.destroy()
                #self.gameGrid.grid_forget(button)
                button.__del__()

            #self.gameGrid.grid_forget()
            print "slaves", self.gameGrid.grid_slaves()
            self.objective.config(text = "You Win!")
            self.master.update()
            print "YOU WIN!"

# a wrapper i made so i could pass parameters into the button commands
class GridButton(Button):

    def __init__(self, master, butRow, butCol, initColour, model, view, *args, **kw):
        Button.__init__(self, master)
        self.gridButton = Button(master, command=self.on_click, *args, **kw)
        self.gridButton.grid(row = butRow, column = butCol )
        self.set_colour(initColour)
        self.row = butRow
        self.col = butCol
        self.model = model
        self.view = view

    def set_colour(self, colour):
        self.gridButton.config(bg=colour)

    def on_click(self):
        self.model.flip_tiles(Row = self.row, Col = self.col)
        self.view.update()

    def __del__(self):
       self.gridButton.destroy()
       del self

这是重置完成

from Tkinter import *
from model import Model
from view import View
from controller import Controller
import sys

root = Tk() #enter the Tkinter event loop


model = Model()
view = View(root, model)
#controller = Controller(root, model, view)
root.mainloop()

from Tkinter import *
import random

class Model:
    UP = 1
    DOWN = 0
    def __init__(self, gridSize=None):
        Model.instance = self
        self.init_model(gridSize)

    def init_model(self, gridSize):
        self.size = gridSize
        self.tiles = []
        self.won = False
        self.tilesInitialised = False
        if gridSize != None:
            self.init_tiles()


    ## @brief gets the only Model instance. A static method
    # @param None
    # @returns the singleton Model object     
    @staticmethod
    def getInstance(gridSize = None):
        if hasattr(Model, 'instance') and Model.instance != None:
            return Model.instance
        else:
            return Model(gridSize)

    #initially init tile vals randomly but since not all problems can be solved
        # might want to start with a soln and work backwards

    def init_tiles(self):
        # i should also make sure they're not all 0 to start with
        self.tiles = []
        for row in range(self.size):
            rowList = []
            for col in range(self.size):
                rowList.append(random.randint(Model.DOWN, Model.UP))
            self.tiles.append(rowList)
        self.check_win()
        if self.won == True:
            self.init_tiles()
        self.tilesInitialised = True

    # tile indexing starts at 0            
    def flip_tiles(self, selected = None, Row = None, Col = None):
        if selected == None and (Row == None and Col == None):
            raise IOError("Need a tile to flip")
        elif selected != None:
            neighbours = self.get_neighbours(selected)

            for r in neighbours:
                for c in r:
                    if self.tiles[r][c] == Model.DOWN:
                        self.tiles[r][c] = Model.UP
                    elif self.tiles[r][c] == Model.UP:
                        self.tiles[r][c] = Model.DOWN

        else:
            selectedTile = Row, Col
            neighbours = self.get_neighbours(selectedTile)
            for tile in neighbours:
                    r = tile[0]
                    c = tile[1]
                    if self.tiles[r][c] == Model.DOWN:
                        self.tiles[r][c] = Model.UP
                    elif self.tiles[r][c] == Model.UP:
                        self.tiles[r][c] = Model.DOWN

    # selected is a tuple (row, column)  
    # returns a list of tuples of tiles to flip 
    def get_neighbours(self, selected):
        row = selected[0]
        col = selected[1]
        tilesToFlip = []
        for modifier in range(-1,2):
            rowIndex = row + modifier

            if rowIndex < 0:
                pass
            elif rowIndex > self.size - 1 :
                pass
            else:
                final = rowIndex, col
                tilesToFlip.append(final)


        for modifier in range(-1,2):   
            colIndex = col + modifier

            if colIndex < 0:
                pass
            elif colIndex > self.size - 1:
                pass
            else:
                final = row, colIndex
                tilesToFlip.append(final)


        neighbours = set(tilesToFlip)
        return neighbours


    def check_win(self):
        self.won = True
        #everytime a tile is selected
        for row in range(len(self.tiles)):
            for col in range(len(self.tiles)):

                if self.tiles[row][col] == Model.UP:
                    self.won = False
                    break

        return self.won

    def getTileState(self, buttonRow, buttonCol):
        return self.tiles[buttonRow][buttonCol]

非常感谢任何帮助

1 个答案:

答案 0 :(得分:1)

您好像没有重置self.game_buttons。您可以在__init__中将其设置为init_game中的空列表。因为它没有重置,所以第二次运行游戏self.view.update()会迭代包含两个游戏按钮的列表。由于缺少一半这些按钮,因此当您第一次尝试更改现在删除的按钮的颜色时会出现错误。

顺便说一句:一个真正简单的方法是将所有按钮作为内框的子项。这样做您只需删除框架即可删除其所有子项。作为附带好处,您不需要按钮列表,因为您可以询问所有子项的框架(winfo_children)。