Hello avid python users...
I was trying to create my first GUI writing a tic-tac-toe program but I ran into a problem regarding the 9 buttons on the grid. Here is part of the code that generates the buttons:
button = 0
for x in range(3):
for y in range(3):
button = Button(root, text= " ", font=("Helvetica", 20), height=3, width=6, bg="SystemButtonFace", command=lambda button=button: b_click(button))
button.grid(row = x, column = y)
The click function looks like this:
def b_click(b):
global clicked
if b["text"] == " " and clicked == True:
b["text"] = "X"
clicked = False
elif b["text"] == " " and clicked == False:
b["text"] = "O"
clicked = True
else:
messagebox.showerror("Tic Tac Toe", "Hey! That box has already been selected \nPick another box...")
My problem is that whenever I click a button on the GUI it selects and use b_click(b) on the button to the left of whichever one I originally picked...
Help would be appreciated...
Look at this script:
import tkinter as tk
from functools import partial
def b_click(button):
button.config(text="X")
root = tk.Tk()
for x in range(3):
for y in range(3):
button = tk.Button(root, text=" ")
command = partial(b_click, button)
button.config(command=command)
button.grid(row=x, column=y)
root.mainloop()
It uses functools.partial and <tkinter.Button>.config(...) to pass in the button to the function. From there you can do anything you like with the button.
Edit:
functools.partial is like a labmda but you don't need the button=button part. It takes at least 1 argument (the function name) and the rest of the arguments/key word arguments are passed to the function when it is called.
So
x = partial(function, arg1, arg2, kwarg1="")
x(arg3)
will be the same as function(arg1, arg2, arg3, kwarg1="text").
Related
I'm new to Tkinter and as my first project I wanted to create a Tic Tac Toe.
I want to create 9 buttons, that will change their background image when I click on them, the problem is that I dont want to create a function for every single button but one function that will take the button in argument and will change its background image.
The code I wrote:
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
b1 = tk.Button(app, text="", image=white, command=lambda id=b1: play(id))
b1.grid(column=0, row=0)
How can I pass b1 as an argument to play() function?
Thanks
I tried to use b1 as an argument to play(), and use play() to change b1's image.
When I try to run this code I get "name b1 is not defined".
Define a single function to create, configure, and bind your button; then your callback can close over the necessary variable referring to your button. Something like
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
def add_button(app, r, c):
b = tk.Button(app, text="", image=white)
b.config(command=lambda: play(b))
b.grid(column=c, row=r)
return b
for row in [0,1,2]:
for col in [0,1,2]:
# Save the return value somewhere if necessary
addButton(app, row, col)
The lambda expression contains a free variable b, which refers to the variable b in the closest enclosing scope, which is the call to add_button where the lambda expression is evaluated.
One way you can do it is to separate the creation of the button from the assignment using the .config method.
b1 = tk.Button(app, text="", image=white)
b1.config(command=lambda btn_id=b1: play(btn_id))
This is not really the best way to go about it, it's better to instead pass something that defines the button like an Enum or text.
b1 = tk.Button(app, text="", image=white, command=lambda : play("b1"))
then check that "b1" is the input in your function, this way b1 can be redefined as what would happen in loops.
Here is what I wrote at my leisure, it may be useful. Just create a folder 'icons' and put 100x100 px images there
from tkinter import *
import random
class Main:
def __init__(self):
self.root = Tk()
#self.root.geometry('900x100')
def run(self):
self.variables()
self.interface()
self.root.mainloop()
def variables(self):
self.PHOTO_COUNTER = 0
self.photo_list = [
PhotoImage(file="icons/bublegum.png"),
PhotoImage(file="icons/fin.png"),
PhotoImage(file="icons/jake.png"),
PhotoImage(file="icons/marcelin.png"),
PhotoImage(file="icons/navel.png"),
PhotoImage(file="icons/winter_king.png"),
]
def interface(self):
self.Buttons = []
for i in range(10):
item = random.choice(self.photo_list)
self.btn = Button(self.root, image=item, command=lambda c=i: self.click(c))
self.btn.pack(fill=BOTH, expand=1, side=LEFT)
self.Buttons.append(self.btn)
def click(self, i):
btn = self.Buttons[i]
item = random.choice(self.photo_list)
btn.config(image=item)
A = Main()
A.run()
I'm currently in a project of making "Shut The Door, Dice Game". I've used the for loop to label the "door", now my problem is how to take the "door" to update to "-" after click the correct "door".
import tkinter, random
window = tkinter.Tk(); window.title("Dice Game - Shut the DOOR")
def roll():
result["text"] = int(random.randint(1,6))
result_2["text"] = int(random.randint(1,6))
for door in range(12):
number = tkinter.Label(window, text=door+1)
number.grid(row=0, column=door, sticky="e")
result = tkinter.Label(text="-")
result_2= tkinter.Label(text="-")
result.grid(row=1, column=0)
result_2.grid(row=1,column=1, sticky="w")
dice = tkinter.Button(window, text="Roll", command=roll)
dice.grid(row=2, column=0)
Appreciate for the help
Use .bind("<Button>", function) to do something when a label (or any other widget) is clicked. First, define a function to handle the event, then add number.bind("<Button>", function_name) to make the function run when number is clicked.
Tkinter will automatically pass an argument to the function containing information about the event so the function must accept one argument. This is used to get which number was clicked.
Full code:
import tkinter, random
window = tkinter.Tk(); window.title("Dice Game - Shut the DOOR")
def change_text(event):
number = event.widget
if result["text"] == "-" or number["text"] == "-": return
if int(result["text"]) + int(result_2["text"]) == int(number["text"]):
number["text"] = "-"
def roll():
result["text"] = int(random.randint(1,6))
result_2["text"] = int(random.randint(1,6))
for door in range(12):
number = tkinter.Label(window, text=door+1, width=1)
number.grid(row=0, column=door, sticky="e")
number.bind("<Button>", change_text)
result = tkinter.Label(text="-")
result_2= tkinter.Label(text="-")
result.grid(row=1, column=0)
result_2.grid(row=1,column=1, sticky="w")
dice = tkinter.Button(window, text="Roll", command=roll)
dice.grid(row=2, column=0)
I'm making a basic TicTacToe UI in python, and I believe that a fundamental item to this code is a reset button which resets your codes back to the default. is there any other way to do this?
I've Tried to define a function which resets the text of the button back to " " but I don't think that's a great idea because of a lot of other complexities within the cod.
from tkinter import *
root = Tk()
def changetext():
BTN1["text"] = "X"
BTN1 = Button(root, text=" ", command=changetext)
BTN1.pack()
root.mainloop()
So I want to add a button here that says "Reset Text" and it resets all the codes to defaults.
The easiest way to reset the game would be to
Reset the UI as you suggest, with a single dedicated reset_UI() function
Reset the board state by creating a new game board object, and discarding the old one
This of course means that you'll need to wrap all your variables and functions in a board class Board, so that there aren't a billion global variables you have to worry about resetting. The only thing that should persist between resets are your UI buttons, which can be created in your main() function before initializing the game board.
Here's code demonstrating how something like that could be done (plus a few other things):
import tkinter as tk
def toggle(btn):
if btn["text"] == "X":
btn["text"] = " "
else:
btn["text"] = "X"
def reset(buttons):
for btn in buttons.values():
btn["text"] = " "
root = tk.Tk()
buttons = {}
for row in range(3):
for col in range(3):
button = tk.Button(root, text=" ", width=1, height=1)
button.config(command=lambda btn=button: toggle(btn))
button.grid(row=row, column=col)
buttons[row, col] = button
reset_button = tk.Button(root, text="Reset", command=lambda: reset(buttons))
reset_button.grid(columnspan=3)
root.mainloop()
I am trying to use lambda to create callbacks for tkinter buttons.
There are multiple buttons and each callback needs to pass an object inside it. Following code is what I am doing and is running fine
var0 = tk.StringVar()
label = tk.Label(top, bg = "White",height = 2, width = 12,textvariable=var0, justify="right")
def b0Callback(var):
var.set(var.get()+"0")
return
# creating a label which will print value of the any of the 0-9 button pressed
# creating a button 0
b0 = tk.Button(numFrame0, height = 1, width = 4, bg = "grey", text =
"0",command = lambda: b0Callback(var0))
#there are more buttons like that
var0 is used to update a label. Above code is working fine but I have to create callback for 0 to 9 and I have to just repeat above definition. So I tried using following example from this tutorial
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
Using it I did following
def Callback(n):
return lambda var.set(var.get()+n)
b0Callback = Callback("0")
This shows error invalid index in the return line at var.set
Is there any way to pass var0 in this case to avoid this error?
Maybe its only me, but I don't see a reason for using lambda if you just want to add a number to the label text.
Lets make a function for it that gets your StringVar() as a variable and adds some number to it:
def button_callback(str_var, number):
str_var.set(str_var.get() + str(number))
To run this command we simply put it in the code as a lambda function, otherwise it will run upon initialization (because we are providing a function instead of a reference).
So to run it within a button we declare it like this:
my_button = Button(root, text='Some text here', command=lambda: button_callback(my_string_var, 5))
The '5' could be potentially changed to any other number.
I have now solved the problem, here is the final code:
I have also changed the number of buttons to 300 and added code to arrange them all in a nice grid, just for fun. (You can change this to however many you want by changing for number in range(1, whatever).
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var0 = tk.StringVar()
self.var0.set('0')
# creating buttons and adding them to dictionary
self.buttons = {}
for number in range(1, 301):
self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})
label = tk.Label(self, textvariable=self.var0, font='none 50')
label.grid(column=0, row=0)
for button in self.buttons:
self.buttons[button].bind('<Button-1>', lambda event, num=button[6:]: self.Callback(event, num))
self.buttons[button].grid(column=(int(button[6:]) % 10), row=(int(button[6:]) / 10) + 1)
def Callback(self, event, num):
self.var0.set(num)
self.update()
I have code for radio buttons in tkinter. I am struggling to write the code that invokes the button command. Basically I want the user to be able to choose a time frame and a person. I have three different files that run three different data analyses, so I want the three files to run but only take data from the time frame and for that person.
from Tkinter import *
class RBDemo:
def __init__(self, win):
self.v = IntVar()
#Put the first group of radio buttons in their own frame.
f1 = Frame(win, borderwidth=3, relief=RAISED)
rb1 = Radiobutton(f1, text="This Week", variable=self.v, value=1)
rb2 = Radiobutton(f1, text="This Month", variable=self.v, value=2)
rb3 = Radiobutton(f1, text="This Year", variable=self.v, value=3)
rb1.pack(anchor=W); rb2.pack(anchor=W); rb3.pack(anchor=W)
f1.pack(side=LEFT)
#Button one will be selected by default
self.v.set(1)
#Make a second group of radiobuttons in their own frame.
#Make first button the default
self.v2 = StringVar()
f2 = Frame(win, borderwidth=2, relief=SOLID)
rb4 = Radiobutton(f2, text="Bob", variable=self.v2, value="Bob")
rb5 = Radiobutton(f2, text="Stacy", variable=self.v2, value="Stacy")
rb6 = Radiobutton(f2, text="Both", variable=self.v2, value="Both")
rb4.pack(anchor=W); rb5.pack(anchor=W); rb6.pack(anchor=W)
f2.pack(side=RIGHT)
self.v2.set("Bob")
#Make a button that prints what each value is when clicked
b = Button(win, text="Let's do this!", command=self.clicked)
b.pack(side=BOTTOM, fill=BOTH, expand=1)
def clicked(self):
print("button clicked!")
print("v is:", self.v.get())
print("v2 is:", self.v2.get() )
mw = Tk()
app = RBDemo(mw)
mw.mainloop()
I tried
def selected(self):
if self.my_var.get()==1:
"do something"
elif self.my_var.get()==2:
"do something"
else:
"do something"
but this doesn't seem to work, nor is it very pythonic considering I have to run three files using the input from the button.
First, When properly indented, an if - elif block is perfectly ok. So you could just use
if whatevervar.get() == 1:
dosomethingfancy()
elif whatevervar.get() == 2:
dosomethingcool()
#and so on
In other languages there is something like a switch - case block: wikipedia There is no such construct in Python, but there is a neat little trick that helps especially when dealing with bigger code blocks:
Options = {
1: dosomething,
2: dosomethingelse
}
#execution
Options[myvar.get()]()
Basically, a dictionary is defined, that maps its key values to functions. Mind the parentheses: You don't want to call the function, when the dictionary is defined.