I am attempting to work with Tkinter, and the mouse events should change the text of the button clicked.
Through testing, this works with right click () and middle click (). However, (left click) uses a function, which should find the "coordinates" of the widget in the array (I need these numbers for later calculations). The bottom row of the grid works, but any other button leads to the bottom-right button being selected.
from tkinter import *
from random import *
win = Tk()
win.geometry("380x410")
win.grid()
buttons = []
def search(event):
for j in range(10):
for i in range(10):
#print(event.widget)
if buttons[j][i] == event.widget:
break
print(i,j)
buttons[j][i].config(text="1")
for y in range(10):
temp = []
for x in range(10):
button = Button(win,text="",width=4,height=2)
button.grid(row=y,column=x)
button.bind("<Button-1>",search)
button.bind("<Button-2>",lambda event: event.widget.config(text=""))
button.bind("<Button-3>",lambda event: event.widget.config(text="R"))
temp.append(button)
buttons.append(temp)
I have tried messing about with lambdas, but I believe the problem lies within the function.
Any help would be appreciated.
Question: Finding widgets in an array Tkinter
Simpler solution
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
for y in range(10):
for x in range(10):
button = tk.Button(self, text="", width=4, height=2)
button._coords = x, y
button.grid(row=y, column=x)
button.bind("<Button-1>", self.on_button_1_click)
def on_button_1_click(self, event):
print('on_button_1_click:{}'.format(event.widget._coords))
if __name__ == '__main__':
App().mainloop()
Maybe this is somewhat close to what you're looking for:
from tkinter import *
from random import *
win = Tk()
win.geometry("380x410")
win.grid()
buttons = []
def search(event):
i2 = 0
j2 = 0
for j in range(10):
for i in range(10):
#print(event.widget)
if buttons[j][i] == event.widget:
i2 = i
j2 = j
event.widget.config(text="1")
break
print(i2,j2)
#buttons[j][i].config(text="1")
for y in range(10):
temp = []
for x in range(10):
button = Button(win,text="",width=4,height=2)
button.grid(row=y,column=x)
button.bind("<Button-1>", lambda event: search(event))
button.bind("<Button-2>",lambda event: event.widget.config(text=""))
button.bind("<Button-3>",lambda event: event.widget.config(text="R"))
temp.append(button)
buttons.append(temp)
win.mainloop()
Are you sure you need coordinates or just the index of the Button in the buttons list?
If you just need to accurately interact with each button try using the index value in a lambda.
from tkinter import *
win = Tk()
win.geometry("380x410")
buttons = []
def search(btn, ndex):
if btn == '1':
buttons[ndex].config(text='1')
if btn == '2':
buttons[ndex].config(text='')
if btn == '3':
buttons[ndex].config(text='R')
for y in range(10):
for x in range(10):
buttons.append(Button(win, text="", width=4, height=2))
ndex = len(buttons) - 1
buttons[-1].bind("<Button-1>", lambda e, z=ndex: search('1', z))
buttons[-1].bind("<Button-2>", lambda e, z=ndex: search('2', z))
buttons[-1].bind("<Button-3>", lambda e, z=ndex: search('3', z))
buttons[-1].grid(row=y, column=x)
win.mainloop()
Related
Hi wanted to get in this program something like getting button id, here is code:
import tkinter as tk
root = tk.Tk()
def button_click(event):
button = event.widget
print(button['text'])
buttons = []
k = 0
for x in range(10):
for y in range(10):
buttons.append(tk.Button(root, width=4, height=2, text=k))
buttons[k].grid(row=x, column=y)
buttons[k].bind('<Button-1>', lambda e: button_click(e))
k += 1
root.mainloop()
I want to get extacly same result as that program but without putting any text on those buttons.
Try this
import tkinter as tk
root = tk.Tk()
def button_click(event, btnId):
print(btnId)
buttons = []
k = 0
for x in range(10):
for y in range(10):
buttons.append(tk.Button(root, width=4, height=2))
buttons[k].grid(row=x, column=y)
buttons[k].bind('<Button-1>', lambda e, btnId=k: button_click(e, btnId))
k += 1
root.mainloop()
Don't use text=k instead add a parameter in button_click and pass k as a parameter.
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.
from Tkinter import *
import csv
root = Tk()
def click(event):
global x,y
x, y= event.x,event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()
row=[]
col=[]
row.append(x)
col.append(y)
Please! How do I write a loop, so that the two list can contain all x, and y that I clicked.
There's no reason to use an explicit loop here, one is already provided by root.mainloop, which calls your handler for you on every click event. Writing:
from Tkinter import *
root = Tk()
row = []
col = []
def click(event):
row.append(event.x)
col.append(event.y)
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()
will leave row and col populated with all of the x and y coordinates from each click once root.mainloop completes. There's also no reason to make x and y global: their global values will just always hold the values from the last call to click (or give you an undefined variable error if you never clicked at all).
As it is, you are only appending x and y once. You can make the append happen on click event - no loop required!
from tkinter import *
import csv
root = Tk()
coords = []
def click(event):
global x,y
x, y= event.x,event.y
coords.append([x, y])
print("Clicked at: ", x, y)
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()
I'm trying to make the Minesweeper game. For each undifferentiated square I created a button.
my_list = [[0 for i in range(9)] for j in range(9)]
all_buttons = []
def creaMatriz():
for y, row in enumerate(my_list):
buttons_row = []
for x, element in enumerate(row):
boton2 = Button(root, text="", width=6, height=3, command=lambda a=x, b=y: onButtonPressed(a, b))
boton2.grid(row=y, column=x)
buttons_row.append(boton2)
all_buttons.append(buttons_row)
def onButtonPressed(x, y):
all_buttons[y][x]['text'] = str(qwer[x][y]) # Some action!!!
....
When i press the left mouse button on a undifferentiated square, I'm calling the function onButtonPressed(x, y), and a digit or a mine appears on the square.
How can I call another function when pressing the right mouse button on a undifferentiated square. I want see 'M' on the square.
full code: http://pastebin.com/cWGS4fBp
You need to bind keys you wish in order to get this functionality. Here's a simple concept:
from tkinter import *
root = Tk()
def left(event):
label.config(text="Left clicked")
def right(event):
label.config(text="Right clicked")
label = Label(root, text="Nothing")
label.pack()
label.focus_set()
label.bind("<1>", left)
label.bind("<3>", right)
Let us know if it is what you're looking for.
There's nothing special you need to do, you simply need to bind each mouse button separately rather than using the command attribute.
For example, let's create a callback for the left and right mouse buttons:
def onLeftClick(x, y):
print("you clicked on %x,%y" % (x, y))
def onRightClick(x, y):
print("you clicked on %x,%y" % (x, y))
Next, we can bind to each of those functions separately using the bind method. Since we're adding custom bindings, we do not want to set the command attribute of the button.
def creaMatriz():
for y, row in enumerate(my_list):
buttons_row = []
for x, element in enumerate(row):
button = Button(root, text="", width=6, height=3)
...
button.bind("<1>", lambda event, x=x, y=y: onLeftClick(x,y))
button.bind("<3>", lambda event, x=x, y=y: onRightClick(x,y))
I want to create a grid of buttons that will toggle colour when they are clicked. At the moment every button will trigger the bottom right button instead. Below is the code. Two questions, why is it doing this and how do I correct it?
def main(self):
root = Tk()
frame=Frame(root)
frame.grid(row=0,column=0)
self.btn= [[0 for x in xrange(20)] for x in xrange(60)]
for x in range(60):
for y in range(20):
self.btn[x][y] = Button(frame,command= lambda: self.color_change(x,y))
self.btn[x][y].grid(column=x, row=y)
root.mainloop()
def color_change(self,x,y):
self.btn[x][y].config(bg="red")
print x,y
I figured it out. Replace:
self.btn[x][y] = Button(frame,command= lambda: self.color_change(x,y))
With:
self.btn[x][y] = Button(frame,command= lambda x1=x, y1=y: self.color_change(x1,y1))
Sorry if this was a nuisance to any one.