Tkinter - label doesn't update - python-3.x

This morning I started to work on a little app so I can understand better TkInter.
I didn't get too far because I can't make the label to update after I press a button.
It seems like after I create an instance of the Label object I can't work on it anymore because of the mainloop() I guess? I tried to use .update() and other ways to make this work but I can't figure it out. If I add () to file_explorer method, the label updates but I can't open the file explorer anymore and it also starts the file explorer without pressing the button so it's pointless. Found something here on StackOverflow but still nothing.
from tkinter import *
from tkinter import filedialog as fd
import os
# Main_window
App = Tk()
App.geometry("300x300")
App.resizable(0, 0)
filename = "empty"
class Btn:
def __init__(self, master, pos_x, pos_y, label):
frame = Frame(master)
frame.pack()
self.Button = Button(master, text=label, command=self.file_explorer)
self.Button.place(x=pos_x, y=pos_y)
def file_explorer(self):
global filename
filename = fd.askopenfilename(filetypes=(('text files', '*.txt'), ('All files', '*.*')))
filename = os.path.basename(filename)
class FileLabel:
def __init__(self, master, pos_x, pos_y):
global filename
frame = Frame(master)
frame.pack()
self.label1 = Label(master, text=filename)
self.label1.place(x=pos_x, y=pos_y)
e = Btn(App, 10, 10, "Browse file")
f = FileLabel(App, 90, 12)
App.mainloop()

Updating filename will not update the label automatically. However, you can use StringVar instead of normal string and textvariable option of Label to achieve the goal:
...
filename = StringVar(value="empty")
class Btn:
def __init__(self, master, pos_x, pos_y, label):
frame = Frame(master)
frame.pack()
self.Button = Button(master, text=label, command=self.file_explorer)
self.Button.place(x=pos_x, y=pos_y)
def file_explorer(self):
fname = fd.askopenfilename(filetypes=(('text files', '*.txt'), ('All files', '*.*')))
# update filename
filename.set(os.path.basename(fname))
class FileLabel:
def __init__(self, master, pos_x, pos_y):
frame = Frame(master)
frame.pack()
self.label1 = Label(master, textvariable=filename) # used textvariable instead
self.label1.place(x=pos_x, y=pos_y)
...

Related

How to get the values of all the OptionMenu widgets within a frame inside a canvas in Tkinter

I'm writing a minimalist image tagging app that will list out all the image files in a specific location alongside a dropdown menu to select the options for tagging the image. Once the images are tagged, I need to save the changes to a JSON file and I've got a button for that. How can we read all the options selected so that it can be written into a file?
Following is the code so far:
from tkinter import N, RIGHT, Button, OptionMenu, Scrollbar, StringVar, Tk, Canvas, Frame, Label
class App:
def __init__(self):
self.root = Tk()
self.tags = ['Apples', 'Oranges', 'Berries']
self.GetRows()
self.SaveButton()
self.root.mainloop()
def GetRows(self):
self.canvas = Canvas(self.root)
self.scroll_y = Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
self.frame = Frame(self.canvas)
lst = [f"A01{str(i)}.JPG" for i in range(100)]
for idx, r in enumerate(lst):
filename = Label(self.frame, text=r)
filename.grid(row=idx+2, column=0, sticky=N)
label = StringVar()
drop = OptionMenu(self.frame, label, *self.tags)
drop.grid(row=idx+2, column=1)
# put the frame in the canvas
self.canvas.create_window(0, 0, anchor='nw', window=self.frame)
# make sure everything is displayed before configuring the scrollregion
self.canvas.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'),
yscrollcommand=self.scroll_y.set)
self.canvas.pack(fill='both', expand=True, side='left')
self.scroll_y.pack(fill='y', side='right')
def SaveState(self):
pass
def SaveButton(self):
self.save_button = Button(self.root, text="Save Changes", padx=50, pady=10, command=self.SaveState)
self.save_button.pack(side=RIGHT)
if __name__ == '__main__':
App()
The SaveState method is what will be used to write the selections so far into a file.
Thanks in advance!
In order to make OptionMenu results available try modifying your code so that
all StringVars are accessible outside of GetRows.
def GetRows(self):
...
# Define label as a list
self.label = []
for idx, r in enumerate(lst):
filename = Label(self.frame, text=r)
filename.grid(row=idx+2, column=0, sticky=N)
label = StringVar()
drop = OptionMenu(self.frame, label, *self.tags)
drop.grid(row=idx+2, column=1)
# Save StringVar reference
self.label.append(label)
...
def SaveState(self):
self.data = dict()
# Retrieve results into dictionary
for i, a in enumerate(self.label):
self.data[f"A_{i}"] = a.get()
print(self.data)
Then use json.dump(self.data, a_file) to save it

