How to 'fix' selected item in tkinter.Listbox? - python

Here's is an extract from my code:
import tkinter as tk
def evaluate(event):
print(list_box.curselection())
root = tk.Tk()
var = tk.StringVar()
var.set(0)
entry = tk.Entry(root, textvariable = var)
entry.place(x = 150, y = 0, width = 20)
entry.bind("<Return>", evaluate)
list_box = tk.Listbox(root, selectmode = 'single')
list_box.place(x = 0, y = 0)
lst = [1, 2, 3]
for elem in lst:
list_box.insert('end', elem)
list_box.selection_set(first = 0)
list_box.bind("<<ListboxSelect>>", evaluate)
root.mainloop()
The problem is that I want to 'fix' somehow the last selected value in the tkinter.Listbox. I mean, if in the window we type something to the entry, then in some cases (probably, it depends on how you click the entry box) the value chosen in the list will be lost. Is it possible to save, for example, the last selected value?
I'm new to Python and, in particular, to tkinter package, so any help would be appreciated.

the problem is as soon as you select something else like the input of the entry, your listbox lose the shown selection. I removed the default binding for double click, but if you still want to use it to write input, I can't remove the single click (B1-Motion dosent works either). At least I dont know how. I recommand to use the entry just as display or vise versa.
import tkinter as tk
def evaluate(event):
print(list_box.curselection())
root = tk.Tk()
var = tk.StringVar()
var.set(0)
entry = tk.Entry(root, textvariable = var)
entry.place(x = 150, y = 0, width = 20)
entry.bind("<Return>", evaluate)
entry.bind('<Double-Button-1>', lambda e: "break")
list_box = tk.Listbox(root, selectmode = 'single')
list_box.place(x = 0, y = 0)
lst = [1, 2, 3]
for elem in lst:
list_box.insert('end', elem)
list_box.selection_set(first = 0)
list_box.bind("<<ListboxSelect>>", evaluate)
root.mainloop()

Related

Tkinter : How to pass a button's text as an argument to a function when button is clicked

