How to get grid information from pressed Button in tkinter? [duplicate] - python

This question already has answers here:
How to pass arguments to a Button command in Tkinter?
(18 answers)
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I need to create table of buttons using Tkinter in Python 2.7, which has n rows and n columns, and has no button in bottom right corner.
Problem is that when I press a button, in its place, I need to create free space and move that button to space that was empty before, and I cannot do that because I don't know how to get grid (x and y axes) information of pressed button to use it to create free space.
This is my current code:
from Tkinter import *
#Input:
n=int(raw_input("Input whole positive number: "))
L = range(1,n+1)
k = n
m = n
#Program:
root = Tk()
for i in L:
for j in L:
frame = Frame(root)
frame.grid(row = i, column = j)
if j == k and i == m:
pass
else:
button = Button(frame)
button.grid(row = i, column = j)
root.mainloop()
It would be something like this, where I wanted to get button grid position, and use it to change k and m variables to make empty space in position where pressed button was.

You can pass the row and column by using a lambda expression for the button:
button = Button(..., command=lambda row=i, column=j: doSomething(row, column))

Could you do something like create the grid of buttons up front, but include a hidden button in the blank space? Then when the button is clicked hide the clicked one and display the hidden one. Then you don't have to worry about moving buttons around, unless you need the actual button object to move for some reason.
Edit to enhance answer from comments:
Below is a simple example of hiding a button and it shows how to track the buttons as well, unless I screwed up moving it to the editor. This can be translated to a list or dictionary of buttons or whatever need be. You'd also need to determine find a way to register the local buttons or add some context to know which one to show or hide.
from Tkinter import Button, Frame, Tk
class myButton(Button):
def __init__(self, *args, **kwargs):
Button.__init__(self, *args, command=self.hideShowButton,
** kwargs)
self.visible = True
def hideShowButton(self):
self.visible = False
self.pack_forget()
window = Tk()
frame = Frame(window)
frame.pack()
b1 = myButton(window, text="b1")
b1.pack()
b2 = myButton(window, text="b2")
b2.pack()
b3 = myButton(window, text="b3")
b3.pack()
window.wait_window(window)
print "At the end of the run b1 was %s, b2 was %s, b3 was %s" % (str(b1.visible), str(b2.visible), str(b3.visible))

This is the way I tried to do this and problem is that I still don't know how to get row and column of pressed button...
from Tkinter import *
#Input:
n = int(raw_input("Input whole positive number: "))
L = range(1,n+1)
k = n
m = n
#Try putting values for k and m which are less then n and you will see what i need to get
#Moving:
def Move():
#This i cant fill
return k,m
#Program:
root = Tk()
for i in L:
for j in L:
frame = Frame(root)
frame.grid(row = i,column = j)
if i == int(k) and j == int(m):
pass
else:
button = Button(frame, command = lambda: Move())
button.pack()
root.mainloop()
So by changing k and m values gap is created on other place...

Related

Tkinter Widget For Loop is typing in every Widget

I tried creating a widget for loop that would only allow one letter input, but I'm running into an issue where it creates text for every widget. I assume the issue lies with the "len" I used.
from tkinter import *
from pynput.keyboard import Key, Controller
root = Tk()
height = 6
width = 5
delta=0
entries = []
def limitSizeDay(*args):
value = dayValue.get()
if len(value) > 1: dayValue.set(value[:1])
dayValue = StringVar()
dayValue.trace('w', limitSizeDay)
for i in range(height): #Rows
newrow = []
for j in range(width): #Columns
b = Entry(root, text="",width=2,font=('Arial', 40, 'bold'), textvariable=dayValue)
b.grid(row=i, column=j)
newrow.append(b)
entries.append(newrow)
def getword(event):
global b
ass = b.get()
print(ass)
keyboard = Controller()
keyboard.press(Key.tab)
keyboard.release(Key.tab)
root.bind('<Return>', getword)
mainloop()
No. The problem is that you have bound every Entry box to the same textvariable (dayValue). When you change that one variable, all of the boxes respond. I can't tell what you are really trying to achieve with this. If you want each box to have its own variable, then you need to create a LIST of StringVars, probably inside the loop so you get the same size.
As Tim said the cause of your problem is binding the same StringVar to all of the Entry widgets. To make this work, you need to create a StringVar for each Entry in a loop:
for i in range(height): #Rows
newrow = []
for j in range(width): #Columns
dayValue = StringVar()
dayValue.trace('w', limitSizeDay)
b = Entry(root, text="",width=2,font=('Arial', 40, 'bold'), textvariable=dayValue)
b.var = dayValue # Prevent garbage collection (I think)
b.grid(row=i, column=j)
newrow.append(b)
entries.append(newrow)
Then you need to change limitSizeDay to access the variable.
def limitSizeDay(var, *args):
value = root.globalgetvar(var)
if len(value) > 1: root.globalsetvar(var, value[:1])
The globalgetvar and globalsetvar methods allow you to get and set the variable respectively as the usual getvar and setvar don't work in this case.

