tkinter not binding all the specified labels? - python

So I was trying to create a Tkinter application in which when you click and drag the mouse, it will print the initial and final position of the mouse pointer.
There is a nested list containing labels whom I want to bind. I first bind the left click on each label, then when the label is left-clicked I bind its release. And when the left click is released, it prints the initial and the final position of the mouse pointer relative to the label.
So here is the code
from tkinter import *
class game(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.llist = [[0]*4]*4
for i in range(4):
for j in range(4):
self.llist[i][j] = Label(self,bd = 1, relief = 'ridge',height = 4, width = 8)
self.llist[i][j].grid(row = i, column = j)
self.llist[i][j].bind('<Button-1>',lambda event, r = i, c = j: self.click(event,r,c))
def click(self,event,r,c):
a,b = event.x,event.y
self.llist[r][c].bind('<ButtonRelease-1>',lambda event: self.release(event,a,b))
def release(self,event,a,b):
print(a,b,event.x,event.y)
r = Tk()
r.geometry('300x300')
f = game(r)
f.pack()
r.mainloop()
But the problem is that only the last 4 labels work!
The problem is with the second binding. When I test only the first binding, all the labels work!
Please Help!!!

It is caused by the line:
self.llist = [[0]*4]*4
It created list of 4 lists that reference to the same list, i.e. self.llist[r][c] reference to the same self.llist[0][c].
Change it to self.llist = [[0]*4 for _ in range(4)].

Related

Tkinter function range issue

I am creating a TicTacToe game in tkinter, consisting of a 3x3 grid made out of buttons.
In the code below, once a player has drawn on a tile (by clicking on the button), the program should remove this tile from the list 'self.flattenedButtons'. This is to prevent the computer (player 2) from drawing on the same tile.
The method this check is made in is self.add_move(). This works on all buttons apart from the bottom right, I assume this is as I took away 1 from the ending range. If I do not do this I am given an 'out of range' error.
How would I change my method so it works on all buttons?
CODE:
from tkinter import *
from functools import partial
from itertools import *
import random
class Window(Frame):
def __init__(self, master = None): # init Window class
Frame.__init__(self, master) # init Frame class
self.master = master # allows us to refer to root as master
self.rows = 3
self.columns = 3
self.guiGrid = [[None for x in range(self.rows)] for y in range(self.columns)] # use this for the computer's moves
self.buttonText = StringVar(value = '')
self.buttonText2 = StringVar(value = 'X')
self.buttonText3 = StringVar(value = 'O')
self.button_ij = None
self.flattenedButtons = []
self.create_window()
self.add_buttons()
def create_window(self):
self.master.title('Tic Tac Toe')
self.pack(fill = BOTH, expand = 1)
for i in range(0,3):
self.grid_columnconfigure(i, weight = 1)
self.grid_rowconfigure(i, weight = 1)
def add_buttons(self):
rows = 3
columns = 3
for i in range (rows):
for j in range(columns):
self.button_ij = Button(self, textvariable = self.buttonText, command = lambda i=i, j=j: self.add_move(i,j))
self.guiGrid[i][j] = self.button_ij # place button into 2d array to access later on
self.flattenedButtons.append(self.button_ij)
self.button_ij.grid(row = i,column = j, sticky =E+W+S+N)
def add_move(self, i,j):
pressedButton = self.guiGrid[i][j]
self.guiGrid[i][j].config(textvariable =self.buttonText2)
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
root = Tk() # creating Tk instance
rootWidth = '500'
rootHeight = '500'
root.geometry(rootWidth+'x'+rootHeight)
ticTacToe = Window(root) # creating Window object with root as master
root.mainloop() # keeps program running
It is not recommended to operate the list when you iterate it.
If your code is:
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
print(self.flattenedButtons)
You will see that your button 9 will never be removed.
Change your for loop to a easy list-comprehension:
self.flattenedButtons = [i for i in self.flattenedButtons if i != pressedButton]
print(self.flattenedButtons)
You will see the change.

How to make each button display a different image on click using tkinter (python)

I am creating a GUI with a ".grid" of buttons. And I want to make each of those buttons display a different image on press. So when I click button 1, it will bring up "image1", on the bottom of the buttons. When I click button 2, it will bring up "image2" and etc.
Through some research, I was able to make the buttons run a function that takes in a parameter through the method below. However, I can't seem to make the button display the image. rather, it just makes an empty white space underneath the buttons, when I press any buttons.
Disclaimer:
- I do not want there to be loads of images, there will only be 1 image, and it will change depending on what button i press.
Here's the code:
from tkinter import *
def funct(numimg):
image = PhotoImage(file="image"+str(numimg)+".png")
label = Label(image=image)
label.grid(row = row_no+1, column = 0, columnspan = num_of_cols)
def make_funct(number):
return (lambda: funct(number))
root= Tk()
row_no = -1
buttons = []
num_of_cols = 3
root.resizable(0, 0)
numfiles = 6
for x in range(0, numfiles):
if(x % num_of_cols is 0):
row_no+=1
buttons.append(Button(root, text = "Button "+str(x), bg = '#4098D3', width = 30,height = 13,command = make_funct(x)))
buttons[x].grid(row = row_no, column = x % num_of_cols)
root.mainloop()
So my question is, how do you make each individual button, display a different image when it is pressed? this program right here just leaves an empty white space in replace of the image, and the image is not shown.
There are two major problems with the code you posted.
The first is basically the same as in this question: Why does Tkinter image not show up if created in a function?. You should keep a reference to the PhotoImage object, else it will be garbage collected and it will not show.
The second is that you create a new Label on every button click. You should only make one Label and change the image with the label.config() method.
I would (without wrapping your GUI in a class, which might be a nicer solution) load all images on initialization, save them in a list as attribute of the label and only change the image upon a button click.
I also removed your make_funct function and replaced it with a lambda, which is the most used way to pass variables to a function on callback.
from tkinter import *
def funct(numimg):
label.config(image=label.images[numimg])
root= Tk()
row_no = -1
buttons = []
num_of_cols = 3
root.resizable(0, 0)
numfiles = 3
for x in range(0, numfiles):
if(x % num_of_cols is 0):
row_no+=1
buttons.append(Button(root, text = "Button "+str(x), bg = '#4098D3', width = 30,height = 13, command = lambda n=x: funct(n)))
buttons[x].grid(row = row_no, column = x % num_of_cols)
label = Label(root)
label.grid(row = row_no+1, column = 0, columnspan = num_of_cols)
label.images=[]
for x in range(0, numfiles):
label.images.append(PhotoImage(file="image"+str(x)+".png"))
root.mainloop()

Tkinter: Integers as labels overwrite

I am creating labels in a for loop that display integers every time I fire an event (a mouse click) on my application. The problem is that old labels don't get erased and the new ones come on top of them causing a big mess.
Here is the working code that you can try out:
import numpy as np
import Tkinter as tk
class Plot(object):
def __init__(self, win):
self.win = win
self.bu1 = tk.Button(win,text='Load',command=self.populate,fg='red').grid(row=0,column=0)
self.listbox = tk.Listbox(win, height=5, width=5)
self.listbox.grid(row=1,column=0)#, rowspan=10, columnspan=2)
self.listbox.bind("<Button-1>", self.print_area)
def populate(self):
"""Populate listbox and labels"""
self.time = [1,2,3]
self.samples = ['a','b','c']
for item in self.time:
self.listbox.insert(tk.END,item)
for i,v in enumerate(self.samples):
tk.Label(self.win, text=v).grid(row=2+i,column=0,sticky=tk.W)
self.lbl_areas = []
for i in range(0, len(self.samples)):
self.lbl=tk.IntVar()
self.lbl.set(0)
self.lbl_areas.append(tk.Label(self.win,textvariable=self.lbl).grid(row=2+i,column=1,sticky=tk.W))
def print_area(self, event):
"""Prints the values"""
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
#Here is the dictionary that maps time with values
self.d = {1:[('a',33464.1),('b',43.5),('c',64.3)],
2:[('a',5.1),('b',3457575.5),('c',25.3)],
3:[('a',12.1),('b',13.5),('c',15373.3)]}
lbl_val = []
for i in range(0, len(self.samples)):
lbl_val.append(self.d[value][i][1])
for i in range(0, len(self.samples)):
self.lbl=tk.IntVar()
self.lbl.set(lbl_val[i])
tk.Label(self.win,textvariable=self.lbl).grid(row=2+i,column=1,sticky=tk.W)
def main():
root = tk.Tk()
app = Plot(root)
tk.mainloop()
if __name__ == '__main__':
main()
If You try to run this code and click on LOAD you will see the numbers appearing in the listbox and labels a,b,c with values set to zero at the beginning. If you click on the number in the listbox the values (mapped into the dictionary d) will appear but you will see the overwrite problem. How can I fix that?
How can I overcome this problem? Thank you
Don't create new labels. Create the labels once and then update them on mouse clicks using the configure method of the labels.
OR, before creating new labels delete the old labels.If you design your app so that all of these temporary labels are in a single frame you can delete and recreate the frame, and all of the labels in the frame will automatically get deleted. In either case (destroying the frame or destroying the individual labels) you would call the destroy method on the widget you want to destroy.

Python Tkinter Grid Checkbox

I was wondering if there is an easy way to create a grid of checkboxes using Tkinter. I am trying to make a grid of 10 rows and columns (so 100 checkboxes) so that only two checkboxes can be selected per row.
Edit: I'm using python 2.7 with spyder
What I have so far:
from Tkinter import*
master = Tk()
master.title("Select Groups")
rows=10
columns=10
for x in range(rows):
for y in range(columns):
Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
Checkbutton(master).grid(row=x+1, column=y+1)
mainloop()
I'm trying to use state='Disabled' to grey out a row once two checkboxes have been selected.
Here's a version that puts everything into a class so we don't need to use global variables. It also avoids the import * construction which is generally considered bad style in Python. True, lots of example code uses import * but it's not a good practice because it clutters up the global namespace with all the names from the imported module. So those names can clash with the names of your own variables, and they can also clash with the names of other modules you import using import *.
The program prints lists of the selected Groups for each Test row when the window closes.
#!/usr/bin/env python
''' Create a grid of Tkinter Checkbuttons
Each row permits a maximum of two selected buttons
From http://stackoverflow.com/q/31410640/4014959
Written by PM 2Ring 2015.07.15
'''
import Tkinter as tk
class CheckGrid(object):
''' A grid of Checkbuttons '''
def __init__(self, rows=10, columns=10):
master = tk.Tk()
master.title("Select Groups")
rowrange = range(rows)
colrange = range(columns)
#Create the grid labels
for x in colrange:
w = tk.Label(master, text="Group %s" % (x + 1))
w.grid(row=0, column=x+1)
for y in rowrange:
w = tk.Label(master, text="Test %s" % (y + 1))
w.grid(row=y+1, column=0)
#Create the Checkbuttons & save them for future reference
self.grid = []
for y in rowrange:
row = []
for x in colrange:
b = tk.Checkbutton(master)
#Store the button's position and value as attributes
b.pos = (y, x)
b.var = tk.IntVar()
#Create a callback bound to this button
func = lambda w=b: self.check_cb(w)
b.config(variable=b.var, command=func)
b.grid(row=y+1, column=x+1)
row.append(b)
self.grid.append(row)
#Track the number of on buttons in each row
self.rowstate = rows * [0]
master.mainloop()
def check_cb(self, button):
''' Checkbutton callback '''
state = button.var.get()
y, x = button.pos
#Get the row containing this button
row = self.grid[y]
if state == 1:
self.rowstate[y] += 1
if self.rowstate[y] == 2:
#Disable all currently off buttons in this row
for b in row:
if b.var.get() == 0:
b.config(state=tk.DISABLED)
else:
self.rowstate[y] -= 1
if self.rowstate[y] == 1:
#Enable all currently off buttons in this row
for b in row:
if b.var.get() == 0:
b.config(state=tk.NORMAL)
#print y, x, state, self.rowstate[y]
def get_checked(self):
''' Make a list of the selected Groups in each row'''
data = []
for row in self.grid:
data.append([x + 1 for x, b in enumerate(row) if b.var.get()])
return data
def main():
g = CheckGrid(rows=10, columns=10)
#Print selected Groups in each Test row when the window closes
data = g.get_checked()
for y, row in enumerate(data):
print "Test %2d: %s" % (y + 1, row)
if __name__ == '__main__':
main()
Here's an example using your provided 10x10 grid. It should give you the basic idea of how to implement this.
Just make sure you keep a reference to every Checkbutton (boxes in the example) as well as every IntVar (boxVars in the example).
Here's why:
-Checkbuttons are needed to call config(state = DISABLED/NORMAL).
-IntVars are needed to determine the value of each Checkbutton.
Aside from those crucial elements its basically just some 2D array processing.
Here's my example code (now based off of your provided code).
from Tkinter import *
master = Tk()
master.title("Select Groups")
rows=10
columns=10
boxes = []
boxVars = []
# Create all IntVars, set to 0
for i in range(rows):
boxVars.append([])
for j in range(columns):
boxVars[i].append(IntVar())
boxVars[i][j].set(0)
def checkRow(i):
global boxVars, boxes
row = boxVars[i]
deselected = []
# Loop through row that was changed, check which items were not selected
# (so that we know which indeces to disable in the event that 2 have been selected)
for j in range(len(row)):
if row[j].get() == 0:
deselected.append(j)
# Check if enough buttons have been selected. If so, disable the deselected indeces,
# Otherwise set all of them to active (in case we have previously disabled them).
if len(deselected) == (len(row) - 2):
for j in deselected:
boxes[i][j].config(state = DISABLED)
else:
for item in boxes[i]:
item.config(state = NORMAL)
def getSelected():
selected = {}
for i in range(len(boxVars)):
temp = []
for j in range(len(boxVars[i])):
if boxVars[i][j].get() == 1:
temp.append(j + 1)
if len(temp) > 1:
selected[i + 1] = temp
print selected
for x in range(rows):
boxes.append([])
for y in range(columns):
Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
boxes[x].append(Checkbutton(master, variable = boxVars[x][y], command = lambda x = x: checkRow(x)))
boxes[x][y].grid(row=x+1, column=y+1)
b = Button(master, text = "Get", command = getSelected, width = 10)
b.grid(row = 12, column = 11)
mainloop()

In Tkinter how can I correct this loop/function so that each button changes a value to the value of the button?

In Tkinter how can I correct this loop/function so that each button changes a value to the value of the button?
This is a simplified version of my code, at the moment each button changes the value of size to 15 rather than the number on the button. I was wondering if there was anyway of fixing this loop without printing each individual button and value without a loop?
from Tkinter import *
size = 7
def AI():
AIBoard = Tk()
AIBoard.title("Board Select")
BoardSize = Label(AIBoard, text = "Please pick a board size: ", font = ('Helvetica',20))
BoardSize.pack(side = 'top')
for a in range(5,16,1):
sizeBut = Button(AIBoard, text = a, width = 5, command = lambda: inputBoardSize(a))
sizeBut.pack(side = 'left')
AIBoard.mainloop()
def inputBoardSize(x):
size = x
print size
AI()
Thanx
Change your lambda to bind the value at the time the anonymous function is created.
lambda a=a: inputBoardSize(a)

Categories

Resources