I have the following code that generates a random button grid of 5x5 dimensions :
import tkinter as tk
from tkinter import *
from tkinter import messagebox
import random
def numberClick(num):
messagebox.showinfo('Message', 'You clicked the '+str(num)+' button!')
root = Tk()
#root.geometry("200x200")
w = Label(root, text='Welcome to Bingo!')
linear_array = [i for i in range(1,26)]
random_array = []
for i in range(1,26):
temp = random.choice(linear_array)
linear_array.remove(temp)
random_array.append(temp)
for i in range(25):
num = random.choice(random_array)
#tk.Label(root,text=num).grid(row=i//5, column=i%5)
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda: numberClick(num))
redbutton.grid(row=i//5, column=i%5)
root.mainloop()
I have implemented the command function and passed a parameter with lambda as shown :
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda: numberClick(num))
Now when I click a button , the function call should print the text value assigned with it. Instead it just prints the same value , that is the last assigned value in num variable :
Output when i clicked on button 20
Any workarounds?? TIA.
Just change your button to:
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda num=num: numberClick(num))
This should fix the issue, this will store the value of num in lambda rather than just looping and using the last value of num.
I was just about to point out the same thing a Cool Cloud, but I'd also like to add that you are randomizing twice such that you might get duplicate numbers.
The first for loop randomizes the numbers 1-25 in random_array, but then in the second loop you randomly select one element from that list without removing it when you initialize num. I'd write the second loop as:
for i in range(25):
num = random_array[i]
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5, command=lambda n=num: numberClick(n))
redbutton.grid(row=i//5, column=i%5)

Adding elements using Tkinter Dynamically

I am trying to create the program that has an add button, When it is clicked, several different elements like entries and buttons should appear on the output window. Firstly I am not able to structure the display correctly and secondly I am not sure as to how to get the values entered by the user in entry widget of Tkinter.
Here is the Code:
from tkinter import *
from tkinter import messagebox
lb = Tk()
def addentry():
i = 3 // the 3 here should keep on incrementing so that the row goes on increasing as the user
keeps on adding different entries. This is for the display
ent1 = Entry(lb, bd=5).grid(row =i ,column= 0)
ent2 = Entry(lb, bd=5).grid(row = i, column=2)
ent3 = Entry(lb, bd=5).grid(row = i, column=4)
ent4 = Entry(lb, bd=5).grid(row = i , column=6)
addent = Button(lb, text = "Add Entry",command = addentry).grid(row = 0, column = 2)
It's all about keepin references. References are used to identify objects.
import tkinter as tk
root = tk.Tk()
my_entries = []
entry_row = 1
def addentry():
global entry_row
ent = tk.Entry(root, bd=5)
ent.grid(row =entry_row ,column= 0)
my_entries.append(ent)
entry_row = entry_row+1
def getter():
for entry in my_entries:
my_stuff = entry.get()
print(my_stuff)
addent = tk.Button(root, text = "Add Entry",command = addentry)
addent.grid(row = 0, column = 0)
getent = tk.Button(root,text='get input', command= getter)
getent.grid(row=0, column=1)
root.mainloop()
In this exampel we keepin the references of the tk.Entry and the variable entry_row while we would like to work with later on. There are a bunch of solution for this. Here we had used a global variable and a list in the global namespace to access them.

Change tkinter entry widget multiple times after pressing a button

I have a tkinter Entry widget and when a user presses a button the contents update:
from tkinter import *
window = Tk()
def abcdef(num):
ent.config(state=NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000, abcdef(j))
ent = Entry(widow, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
When I press the button the window freezes for 3 seconds and then just displays the final number. How can I make it so when the user presses the button, the entry changes every second instead of just freezing for 3 seconds and only displaying the final one?
Thanks in advance
You have two problems with that .after call. The .after method tells Tkinter to call the function you pass it after the time interval has passed. But you're telling Tkinter to do 3 things after 1000 milliseconds have passed, so they'll all happen on top of each other. So you need to stagger the delays.
Secondly, you need to give .after a function to call when its time to call it. But your code calls the function and gives .after the return value of your function. We can fix that by wrapping the function call inside another function. A convenient way to do that is using lambda, giving the lambda a default argument it can pass to abcdef
import tkinter as tk
window = tk.Tk()
def abcdef(num):
ent.config(state=tk.NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000 * j, lambda num=j: abcdef(num))
ent = tk.Entry(window, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = tk.Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
I've also replaced that "star" import with the neater import tkinter as tk. That makes it obvious which names come from Tkinter and which names are local to your program.
Bryan Oakley points out that we don't need that lambda, we can pass in arguments after the function name. See the Basic Widget Methods in the Tkinter docs for details. So we can re-write changeEntry like this:
def changeEntry():
for j in range(3):
ent.after(1000 * j, abcdef, j)
Thanks, Bryan!

getting around ints and floats with button initialisation for Tkinter calculator

Below is a follow on from this question...
Python & Tkinter - buttons command to set label textvariable issue
I've finished the calculator but here is the problem:
My buttons were all built using a for-loop; function buttons included. I like the fact that the code is short and don't really want to go and remove all function buttons from the for-loop but i might have to to get around the problem where I don't get a float value returned for the division of two integers.
i.e 12/8 = 1 according to this calculator.
Any clever ideas how I could do that without removing the operators form the for-loop?
from Tkinter import *
import Tkinter as tk
import tkMessageBox
# main window
root = Tk()
root.title('Calculator')
# button set
buttons = ['1','2','3','4','5','6','7','8','9','0','+','-','/','*','.']
sum_value = StringVar()
def appear(x):
return lambda: output_window.insert(END,x)
# output window
output_window = tk.Entry(root, textvariable=sum_value, width=20, font = 'courier 10')
output_window.grid(row=0, columnspan=3, sticky=(E,W))
def equals():
try:
result = eval(output_window.get())
except:
result = 'INVALID'
output_window.delete(0,END)
output_window.insert(0,result)
def refresh():
output_window.delete(0,END)
# button creation
r=1
c=0
for i in buttons:
if c < 2:
tk.Button(root, text = i, command = appear(i), pady = 3).grid(row = r, column = c, sticky = (N,S,E,W))
c += 1
else:
tk.Button(root, text = i, command = appear(i), pady = 3).grid(row = r,column = c,sticky = (N,S,E,W))
r += 1
c = 0
# clear and equal button
equal = tk.Button(root,text='=',padx = 5, pady=3, command=equals)
equal.grid(row=6,column=0,sticky=(N,S,E,W))
clear = tk.Button(root,text='CLEAR',padx = 5, pady=3,command = refresh)
clear.grid(row=6,column=1, columnspan = 2,sticky=(N,S,E,W))
#menu
menubar = Menu(root)
def quit1():
if tkMessageBox.askokcancel("Quit","Are you sure you want to quit?"):
root.destroy()
viewMenu = Menu(menubar)
viewMenu.add_command(label='Quit', command = quit1)
menubar.add_cascade(label="Home", menu=viewMenu)
root.config(menu=menubar)
root.mainloop()
Write from __future__ import division as the first line of your program. This will make / into the floating point division operator. Of course, now 8/4 will give 2.0 not the integer 2. (If you wanted integer division also, you could add a // button, but I gather you want this to work like a standard hand-held calculator.)

Refering to a button then config it

Okay, the problem Im having is that I do not understand how to refer to that particular button that was pressed. Im doing a game, if the use clicks a button I wanna know which X and Y coordinate it had. So, if you have a grid of buttons and you click one I want the coordinates and then I will change that button's color. :)
PROBLEM: Knowing which button was pressed in a grid.
Thanks in advance!
def matris():
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i: function(i))
newButton.grid(row = i, column = 0)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 5: function(i))
newButton.grid(row = i, column = 1)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 10: function(i))
newButton.grid(row = i, column = 2)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 15: function(i))
newButton.grid(row = i, column = 3)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 20: function(i))
newButton.grid(row = i, column = 4)
def function(i):
if button 23 was clicked.changeColor to e.g "blue"
After reading all the comments and edits, it looks like what you really want to know is "which button was clicked?" when using the same command for multiple buttons.
The easiest way to do that is to pass some sort of unique identifier to the command associated with the widget. The most straight-forward way is to pass a reference to the widget itself, though that requires a two step process.
For example:
this_button = Button(...)
this_button.configure(command=lambda button=this_button: do_something(button))
def do_something(button):
print "you clicked this button:", button
You can also use functools.partial to get the same result if you find lambda hard to wrap your head around:
this_button.configure(command=functools.partial(do_something, this_button)
If you prefer to create your button in a single step instead of two, you need some way to identify it. For me, the easiest way is with a dictionary. For example, if you're creating rows and columns of buttons, you could do this:
button = {}
for r in range(10):
for c in range(10):
button[r,c] = Button(..., command=lambda row=r, column=c: do_something(row, ccolumn))
def do_something(row, column):
print "you clicked button", button[row,column]
You need to keep a reference to each button if you want to modify that button later. Since your numbering scheme is sequential you can use a list:
newButton = Button(...)
buttons.append(newButton)
...
def function(i):
widget = buttons[i-1] # -1, because list indexes are zero-based
if i == 23:
widget.configure(background="blue")
If you're creating a grid of buttons and the buttons are otherwise identical, you might want to consider a simpler structure such as:
for row in range(5):
for column in range(5):
...
It becomes instantly clear that you're creating five rows of five columns, whereas with your original code it takes several seconds of study to come to the same conclusion.
Instead of using the command argument, use the bind method to set a callback for <Button-1>. When Tkinter calls your callback, it will pass in an event object, which contains the widget that raised the event.
from Tkinter import *
def buttonClicked(e):
e.widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", buttonClicked)
newButton.grid(row=y, column=x)
root.mainloop()
Edit: suppose you want to treat buttons differently depending upon their position in the grid. The simplest way to do this is to use a global dictionary to associate each button with its coordinate.
from Tkinter import *
coords = {}
def buttonClicked(e):
x,y = coords[e.widget]
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
e.widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", buttonClicked)
newButton.grid(row=y, column=x)
coords[newButton] = (x,y)
root.mainloop()
In general, however, having a variable at the global scope isn't a good idea. It may be worth the effort to group all of your grid-based code into a single class, so none of its details leak out to the rest of the program.
from Tkinter import *
class ButtonGrid:
def __init__(self, root):
self.coords = {}
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", self.buttonClicked)
newButton.grid(row=y, column=x)
self.coords[newButton] = (x,y)
def buttonClicked(self, e):
x,y = self.coords[e.widget]
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
e.widget["bg"] = "red"
root = Tk()
b = ButtonGrid(root)
root.mainloop()
If you prefer functional over object-oriented, here's an alternative. Use the command option as you did in your original code, but use the functools.partial function to specify ahead of time which variables ought to be passed to the function.
from Tkinter import *
import functools
def buttonClicked(widget, x, y):
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton["command"] = functools.partial(buttonClicked, newButton, x, y)
newButton.grid(row=y, column=x)
root.mainloop()

Categories

Resources