How do I make a function for cancel button which when button is clicked unchecks all buttons that I have checked?

I have made a menu which consists of checkbuttons which are created by adding values to a list, so to be precise if I want to add a new item on the menu all I need to do is to put something in the list and checkbutton will be created through while loop. I landed on a problem here, I want to make a cancel button which unchecks all the selected buttons but due to variable option in checkbutton being same for all the buttons because there is only one while loop for creating them all my buttons get selected when I press on one.
Note I do not want to use deselect() option, I am trying to make it this way. Typical error I get is:
Ponisti[p] = tkinter.IntVar()
IndexError: list assignment index out of range,
Or it does that thing where it selects all when I press one button.
So all in all, I am trying to make the variable in checkbutton change for each new button I add so I can select them independently.
My piece of code is given as an example, I hope you can get a grasp of it and a grasp of my idea.
Piece of code:
import tkinter
window = tkinter.Tk()
Array =[]
p=0
Ponisti =[]
while p != len(Meni):
Ponisti[p] = tkinter.IntVar()
p=p+1
def cancel():
f=0
for i in Array:
Ponisti[f].set('0')
f=f+1
while j != len(Meni):
items = tkinter.Checkbutton(window, text=Meni[j], onvalue=1, offvalue=0)
canceldugme = tkinter.Button(frame1,text="Cancel",command=cancel)
This does what you want.
import tkinter as tk
def reset():
for var in variables:
var.set(0)
menu = [str(i) for i in range(8)]
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
variables = [tk.IntVar(0) for _ in range(len(menu))]
length = len(menu) // 2
for i, var in enumerate(variables):
row, col = divmod(i, length)
checkbutton = tk.Checkbutton(frame, variable=var, text=menu[i])
checkbutton.grid(row=row, column=col, sticky="w")
reset_button = tk.Button(root, text="Reset", command=reset)
reset_button.pack(side="right")
root.mainloop()
Basically you want to create a list of variables and for each variable create a new button that is bound to it. When you call the reset function, all you have to do is iterate over your variables and reset their value.
I put the checkbuttons in a frame so you can use the grid method, since it seeems you want to lay them out in a grid fashion. The reason for using a frame is that you can't mix grid and pack for the same window. Both frame and reset_button are placed using pack(), and the checkuttons inside the frame can then be placed using grid().

How to use for loop to create buttons that can be then each configured separately in my command function?

I have created 9 buttons with a loop and want each of them to display "x" when clicked. However, the command function is not properly executing for each button.
I already tried using lambda... I think the issue might be in the way that I named each Button?
def create_buttons(self):
buttoncounter = 1
for i in range(9):
self.temp_string = "b" + str(buttoncounter)
self.temp_string = Button(self, text = "\n\t\n\t\n\t")
self.temp_string.grid(row = (20 + i), column = (20))
self.temp_string.configure(command=partial(self.mark_box,
buttoncounter))
buttoncounter += 1
def mark_box(self, num):
turnlist = ["x", "o"]
self.temp_string = "b" + str(num)
self.temp_string.configure(text = "x")
I want to be able to click a button and have it check itself off, but when I click any of the 9 buttons it only checks off the 9th one.
To access widgets created in a loop we use dictionaries and list to keep a reference to them. Later we can modify them from their references stored inside dictionaries or list.
Like so..
all_buttons = []
for i in range(9):
button = Button(root, .... )
all_buttons.append(button)
When we need to get a specific button we can get it by all_buttons[0] which will gives us the instance of the Button first created in the loop.
But, if you want to give your own tag or name to refer each Button then use dictionary where key will be the name and value will be the instance to the Button.
all_buttons = {}
for i in range(9):
button = Button(root, .... )
all_buttons.update({ 'Button_%s'%i : button })
To refer, we use all_buttons['Button_0'] gives us the first created Button.
Now, How to pass commands to each button in a loop.
I see you're using partial from functools to pass argument to the function mark_box, we can also use lambda to get the same results without importing functools. Refer to this post for better understanding.
Here is a combined example of how to pass arguments in a loop to the callback function of Button using lambda and also keep the reference to the Button?
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text='Click the Button')
label.pack()
def update(text):
label.config(text="Button %s is clicked"%text)
all_buttons = []
for i in range(9):
button = tk.Button(root, text='Button %s'%i, command=lambda i=i: update(i) )
button.pack()
all_buttons.append(button)
print('\nThis the list containing all the buttons:\n', all_buttons)
root.mainloop()

