简体   繁体   中英

tkinter Button doesn't call my event function

this is my first time asking a question here, so please bear with me. I'm trying to write a tic-tac-toe game for two players that is supposed to notify you when a player has won and who won. The game worked well, but then I tried doing it with OOP and it stopped working. As you probably can see, I'm quite new to it and haven't grasped the concept completely yet. TkInter is also something I have never worked with before.

I tried putting the click function before and after the __init__ function but neither works.

from tkinter import *
from tkinter.font import Font
import tkinter.messagebox

turn = True
playerX = False  #If True, Player X won
playerO = False  #If True, Player O won
try:
    while True:
        class Game():
            def click(self, button):
                global turn
                global playerX
                global playerO
                ########## Test whether button is blank and then inserts 'X'
                if(button["text"]=="" and turn == True):
                    button["text"]= "X"
                    if(button_1["text"]=="X" and button_2["text"]=="X" and button_3["text"]=="X" or
                        button_4["text"]=="X" and button_5["text"]=="X" and button_6["text"]=="X" or
                        button_7["text"]=="X" and button_8["text"]=="X" and button_9["text"]=="X" or
                        button_1["text"]=="X" and button_5["text"]=="X" and button_9["text"]=="X" or
                        button_3["text"]=="X" and button_5["text"]=="X" and button_7["text"]=="X" or
                        button_1["text"]=="X" and button_4["text"]=="X" and button_7["text"]=="X" or
                        button_2["text"]=="X" and button_5["text"]=="X" and button_8["text"]=="X" or
                        button_3["text"]=="X" and button_6["text"]=="X" and button_9["text"]=="X"):
                        tkinter.messagebox.showinfo(title="Congrats", message="Player X won!")
                        self.root.update()
                        playerX = True
                        exit()
                    self.root.title("Player O")
                    turn=False
                ########### Test whether button is blank and then inserts 'O'   
                elif(button["text"]=="" and turn == False):
                    button["text"] = "O"
                    if(button_1["text"]=="O" and button_2["text"]=="O" and button_3["text"]=="O" or
                        button_4["text"]=="O" and button_5["text"]=="O" and button_6["text"]=="O" or
                        button_7["text"]=="O" and button_8["text"]=="O" and button_9["text"]=="O" or
                        button_1["text"]=="O" and button_5["text"]=="O" and button_9["text"]=="O" or
                        button_3["text"]=="O" and button_5["text"]=="O" and button_7["text"]=="O" or
                        button_1["text"]=="O" and button_4["text"]=="O" and button_7["text"]=="O" or
                        button_2["text"]=="O" and button_5["text"]=="O" and button_8["text"]=="O" or
                        button_3["text"]=="O" and button_6["text"]=="O" and button_9["text"]=="O"):
                        tkinter.messagebox.showinfo(title="Congrats", message="Player O won!")
                        self.root.update()
                        playerO = True
                        exit()
                    self.root.title("Player X")
                    turn = True

            def __init__(self):
                self.root = Tk()

                self.root.title("Tic-Tac-Toe")
                self.root.resizable(width=False, height=False)
                self.root.font = Font(family="Times 80", size=80)

                button_1 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_2 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_3 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_4 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_5 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_6 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_7 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_8 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)
                button_9 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click)


                button_1.grid(row=0)
                button_2.grid(row=0, column=1)
                button_3.grid(row=0, column=2)
                button_4.grid(row=1)
                button_5.grid(row=1, column=1)
                button_6.grid(row=1, column=2)
                button_7.grid(row=2)
                button_8.grid(row=2, column=1)
                button_9.grid(row=2, column=2)


        play = Game()
        play.root.mainloop()


except:
    tkinter.messagebox.showinfo(title="Error", message="Sorry there was an error!")

You have a lot of problems.

First, you don't need the while True loop like you would in CLI because tkinter (and all GUIs) has a mainloop that runs the GUI. You start that loop when you call mainloop() .

If you want to use your buttons (or any variables) in more than one method (__init__ and click in your case), then you have to name them with "self." on the front. These are called "instance variables" and it's what classes use instead of global variables.

You do not want to nest the class definition in something else. If you want to use a try block (I see no reason to) then put the class instance in the try block.

When using lambda, you have to call the function by adding () on the end. People usually use lambda to call a function with arguments, for instance lambda: self.click(1) .

Here's your code fixed:

from tkinter import *
from tkinter.font import Font
import tkinter.messagebox

