Creating button widgets in a loop tkinter [duplicate] - python

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 1 year ago.
I have some code below that creates 26 buttons in a tkinter window with each letter of the alphabet in it. I want the code to print the letter in the button when it is pressed. My code prints z no matter what button is pressed. How could I fix this?
import tkinter as tk
import string
def whenPressed(button):
print(button['text'])
root = tk.Tk()
alphabet = list(string.ascii_lowercase)
for i in alphabet:
btn = tk.Button(root, text = i, command = lambda: whenPressed(btn))
btn.grid(row = alphabet.index(i)//13, column = alphabet.index(i)%13, sticky = 'nsew')

Try this:
from functools import partial
import tkinter as tk
import string
def whenPressed(button, text):
print(text)
root = tk.Tk()
alphabet = list(string.ascii_lowercase)
for i in alphabet:
btn = tk.Button(root, text=i)
command = partial(whenPressed, btn, i)
btn.config(command=command)
row = alphabet.index(i) // 13
column = alphabet.index(i) % 13
btn.grid(row=row, column=column, sticky="news")
You have to update the button's command after you create it using <tkinter.Button>.config(command=...). I also used functools.partial. Its documentation is here. Also it's usually better to also pass in the text instead of having button["text"].
A little bit of updated version to avoid calculations to use with rows and columns:
from functools import partial
import tkinter as tk
import string
def whenPressed(button, text):
print(text)
root = tk.Tk()
alphabet = list(string.ascii_lowercase)
for i in range(2):
for j in range(13):
text = alphabet[13*i+j]
btn = tk.Button(root, text=text)
command = partial(whenPressed, btn, text) # Also can use lambda btn=btn,text=text: whenPressed(btn, text)
btn.config(command=command)
btn.grid(row=i, column=j, sticky="news")
root.mainloop()

Related

Weird behavior with Tkinter and lambda in a loop? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed last month.
I am writing an interface which consists of a label with an image, an entrybox as well as a stringvar, and a button.
I am trying to create these in a loop as there are 6 of them, to try to write more efficient code.
here's what I have:
x = 0
y = 2
for mapfile in ["pillars","cosmic","double","underpass","utopia","octagon"]:
self.widgets[mapfile+"-image"] = ImageTk.PhotoImage(Image.open("./images/png/"+mapfile+".png"))
self.widgets[mapfile+"-label"] = ttk.Label(self, image=self.widgets[mapfile+"-image"])
self. Widgets[mapfile+"-string"]= tk.StringVar()
self.widgets[mapfile+"-entry"] = ttk.Entry(self, textvariable=self.widgets[mapfile+"-string"])
self.widgets[mapfile+"-button"]= ttk.Button(self, text="Browse", padding=3, command=lambda: self.browseFiles(self.widgets[mapfile+"-string"]))
self.widgets[mapfile+"-label"].grid(column=x, row=y, columnspan=2)
self.widgets[mapfile+"-entry"].grid(column=x, row=y+1, sticky="we", padx=3, pady=0)
self.widgets[mapfile+"-button"].grid(column=x+1, row=y+1, sticky="we")
x += 2
if x > 4:
x = 0
y += 2
You can see here, I create all the elements needed for each "map" and grid them.
note in the stringvar, and the button, the command I am calling a lambda function to pass a reference to the stringvar to the browseFiles method.
Now, in the browseFiles method, here it is:
def browseFiles(self, mapfile):
print(str(mapfile))
filename = fd.askopenfilename( initialdir=mapfile.get(), \
title="Select Map", \
filetypes=(("Map Files", "*.upk *.udk"),
("ZIP Files", "*.zip")))
if filename != "":
mapfile.set(filename)
return filename
(the comments are there bc its not working)
within that code there, the only stringvar that is getting passed is the final one created in the loop (in this case, no matter which button I click, it prints "PYVAR_5". With the code uncommented, it only pulls (and updates) the octagon entry.
...why?
Here's the full script so you can try it out:
#!/usr/bin/env python
import os
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd
from tkinter import messagebox as mb
import shutil as sh
import zipfile as zip
from PIL import ImageTk, Image
# import win32api as win
class Merl(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self)
self.root = root
self.root.title("Map Editor for Rocket League")
self.grid(column=0,row=0,sticky=("nsew"))
self.widgets = {}
# for element in ["image","label","button","entry","stringVar","command"]:
x = 0
y = 2
for mapfile in ["pillars","cosmic","double","underpass","utopia","octagon"]:
self.widgets[mapfile+"-image"] = ImageTk.PhotoImage(Image.open("./images/png/"+mapfile+".png"))
self.widgets[mapfile+"-label"] = ttk.Label(self, image=self.widgets[mapfile+"-image"])
self.widgets[mapfile+"-string"]= tk.StringVar()
self.widgets[mapfile+"-entry"] = ttk.Entry(self, textvariable=self.widgets[mapfile+"-string"])
self.widgets[mapfile+"-button"]= ttk.Button(self, text="Browse", padding=3, command=lambda: self.browseFiles(self.widgets[mapfile+"-string"]))
self.widgets[mapfile+"-label"].grid(column=x, row=y, columnspan=2)
self.widgets[mapfile+"-entry"].grid(column=x, row=y+1, sticky="we", padx=3, pady=0)
self.widgets[mapfile+"-button"].grid(column=x+1, row=y+1, sticky="we")
x += 2
if x > 4:
x = 0
y += 2
self.widgets["cosmic-string"].set("/home/betty/test.upk")
print(self.widgets)
print(self.widgets["cosmic-string"].get())
for button in ["pillars","cosmic","double","underpass","utopia","octagon"]:
print(self.widgets[button+'-button'].cget("command"))
def browseFiles(self, mapfile):
print(str(mapfile))
filename = fd.askopenfilename( initialdir=mapfile.get(), \
title="Select Map", \
filetypes=(("Map Files", "*.upk *.udk"),
("ZIP Files", "*.zip")))
if filename != "":
mapfile.set(filename)
return filename
root = tk.Tk()
merl = Merl(root)
root.mainloop()
Thanks to #jasonharper - posted this link: tkinter creating buttons in for loop passing command arguments
apparently lambda functions "make a note" of the variable, then resolve it at runtime instead of storing it during the loop, so you have to define the variable in the lambda declaration. my fix was:
....command=lambda mapfile=mapfile:....

Extended ASCII in tkinter

I'm trying to create an inspiration app, which displays random words. it works for alpha characters, but when a word has characters like öäü or ß in them, it just displays random gibberish.
Wanted Text: Rindfleischverpackungsetikettierungsgerät
Text displayed: Rindfleischverpackungsetikettierungsgerßt
Another Example:
Here is my code for it (still very basic and functional)
import tkinter as tk
from random import randint
class ButtonBlock(object):
def __init__(self, master):
self.master = master
self.button = []
self.button_val = tk.IntVar()
entry = tk.Entry()
entry.grid(row=0, column=0)
entry.bind('<Return>', self.onEnter)
def onEnter(self, event):
entry = event.widget
num = int(entry.get())
for button in self.button:
button.destroy()
for i in range(1, num+1):
leine = randint(1, 1908815)
print(leinen[leine])
self.button.append(tk.Label(
self.master, text=leinen[leine]))
self.button[-1].grid(sticky='WENS', row=i, column=2, padx=1, pady=1)
def onSelect(self):
print(self.button_val.get())
if __name__ == '__main__':
deutsch = open("WORT.txt", "r")
leinen = deutsch.readlines()
root = tk.Tk()
ButtonBlock(root)
root.mainloop()
Is there any way to allow tkinter to render the characters öäüß properly? (BTW WORT.txt is just a word list)
To read the extended ASCII characters in the text file, specify the encoding when you open the file like this:
deutsch = open("WORT.txt", "r", encoding="latin1")
This needs to be done because if encoding is not specified the default used is platform dependent (and apparently not the "latin1" needed
on your system). See the documentation for the built-in open() function.

Tkinter entry widget execution [duplicate]

This question already exists:
Entry widget in tkinter
Closed 1 year ago.
So I made a simple program however it doesn't seem to work
my code is:
e = Entry(root, font = 20,borderwidth=5)
e.grid(row=1)
def capture(event):
print(e.get())
e.bind("<Key>", capture)
However the first time I enter something in the box, all I get is an empty string.
As #Art stated:
You can use "<KeyRelease>", e.bind("<Key>", lambda event: e.after(1, capture, event))" or simply Use StringVar()
from tkinter import *
root=Tk()
e = Entry(root, font = 20,borderwidth=5)
e.grid(row=1)
def capture(event):
print(e.get())
e.bind("<Key>", lambda event: e.after(1, capture, event))
root.mainloop()
Or you can use a StringVar()
from tkinter import *
root=Tk()
s=StringVar()
e = Entry(root,textvariable=s, font = 20,borderwidth=5)
e.grid(row=1)
def capture(*args):
print(s.get())
s.trace("w",capture)
root.mainloop()

How do I insert text into the Text widget in the current tab?

I recently learned about the Notebook widget. And I wanted to add tabs dynamically.
from tkinter import *
from tkinter import ttk
root = Tk ()
n = ttk.Notebook (root)
n.pack (fill = X)
tab = 1
def new_tab ():
global tab
text = Text (root)
text.pack ()
n.add (text, text = ("tab" + str (tab)))
tab += 1
def check ():
'' 'code to insert text into Text widget' ''
...
plus = Button (root, text = '+', command = new_tab)
plus.pack (side = LEFT)
check_button = Button (root, text = 'check', command = check)
check_button.pack (side = LEFT)
root.mainloop ()
I added tabs, but when I try to insert any text using insert in the check function, python gives an error. But the problem is not entirely a bug. I wanted to insert text into the text widget in the current tab.
You need to make a text widget that exists outside of the new_tab() function — in your code it's a local variable which cannot be accessed after the function returns.
A simple way (but not the best, since global variables are bad) is to make the text variable a global. A better way would be to use classes to encapsulate your application's data.
Here's an example of the former:
from tkinter import *
from tkinter import ttk
root = Tk()
n = ttk.Notebook(root)
n.pack(fill=X)
tab = 1
text = None
def new_tab():
global tab
global text
text = Text(root)
text.pack()
n.add(text, text=("tab" + str(tab)))
tab += 1
def check():
""" Insert text into Text widget if it exists. """
if text:
text.insert(END, 'new text\n')
plus = Button(root, text='+', command=new_tab)
plus.pack(side=LEFT)
check_button = Button(root, text='check', command=check)
check_button.pack(side=LEFT)
root.mainloop()
For comparison, here's an example of the latter which has a minimal number of globals because it's based on the object-oriented programming paradigm, aka the OOP way, of implementing software — namely by defining a class that encapsulates the whole application.
Note I've also changed how the imports are being done because, for the most part, wildcard imports are also considered a poor programming practice (see Should wildcard import be avoided?).
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
class MyApplication(tk.Tk):
def __init__(self):
super().__init__()
self.nb = ttk.Notebook(self)
self.nb.pack(fill=X)
self.tab = 1
self.text = None
self.plus = tk.Button(self, text='+', command=self.new_tab)
self.plus.pack(side=LEFT)
self.check_button = tk.Button(self, text='check', command=self.check)
self.check_button.pack(side=LEFT)
def new_tab(self):
self.text = tk.Text(self)
self.text.pack()
self.nb.add(self.text, text=("tab" + str(self.tab)))
self.tab += 1
def check(self):
""" Insert text into Text widget if it exists. """
if self.text:
self.text.insert(END, 'new text\n')
app = MyApplication()
app.mainloop()

In Python Tkinter, how do I run code after mainloop window opens? [duplicate]

This question already has answers here:
How do you run your own code alongside Tkinter's event loop?
(5 answers)
Closed 3 years ago.
I want to create a little animation where text appears on an old monochrome terminal like screen as if someone is typing it. However I constantly run into problems with trying to run code after the GUI window opens up. The text is always already there when the window opens or doesnt appear at all. Any help is very appreaciated:)
string = "Hello World this is a Test String"
import random
import time
import tkinter as tk
from tkinter import *
vid = tk.Tk()
vid.title('Terminal')
text = Text( vid, width = 100, height = 50, highlightthickness=1, bg='black', highlightbackground="black", font=('Courier', 14), fg='green')
text.pack()
def main():
for i in string:
text.insert(END, i)
time.sleep(0.2)
text.after(10, main)
vid.mainloop()
This is what I came up with so far:/
You need to use update_idletasks()after your sleep. The following works for me, let me know if you have any other questions:
string = "Hello World this is a Test String"
import random
import time
import tkinter as tk
from tkinter import *
vid = tk.Tk()
vid.title('Terminal')
text = Text( vid, width = 100, height = 50, highlightthickness=1, bg='black', highlightbackground="black", font=('Courier', 14), fg='green')
text.pack()
def main():
for i in string:
text.insert(END, i)
time.sleep(0.2)
vid.update_idletasks()
vid.after(10, main)
vid.mainloop()
It is generally not a good idea to use sleep for an event-driven program (tkinter is event-driven, as it is the case for most GUI libraries). Here, it is better to base your animation on the after method:
import random
import tkinter as tk
string = "Hello World this is a Test String"
def animate(n=0):
text.insert(tk.END, string[n])
n += 1
if n == len(string): # reached end of string
text.insert(tk.END, '\n') # insert newline
n = 0 # and reset current char index
text.after(200, lambda:animate(n)) # call function again in 200ms
vid = tk.Tk()
vid.title('Terminal')
text = tk.Text(vid, width=100, height=50, highlightthickness=1, bg='black',
highlightbackground="black", font=('Courier', 14), fg='green')
text.pack()
vid.after(10, animate)
vid.mainloop()
Note: I've slightly changed your animation to create an infinite loop where the string is endlessly printed char by char, inserting a new line when the end is reached. Just for the fun...

Categories