Passing an object in lambda in tkinter button callback

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()

Python Tkinter - Name of objects created by function

I am creating a set of buttons via this function:
from tkinter import *
from random import randint
window = Tk()
window.title("Test")
window.geometry('200x200')
color = ["red","blue","green","yellow","black","purple","orange"]
RandInt = 0
j = 0
h = 0
def ButtonDef(xvar = 0,yvar = 0):
btn = Button(command =lambda:[RandomColor()])
btn.grid()
btn.place(x = xvar*50, y = yvar*50, width = 50, height = 50)
def RandomColor():
RandInt = randint (0,6)
btn.configure(bg = color[RandInt])
while j in range (4):
i = 0
j += 1
while i in range (4):
ButtonDef(i,h)
i += 1
if i == 4:
h += 1
window.mainloop()
However, my RandomColor() function is changing only the color of the very button i have pressed - that is fun too, but i wonder how i can make it randomly change the color of all buttons. When being created by a function, i would have guessed that all buttons that were created are named "btn" since thats the only name i have given them.
How could i address all (or one specific) buttons, out of a group of created-by-function buttons?
Or to put it simply, what name do all of those buttons have? Do they share the name "btn"? Are they assigned a hidden ID?
The reason behind your problem:
The problem is that when this line is executed: btn = Button(command =lambda:[RandomColor()]) by the end of the loop, you get a reference only to the last button which is created. You loose reference to other buttons.
Solution:
You can overcome this problem if you rely on winfo_children().
You have to do 2 steps to fix your issue:
First of all, change: btn = Button(command =lambda:[RandomColor()]) to btn = Button(window, command=lambda:[RandomColor()]). That simply means we attach each created button to a parent widget called window.
Then all that you need to change is RandomColor() function body as follows:
def RandomColor():
for child in window.winfo_children():
child.configure(bg=color[randint(0,6)])
Demo:
This solves your problem but your code is not clean. You can ask for suggestions to improve your code on Code Review website.
EDIT:
Here is a solution for the scenario you described in the comment.
Note that I had to create code from scratch, cleaner one (I know you started only today, so I am not blaming you). In this code, I keep reference for every button I create:
import tkinter as tk
import random
class ChangeBottomRightColor(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.__colors = ["red","blue","green","yellow","black","purple","orange"]
self.configure_gui()
self.create_widgets()
def configure_gui(self):
pass
def create_widgets(self):
self.create_buttons()
def create_buttons(self):
self.buttons = {}
c = 0
for i in range(4):
for j in range(4):
self.buttons['button{}'.format(c)] = tk.Button(self.master)
self.buttons['button{}'.format(c)].grid(row=i, column=j)
self.buttons['button{}'.format(c)].config(width=3, height=3)
self.buttons['button{}'.format(c)].config(command=self.change_bottom_right_button_color)
c += 1
def get_random_color(self):
return random.choice(self.__colors)
def change_bottom_right_button_color(self):
self.buttons['button{}'.format(15)].config(bg=self.get_random_color())
if __name__ == '__main__':
root = tk.Tk()
main_app = ChangeBottomRightColor(root)
root.mainloop()
Demo:
Let try
btn = []
for i in range(16):
btn.append(Button(window))
it will create an array of button. So you can access by btn[i].configure(command=lambda:[RandomColor()]) or something else.

Categories