Tkinter buttons not changing back to the correct color after state changing to active

I am making this PDF tool, and I want the buttons to be disabled until a file or files are successfully imported. This is what the app looks like at the launch:
Right after running the callback for the import files button, the active state looks like this:
I want the colors of the buttons to turn maroon instead of the original grey. They only turn back to maroon once you hover the mouse over them. Any thoughts for how to fix this? Here is the callback for the import button:
def import_callback():
no_files_selected = False
global files
files = []
try:
ocr_button['state'] = DISABLED
merge_button['state'] = DISABLED
status_label.pack_forget()
frame.pack_forget()
files = filedialog.askopenfilenames()
for f in files:
name, extension = os.path.splitext(f)
if extension != '.pdf':
raise
if not files:
no_files_selected = True
raise
if frame.winfo_children():
for label in frame.winfo_children():
label.destroy()
make_import_file_labels(files)
frame.pack()
ocr_button['state'] = ACTIVE
merge_button['state'] = ACTIVE
except:
if no_files_selected:
status_label.config(text='No files selected.', fg='blue')
else:
status_label.config(text='Error: One or more files is not a PDF.', fg='red')
status_label.pack(expand='yes')
import_button = Button(root, text='Import Files', width=scaled(20), bg='#5D1725', bd=0, fg='white', relief='groove',
command=import_callback)
import_button.pack(pady=scaled(50))
I know this was asked quite a while ago, so probably already solved for the user. But since I had the exact same problem and do not see the "simplest" answer here, I thought I would post:
Just change the state from "active" to "normal"
ocr_button['state'] = NORMAL
merge_button['state'] = NORMAL
I hope this helps future users!
As I understand you right you want something like:
...
ocr_button['state'] = DISABLED
ocr_button['background'] = '#*disabled background*'
ocr_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
ocr_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
merge_button['state'] = DISABLED
merge_button['background'] = '#*disabled background*'
merge_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
merge_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
...
...
ocr_button['state'] = ACTIVE
ocr_button['background'] = '#*active background*'
ocr_button.unbind('<Enter>')
ocr_button.unbind('<Leave>')
merge_button['state'] = ACTIVE
merge_button['background'] = '#*active background*'
merge_button.unbind('<Enter>')
merge_button.unbind('<Leave>')
...
If there are any errors, since I wrote it out of my mind or something isnt clear, let me know.
Update
the following code reproduces the behavior as you stated. The reason why this happens is how tkinter designed the standart behavior. You will have a better understanding of it if you consider style of ttk widgets. So I would recommand to dont use the automatically design by state rather write a few lines of code to configure your buttons how you like, add and delete the commands and change the background how you like. If you dont want to write this few lines you would be forced to use ttk.Button and map a behavior you do like
import tkinter as tk
root = tk.Tk()
def func_b1():
print('func of b1 is running')
def disable_b1():
b1.configure(bg='grey', command='')
def activate_b1():
b1.configure(bg='red', command=func_b1)
b1 = tk.Button(root,text='B1', bg='red',command=func_b1)
b2 = tk.Button(root,text='disable', command=disable_b1)
b3 = tk.Button(root,text='activate',command=activate_b1)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
I've wrote this simple app that I think could help all to reproduce the problem.
Notice that the state of the button when you click is Active.
#!/usr/bin/python3
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.init_ui()
def cols_configure(self, w):
w.columnconfigure(0, weight=0, minsize=100)
w.columnconfigure(1, weight=0)
w.rowconfigure(0, weight=0, minsize=50)
w.rowconfigure(1, weight=0,)
def get_init_ui(self, container):
w = ttk.Frame(container, padding=5)
self.cols_configure(w)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
return w
def init_ui(self):
w = self.get_init_ui(self.parent)
r = 0
c = 0
b = ttk.LabelFrame(self.parent, text="", relief=tk.GROOVE, padding=5)
self.btn_import = tk.Button(b,
text="Import Files",
underline=1,
command = self.on_import,
bg='#5D1725',
bd=0,
fg='white')
self.btn_import.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
self.parent.bind("<Alt-i>", self.switch)
r +=1
self.btn_ocr = tk.Button(b,
text="OCR FIles",
underline=0,
command = self.on_ocr,
bg='#5D1725',
bd=0,
fg='white')
self.btn_ocr["state"] = tk.DISABLED
self.btn_ocr.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_merge = tk.Button(b,
text="Merge Files",
underline=0,
command = self.on_merge,
bg='#5D1725',
bd=0,
fg='white')
self.btn_merge["state"] = tk.DISABLED
self.btn_merge.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_reset = tk.Button(b,
text="Reset",
underline=0,
command = self.switch,
bg='#5D1725',
bd=0,
fg='white')
self.btn_reset.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
def on_import(self, evt=None):
self.switch()
#simulate some import
self.after(5000, self.switch())
def switch(self,):
state = self.btn_import["state"]
if state == tk.ACTIVE:
self.btn_import["state"] = tk.DISABLED
self.btn_ocr["state"] = tk.NORMAL
self.btn_merge["state"] = tk.NORMAL
else:
self.btn_import["state"] = tk.NORMAL
self.btn_ocr["state"] = tk.DISABLED
self.btn_merge["state"] = tk.DISABLED
def on_ocr(self, evt=None):
state = self.btn_ocr["state"]
print ("ocr button state is {0}".format(state))
def on_merge(self, evt=None):
state = self.btn_merge["state"]
print ("merge button state is {0}".format(state))
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title(kwargs['title'])
Main(self, *args, **kwargs)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self, title):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()