class Game():
    def click(self, button_idx):
        button = self.buttons[button_idx]
        ########## Test whether button is blank and then inserts 'X'
        if(button["text"]=="" and self.turn == True):
            button["text"]= "X"
            if(self.button_1["text"]=="X" and self.button_2["text"]=="X" and self.button_3["text"]=="X" or
                self.button_4["text"]=="X" and self.button_5["text"]=="X" and self.button_6["text"]=="X" or
                self.button_7["text"]=="X" and self.button_8["text"]=="X" and self.button_9["text"]=="X" or
                self.button_1["text"]=="X" and self.button_5["text"]=="X" and self.button_9["text"]=="X" or
                self.button_3["text"]=="X" and self.button_5["text"]=="X" and self.button_7["text"]=="X" or
                self.button_1["text"]=="X" and self.button_4["text"]=="X" and self.button_7["text"]=="X" or
                self.button_2["text"]=="X" and self.button_5["text"]=="X" and self.button_8["text"]=="X" or
                self.button_3["text"]=="X" and self.button_6["text"]=="X" and self.button_9["text"]=="X"):
                tkinter.messagebox.showinfo(title="Congrats", message="Player X won!")
                self.root.update()
                self.playerX = True
                self.root.quit()
            self.root.title("Player O")
            self.turn=False
        ########### Test whether button is blank and then inserts 'O'
        elif(button["text"]=="" and self.turn == False):
            button["text"] = "O"
            if(self.button_1["text"]=="O" and self.button_2["text"]=="O" and self.button_3["text"]=="O" or
                self.button_4["text"]=="O" and self.button_5["text"]=="O" and self.button_6["text"]=="O" or
                self.button_7["text"]=="O" and self.button_8["text"]=="O" and self.button_9["text"]=="O" or
                self.button_1["text"]=="O" and self.button_5["text"]=="O" and self.button_9["text"]=="O" or
                self.button_3["text"]=="O" and self.button_5["text"]=="O" and self.button_7["text"]=="O" or
                self.button_1["text"]=="O" and self.button_4["text"]=="O" and self.button_7["text"]=="O" or
                self.button_2["text"]=="O" and self.button_5["text"]=="O" and self.button_8["text"]=="O" or
                self.button_3["text"]=="O" and self.button_6["text"]=="O" and self.button_9["text"]=="O"):
                tkinter.messagebox.showinfo(title="Congrats", message="Player O won!")
                self.root.update()
                self.playerO = True
                self.root.quit()
            self.root.title("Player X")
            self.turn = True

    def __init__(self):
        self.root = Tk()
        self.turn = True
        self.playerX = False  #If True, Player X won
        self.playerO = False  #If True, Player O won

        self.root.title("Tic-Tac-Toe")
        self.root.resizable(width=False, height=False)
        self.root.font = Font(family="Times 80", size=80)

        self.button_1 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(0)) # python is zero-indexed, so 0 is the first button
        self.button_2 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(1))
        self.button_3 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(2))
        self.button_4 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(3))
        self.button_5 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(4))
        self.button_6 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(5))
        self.button_7 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(6))
        self.button_8 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(7))
        self.button_9 = Button(self.root, text = "", height = 6, width = 12, command = lambda: self.click(8))

        self.button_1.grid(row=0)
        self.button_2.grid(row=0, column=1)
        self.button_3.grid(row=0, column=2)
        self.button_4.grid(row=1)
        self.button_5.grid(row=1, column=1)
        self.button_6.grid(row=1, column=2)
        self.button_7.grid(row=2)
        self.button_8.grid(row=2, column=1)
        self.button_9.grid(row=2, column=2)

        self.buttons = [self.button_1, self.button_2, self.button_3, self.button_4, self.button_5, self.button_6, self.button_7, self.button_8, self.button_9]

play = Game()
play.root.mainloop()

For some improvements, why don't you use a loop to define the buttons? If you do you will have to use functools.partial , not lambda , to set the command. You could also use the self.buttons list to do the checks. Also, you repeat a lot of code. Why don't you make a dynamic function that can check if either X or O has won?

The command = lambda: self.click you're using when construction the Button s effectively also creates a bunch of unnamed functions each equivalent to this:

def _():
    return self.click

As can more easily be seen when written out like this, nothing happens except for the click method of the class instance being returned. However what needs to happen is for the method to be called when the callback happens (what it returns will be ignored).

In other words using command=lambda: self.click() instead of what you have would fix the problem.

However , such a function doesn't accomplish much of anything except adding overhead to the callback process—which means it would be better to just directly specify the method with command=self.click (without parentheses at the end once again since all tkinter requires is for the value assigned to be something callable with no parameters). Doing so avoids the unnecessary expense of calling a function that does nothing but call another one.

Thanks to @Novel for pointing out this—undeniablely obvious—optimization out to me in a comment.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM