How to get the values of all the OptionMenu widgets within a frame inside a canvas in Tkinter - python-3.x

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

Related

Tkinter - label doesn't update

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)
...

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)

Adding padding query insert to Listbox

I have read a number of threads and other resources to try to find the correct way to handle this but I have not found anything that works with my application.
Here is what I am trying to accomplish.
When a query is completed and the insert of the data to a Listbox is done I cannot seem to get it to margin the data insert by 1 character space.
I am using pack() and I have read the tkinter manual for this and have tried each example available along with others found on various threads here.
The widget:
output = tkinter.Listbox(window_2, height = 20, font='Times 10',
width=42, bd=1, bg = '#FFD599', fg = '#9A0615', selectmode=SINGLE)
output.pack()
output.place(x=210, y=195)
I have tried padx and pady with pack() without success, although this works successfully with the Text widget. I have also attempted to use a few alternatives that I have found here on the site but all without success in margining the Listbox when the data is inserted.
Any advice?
pack's padx/pady and ipadx/ipady options don't affect the data that is inside the listbox. The listbox itself doesn't have any options to add an internal margin.
To get a margin around the inside of the listbox, what I normally do is give it a zero borderwidth and highlightthickness, and then place it in a frame with the same background color and let the frame be the border. You can then add any padding you want between the border and the listbox.
This is also convenient because you can put a scrollbar inside the frame, giving it the appearance that it is inside the listbox without actually being inside the listbox.
Example:
import tkinter as tk
root = tk.Tk()
root.configure(background="gray")
listbox_border = tk.Frame(root, bd=1, relief="sunken", background="white")
listbox_border.pack(padx=10, pady=10, fill=None, expand=False)
listbox = tk.Listbox(listbox_border, width=20, height=10,
borderwidth=0, highlightthickness=0,
background=listbox_border.cget("background"),
)
vsb = tk.Scrollbar(listbox_border, orient="vertical", command=listbox.yview)
listbox.configure(yscrollcommand=vsb)
vsb.pack(side="right", fill="y")
listbox.pack(padx=10, pady=10, fill="both", expand=True)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
here is a variation on the much appreciated answer by Bryan Oakley.
it uses ttk widgets instead of tk widgets
the scrollbar tracks your position in the list box when you scroll with the mouse
uses the oStyle.theme_use("clam") because it may look more modern...this can be commented out
'
import tkinter as tk
from tkinter import ttk
try: # allows the text to be more crisp on a high dpi display
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass
root = tk.Tk()
oStyle = ttk.Style()
oStyle.theme_use("clam")
oStyle.configure('LB.TFrame', bd=1, relief="sunken", background="white")
listbox_border = ttk.Frame(root, style='LB.TFrame')
listbox_border.pack(padx=4, pady=4, fill=None, expand=False)
vsb = ttk.Scrollbar(listbox_border)
vsb.pack(side="right", fill="y")
listbox = tk.Listbox(listbox_border, width=20, height=10, borderwidth=0,
highlightthickness=0, selectmode=tk.SINGLE,
activestyle=tk.NONE)
listbox.pack(padx=6, pady=6, fill="y", expand=True)
listbox.config(yscrollcommand=vsb.set)
vsb.config(command=listbox.yview)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
'
first of all to format chars in a tkinter listbox you need to use a fixed font and .format python funcion....;
So you can do something this
Press Load to load data in the listbox and pay attention to this line code
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
import tkinter as tk
RS = (('Apple',10),
('Banana',20),
('Peack',8),
('Lemon',6),)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1,)
f = tk.Frame()
sb = tk.Scrollbar(f,orient=tk.VERTICAL)
self.list = tk.Listbox(f,
relief=tk.GROOVE,
selectmode=tk.BROWSE,
exportselection=0,
background = 'white',
font='TkFixedFont',
yscrollcommand=sb.set,)
sb.config(command=self.list.yview)
self.list.pack(side=tk.LEFT,fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
w = tk.Frame()
tk.Button(w, text="Load", command=self.on_callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
def on_callback(self,):
for i in RS:
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Automatically switching checkboxes on tkinter

I would like to switch on the checkboxes (0, 2, 4) automatically with the click of a button. I have the following code. For some reason it dont work. Please help me.
from tkinter import *
class Error(Frame):
def Widgets(self):
for i in range(len(self.X)):
self.X[i] = Checkbutton(self, text="%d"%(i,))
self.X[i].grid(row=i, sticky=W)
self.X[i].configure(variable = ("var_%d"%(i,)))
self.button = Button(self, text = "set", command = self.test)
self.button.grid(row=5, sticky=W)
def test(self):
for i in range(len(self.X)):
if i == 0 or i == 2 or i == 4:
set (("var_%d"%(i,))) == 1
def __init__(self,initial):
super(Error,self).__init__(initial)
self.X = [{},{},{},{},{}]
self.grid()
self.Widgets()
Window = Tk()
Tool = Error(Window)
Window.mainloop()
The way to handle checkboxes is to associate each box with a variable which reflects wether the box is checked or not.
For an array of checkboxes it is convenient to store these variables in a list. The way I would do it is to create an empty list and then append variables as I go along.
In the function test() I use enumerate in the for-loop as this is the recommended way to generate an index of the list.
from tkinter import *
class Error(Frame):
def __init__(self, master):
super(Error,self).__init__(master)
self.box_list = [] # List to holld checbox variables
self.grid()
self.Widgets()
def Widgets(self):
for i in range(5):
var = BooleanVar() # Create variable to associate with box
cb = Checkbutton(self, text="%d"%(i,))
cb.grid(row=i, sticky=W)
cb.configure(variable=var)
self.box_list.append(var) # Append checkbox variable to list
self.button = Button(self, text = "set", command = self.test)
self.button.grid(row=5, sticky=W)
def test(self):
for i, var in enumerate(self.box_list):
if i == 0 or i == 2 or i == 4:
var.set(True)
Window = Tk()
Tool = Error(Window)
Window.mainloop()

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.

Resources