Tkinter error: bad window path name when deleting frames dynamically

Im trying to recreate a little version of trello in tkinter. Right now im stuck I have a problem when I want to delete frames in a different order. For example: I click on the button and a new frame is generated if I delete that everything works. If I create 3 frames I have to remove them in the same order as I have created them. So I think my problems lies in the pop function but I dont know how to access them manually. When i change the pop function to (1) then I have to delete the second creation first instead of the first.
Here is the code:
from tkinter import *
class Window:
def __init__(self, width, height):
self.root = Tk()
self.width = width
self.height = height
self.root.geometry(width + "x" + height)
class Frames:
def __init__(self):
self.l = Frame(window.root, bg="red", height=300, width=300, relief="sunken")
self.l.place(relwidth=0.3, relheight=0.3)
self.deleteB = Button(self.l, text="X", command=self.delete_frame, bg="blue")
self.deleteB.place(rely=0, relx=0.92)
self.addB = Button(self.l, text="Add", command=self.add_note, bg="blue")
self.addB.place(rely=0, relx=0.65)
def delete_frame(self):
self.l.pack()
self.l.pack_forget()
self.l.destroy()
frames.pop()
def add_note(self):
self.note_Label = Label(self.l, text="Clean the room")
self.note_Label.pack(padx=20, pady=10)
self.delete_Note = Button(self.note_Label, text="X", command=self.del_Note)
self.delete_Note.pack(padx=5, pady=5)
def del_Note(self):
self.note_Label.pack_forget()
self.note_Label.destroy()
class Note:
def __init__(self):
pass
class DragNDrop:
def __init__(self):
pass
def make_draggable(self, widget):
widget.bind("<Button-1>", self.on_drag_start)
widget.bind("<B1-Motion>", self.on_drag_motion)
def on_drag_start(self, event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(self, event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
class Buttons:
def __init__(self):
self.button = Button(window.root, width=20, height=20, bg="blue", command=self.add_frames)
self.button.pack()
def add_frames(self):
frames.append(Frames())
print(frames)
window = Window("800", "600")
frames = []
drag = DragNDrop()
button = Buttons()
while True:
for i in frames:
drag.make_draggable(i.l)
window.root.update()
If someone has an Idea or workaround that would be nice to know.
Also I have another Idea instead of destroying them I could just hide them but in the end that makes the programm really slow at some point.
Here is the error: _tkinter.TclError: bad window path name ".!frame2"
Your code needs to remove the frame from the list. Instead, you're calling pop which always removes the last item. That causes you to lose the reference to the last window, and one of the references in frames now points to a window that has been deleted (which is the root cause of the error)
Instead, call remove:
def delete_frame(self):
self.l.destroy()
frames.remove(self)

How to show images from path in new window in python tk

After browsing path of image i wanna show image on next window in python using Tk library but image is not showing in next window. please take a look on my code below and give answer Thanks.
import tkinter as tk
from tkinter import filedialog as fd
a=""
str1 = "e"
class Browse(tk.Frame):
""" Creates a frame that contains a button when clicked lets the user to select
a file and put its filepath into an entry.
"""
def __init__(self, master, initialdir='', filetypes=()):
super().__init__(master)
self.filepath = tk.StringVar()
self._initaldir = initialdir
self._filetypes = filetypes
self._create_widgets()
self._display_widgets()
def _create_widgets(self):
self._entry = tk.Entry(self, textvariable=self.filepath, font=("bold", 10))
a=self._entry
self._button = tk.Button(self, text="Browse...",bg="red",fg="white", command=self.browse)
self._classify=tk.Button(self,text="Classify",bg="red",fg="white", command=self.classify)
self._label=tk.Label(self, text="IMAGE CLASSIFICATION USING DEEP LERAINING.", bg="blue", fg="white",height=3, font=("bold", 14))
def _display_widgets(self):
self._label.pack(fill='y')
self._entry.pack(fill='x', expand=True)
self._button.pack(fill='y')
self._classify.pack(fill='y')
def retrieve_input(self):
#str1 = self._entry.get()
#a=a.replace('/','//')
print (str1)
def classify(self):
newwin = tk.Toplevel(root)
newwin.geometry("500x500")
label = tk.Label(newwin, text="Classification", bg="blue", fg="white",height=3, font=("bold", 14))
label.pack()
canvas = tk.Canvas(newwin, height=300, width=300)
canvas.pack()
my_image = tk.PhotoImage(file=a, master=root)
canvas.create_image(150, 150, image=my_image)
newwin.mainloop()
def browse(self):
""" Browses a .png file or all files and then puts it on the entry.
"""
self.filepath.set(fd.askopenfilename(initialdir=self._initaldir,
filetypes=self._filetypes))
if __name__ == '__main__':
root = tk.Tk()
labelfont = ('times', 10, 'bold')
root.geometry("500x500")
filetypes = (
('Portable Network Graphics', '*.png'),
("All files", "*.*")
)
file_browser = Browse(root, initialdir=r"C:\Users",
filetypes=filetypes)
file_browser.pack(fill='y')
root.mainloop()
Your global variable a which stores the path of the image is not getting updated. You need to explicitly do it. Below is the code that works. Have a look at the browse() function.
import tkinter as tk
from tkinter import filedialog as fd
a=""
str1 = "e"
class Browse(tk.Frame):
""" Creates a frame that contains a button when clicked lets the user to select
a file and put its filepath into an entry.
"""
def __init__(self, master, initialdir='', filetypes=()):
super().__init__(master)
self.filepath = tk.StringVar()
self._initaldir = initialdir
self._filetypes = filetypes
self._create_widgets()
self._display_widgets()
def _create_widgets(self):
self._entry = tk.Entry(self, textvariable=self.filepath, font=("bold", 10))
a = self._entry
self._button = tk.Button(self, text="Browse...",bg="red",fg="white", command=self.browse)
self._classify=tk.Button(self,text="Classify",bg="red",fg="white", command=self.classify)
self._label=tk.Label(self, text="IMAGE CLASSIFICATION USING DEEP LERAINING.", bg="blue", fg="white",height=3, font=("bold", 14))
def _display_widgets(self):
self._label.pack(fill='y')
self._entry.pack(fill='x', expand=True)
self._button.pack(fill='y')
self._classify.pack(fill='y')
def retrieve_input(self):
#str1 = self._entry.get()
#a=a.replace('/','//')
print (str1)
def classify(self):
global a
newwin = tk.Toplevel(root)
newwin.geometry("500x500")
label = tk.Label(newwin, text="Classification", bg="blue", fg="white",height=3, font=("bold", 14))
label.pack()
canvas = tk.Canvas(newwin, height=300, width=300)
canvas.pack()
my_image = tk.PhotoImage(file=a, master=root)
canvas.create_image(150, 150, image=my_image)
newwin.mainloop()
def browse(self):
""" Browses a .png file or all files and then puts it on the entry.
"""
global a
a = fd.askopenfilename(initialdir=self._initaldir, filetypes=self._filetypes)
self.filepath.set(a)
if __name__ == '__main__':
root = tk.Tk()
labelfont = ('times', 10, 'bold')
root.geometry("500x500")
filetypes = (
('Portable Network Graphics', '*.png'),
("All files", "*.*")
)
file_browser = Browse(root, initialdir=r"~/Desktop", filetypes=filetypes)
file_browser.pack(fill='y')
root.mainloop()
P.S. Do change your initialdir. I changed it as I am not on Windows.

Using a conditional statement to link a button press

The idea is that if one presses one of the buttons the labeled text is inputted in the top left entry box. My initial plan was to use an if statement when the button is pressed and then insert the text according.
However, I am not sure what syntax to use that would allow me to make this one line conditional statement that recognizes the button being pressed. Is this actually possible or do I need to make a separate function?
from tkinter import *
from tkinter import ttk
class GUI():
def __init__(self, master):
self.master = master
master.resizable(True, True)
master.title('Conversion Calculator')
self.tabControl = ttk.Notebook(master)
self.tab1 = ttk.Frame(self.tabControl) # tab set up
self.tabControl.add(self.tab1, text='Builder')
self.tabControl.pack(expand=1, fill="both")
self.builder_entrybox = ttk.Entry(self.tab1, width=24) # entry box set up
self.builder_entrybox.grid(column=0, row=0)
self.builder_outputbox = ttk.Entry(self.tab1, width=24) # output box set up
self.builder_outputbox.grid(column=0, row=1)
self.builder_outputbox.config(state='NORMAL')
self.builder_outputbox.config(state='readonly')
self.CH3_Button = ttk.Button(self.tab1, text='CH3', command=self.builder) # CH3 button
self.CH3_Button.grid(column=1, row=0)
self.CH2_Button = ttk.Button(self.tab1, text='CH2', command=self.builder) # CH2 button
self.CH2_Button.grid(column=2, row=0)
self.OH_Button = ttk.Button(self.tab1, text='OH', command=self.builder) # OH button
self.OH_Button.grid(column=1, row=1)
self.O_Button = ttk.Button(self.tab1, text='O', command=self.builder) # O button
self.O_Button.grid(column=2, row=1)
self.H_Button = ttk.Button(self.tab1, text='H', command=self.builder) # H button
self.H_Button.grid(column=3, row=1)
self.COOH_Button = ttk.Button(self.tab1, text='COOH', command=self.builder) # COOH button
self.COOH_Button.grid(column=3, row=0)
class Logic(GUI):
def builder (self): # adding button text to entry box (tab1)
self.builder_entrybox.insert(0, 'CH3')
if __name__ == "__main__":
root = Tk()
test = Logic(root)
Yes, you would make a new function for every button:
class GUI():
def __init__(self, master):
# ...
self.CH3_Button = ttk.Button(self.tab1, text='CH3', command=self.CH3_builder) # CH3 button
self.CH3_Button.grid(column=1, row=0)
def CH3_builder(self):
self.builder_entrybox.insert('end', 'CH3')
Python can make functions on the fly, either with functools.partial (early binding) or lambda (late binding). Using that you could write the same thing like this:
from functools import partial
class GUI():
def __init__(self, master):
# ...
self.CH3_Button = ttk.Button(self.tab1, text='CH3', command=partial(self.builder_entrybox.insert, 'end', 'CH3')) # CH3 button
self.CH3_Button.grid(column=1, row=0)
But it would be better if you make a small subclass to handle all this for you, which makes your code very reusable and therefore neat:
from tkinter import ttk
import tkinter as tk
class Copa(ttk.Button):
"""A new type of Button that moves the text into a Entry when clicked"""
def __init__(self, master=None, **kwargs):
ttk.Button.__init__(self, master, command=self.builder, **kwargs)
def builder(self):
self.master.builder_entrybox.insert('end', self['text'])
class BuilderFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.builder_entrybox = ttk.Entry(self, width=24) # entry box set up
self.builder_entrybox.grid(column=0, row=0)
self.builder_outputbox = ttk.Entry(self, width=24) # output box set up
self.builder_outputbox.grid(column=0, row=1)
self.builder_outputbox.config(state='NORMAL')
self.builder_outputbox.config(state='readonly')
self.CH3_Button = Copa(self, text='CH3') # CH3 button
self.CH3_Button.grid(column=1, row=0)
self.CH2_Button = Copa(self, text='CH2') # CH2 button
self.CH2_Button.grid(column=2, row=0)
self.OH_Button = Copa(self, text='OH') # OH button
self.OH_Button.grid(column=1, row=1)
self.O_Button = Copa(self, text='O') # O button
self.O_Button.grid(column=2, row=1)
self.H_Button = Copa(self, text='H') # H button
self.H_Button.grid(column=3, row=1)
self.COOH_Button = Copa(self, text='COOH') # COOH button
self.COOH_Button.grid(column=3, row=0)
class GUI():
def __init__(self, master):
self.master = master
master.resizable(True, True)
master.title('Conversion Calculator')
self.tabControl = ttk.Notebook(master)
self.tab1 = BuilderFrame(self.tabControl) # tab set up
self.tabControl.add(self.tab1, text='Builder')
self.tabControl.pack(expand=1, fill="both")
if __name__ == "__main__":
root = tk.Tk()
test = GUI(root)
root.mainloop()

Resources