Storing and using GUI objects with Tkinter - python

So I have a class that just creates a gui with a text box to type into. I would like to store multiple of these objects and their content that is entered by the user. I have a button (on a seperate gui) that creates the new object and spawns the gui and then stores that into a simple list listOobjects = []. My thought process was that I could simply just call listOobjects[i] when a button was pressed to bring that gui back with its contents still in place. That didn't work so then I thought maybe I could use the listOobjects[i].withdraw() and listOobjects[i].deiconify() to hide and recall what gui I want. Not only did that not work but I also feel that isn't the best course of action with possibly 12 gui's practically 'minimized'. I also looked into pickle to save and recall objects. I haven't tried it yet but was curious of my best course of action here?

Although I don't know what is considered best practice in this case, here is one possible way that avoids keeping track of 'minimized' widgets that aren't being used.
This solution deletes widgets which are not currently being displayed by calling the destroy method, so the program isn't doing extra work maintaining objects that aren't being used.
In order to be able to recall the deleted widgets when the button is pressed, all relevant information from a widget is recorded and stored in a tuple before the widget is deleted. This way, no information is lost, but only simple values are being stored instead of tkinter widgets.
These tuples of values are then used as parameters to instantiate new widgets which exactly replicate the old, deleted ones.
Here is a simple demo that toggles between three different Entry widgets:
import tkinter as tk
window = tk.Tk()
window['width'] = 500
window['height'] = 300
textBox1 = tk.Entry(master=window, bg='darkblue', fg='yellow', text="First",
relief=tk.RAISED, bd=3)
textBox2 = tk.Entry(master=window, bg='purple', fg='white', text="Second",
relief=tk.RAISED, bd=3)
textBox3 = tk.Entry(master=window, bg='darkgreen', fg='yellow', text="Third",
relief=tk.RAISED, bd=3)
textBox1.place(x=50, y=100, width=100, height=30)
textBox2.place(x=200, y=100, width=100, height=30)
textBox3.place(x=350, y=100, width=100, height=30)
# Will store all information necessary to reconstruct and place previously displayed
# text boxes which have been removed.
storage = [None, None, None]
# Store a reference to the text box which is currently displayed
activeTextBox = {'textBox': textBox1, 'index': 0}
# After recording all information to be used in 'storage', call 'destroy()' to delete
# the widget instead of hiding it (for more efficiency)
def sendToStorage(textBox, index):
parameters = (textBox['bg'], textBox['fg'], textBox['relief'], textBox['bd'], textBox.get())
storage[index] = parameters
textBox.destroy()
# Using the stored information, construct a new text box (tk.Entry widget) which is
# identical to the old one that was deleted.
def retrieveFromStorage(index):
txtB = storage[index]
storage[index] = None
activeTextBox['textBox'] = tk.Entry(window, bg=txtB[0], fg=txtB[1], relief=txtB[2], bd=txtB[3])
activeTextBox['textBox'].insert(0, txtB[4])
activeTextBox['textBox'].place(x=50+150*index, y=100, width=100, height=30)
# Put the old text box in storage and retrieve the one after it. Increment the index
# of the text box that is currently active (loop around once you get to the end of 'storage').
def toggleTextBox():
sendToStorage(activeTextBox['textBox'], activeTextBox['index'])
activeTextBox['index'] += 1
if activeTextBox['index'] == len(storage):
activeTextBox['index'] = 0
retrieveFromStorage(activeTextBox['index'])
window.update()
# DEMO: CALL FUNCTION TO STORE AND DELETE ALL BUT 1 TEXT BOX
sendToStorage(textBox2, 1)
sendToStorage(textBox3, 2)
# THIS IS THE BUTTON THAT WILL CYCLE BETWEEN THE TEXT BOXES
toggleButton = tk.Button(master=window, text='TOGGLE ACTIVE TEXT BOX',
command=toggleTextBox)
toggleButton.place(x=100, y=200, width=300, height=50)
window.mainloop()
It will keep track of the text boxes' current text (that the user has entered) as well as their formatting options. Try it out and see if it does what you're looking for!

Related

Tkinter: Problems having access to text input in text widget

I tried creating a program that will take in the symptoms of a person and return the disease they have. This is the GUI part of the project.
from tkinter import *
root = Tk()
root.title("Health GUI")
root.geometry("1000x625")
symptoms_list = []
def print_symptoms():
print(symptoms_list)
def typeSymptoms():
gap3 = Label(text="").pack()
symptoms_entry = Text(width=50, height=20)
symptoms_entry.pack()
symptoms_list.append(symptoms_entry.get(1.0, END))
done_symptoms = Button(text="I have written my symptoms", width=25, height=5, command=lol)
done_symptoms.pack()
gap1 = Label(text="").pack()
title = Label(text="HEALTH GUI", font=30).pack()
gap2 = Label(text="").pack()
start_button = Button(text="Click here to start", width=30, height=5, command=typeSymptoms, font=20).pack()
root.mainloop()
Just for simplicity, I tried printing out the symptoms given by the user to the console but it gives me a list with '\n'. Please help. Thanks!(PS: I lerned Tkinter day before yesterday so I don't know much)
At the moment, your variable symptoms_list just holds the contents of the newly created Text widget, since you append this content at startup.
If you want to add the symptoms to the list, you need to have your function lol() that you call when pressing the button.
This function should look something like:
def lol():
symptoms_text = symptoms_entry.get(1.0, END)
symptoms_list = symptoms_text.split('\n')
print_symptoms()
However, your widgets and the symptoms_list would have to be global variables in order for this program to work. It would probably be better, while you are getting acquainted with Tkinter, to learn how to create a dialog as Class with attributes. That makes sharing values between methods so much easier.

Updating Entry and Label widget in Tkinter

i have defined a GUI class that creates a tkinter window with a couple of entries. I would like that every time that the user overwrites the Entries and press Enter, there is some operation done in the background. In addition, i would like that the entries are checking regularly certain values and updating them, so the user could see if they changed; In the example below i use a static dictionary, but normally those parameters are obtained from a camera and could fluctuate.
However, i am not even able to get the correct value printed in the label. I am not a tkinter expert so any idea would be appreciated
from tkinter import *
class GUI():
def __init__(self, window, window_title,input_dict):
self.window = window
self.window.title(window_title)
self.window.geometry('400x200')
top_frame = Frame(self.window)
top_frame.pack(side=TOP, pady=5)
Label(top_frame, text="Frame rate (fps)").grid(row=0)
Label(top_frame, text="Exposure time (ms)").grid(row=2)
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.e1_var = StringVar() # or StringVar(top)
self.e1_var.set(str(round(input_dict['frameRate'])))
self.e2_var = StringVar() # or StringVar(top)
# print(type(self.e2_var))
self.e2_var.set(str(round(input_dict['Exp_time'])))
self.fps_entry = Entry(top_frame,textvariable=self.e1_var)
self.exptime_entry = Entry(top_frame,textvariable=self.e2_var)
self.fps_entry.bind("<Return>",self.my_tracer)
self.exptime_entry.bind("<Return>",self.my_tracer)
self.fps_entry.grid(row=0, column=1)
self.exptime_entry.grid(row=2, column=1)
self.window.mainloop()
def my_tracer(self,event):
val1=int(self.e1_var.get())
val2=int(self.e2_var.get())
self.labeling.configure(text=str(val1*val2))
input_dict = {
'frameRate': 50,
'Exp_time': 5000}
video_object=GUI(Tk(),"Test",input_dict)
The error your code produces is AttributeError: 'NoneType' object has no attribute 'configure', right?
Look at this line:
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.labeling will be None because grid() returns None. It is indeed bad practice to 'chain' a geometry manager to the creation of a widget. Change to:
self.labeling=Label(top_frame, text="Result")
self.labeling.grid(row=3)
Now the labels are updating when the user enters a new value.

Struggeling with defining subclass of tkinter frame (in python) which should let user choose which part of content to display

I am trying to make a class in Python (using Tkinter) whom I can pass multiple Widgets to store them; of these widgets, the class should always display only one in a frame, and when I press a button, a drop-down menu should open from which I can choose which one of the widgets the frame should go on displaying.
My actual implementation of this idea is working pretty well, but only when I put only one of these frames into a window. As soon as I define more than one, they start to behave oddly...
One example:
I used the class and gave it three Tkinter labels as an input. One of these Labels displays the text "Label 1", the second the text "Label 2" and the third the text "Label 3".
I then .pack() the whole thing into my label and it works fine. This is how it looks:
image of the widget
When I click on the '+'-Button, it unwraps like this:
image of the widget with all Labels displayed
And when I click on the 'x'-Button, it goes back to its initial state. When I, however, click on the radio button next to 'Label 2', it goes back to its initial state, but now displaying 'Label 2' instead of 'Label 1':
widget now displaying label 2
So far, It's doing exactly what I want it to do: I can switch between multiple sub-widgets using one widget in which they stay contained.
However, the problems start as soon as I try to put multiple of these things into a single window. I tried to put one into the window containing "Label 1"-"Label 3", one containing "Label 4"-"Label 6" and one containing "Label 7"-"Label 9". The problem is that when I press the "+"-Button of one of them, all of them start to show their whole content (so suddenly, I have "Label 1"-"Label 9" unwrapped and not only "Label 1"-"Label 3"). As long as they are closed, they look somewhat like this: weird formatted stuff
And that's how it looks when I have clicked the "+"-Button: all contents wrapped out
I think the reason for all of them unwrapping at once is that for some reason, the buttons (the '+'- and 'x'-Button) call the specified function in ALL of the frames at once, not only in the one it's in. I tried to fix this using lambda whilst specifying the function the buttons should call, but it is still not working.
I am not a native english speaker, so sorry if my explanation seems a little weard, and also, I have all my python knowledge from learning-by-doing and the internet, so also sorry if I mess up the words for everything a little. Maybe my code helps explayning the problem a little better:
class MultiInput(Frame):
"""use .add(Widged)-Method to add widged!"""
subs = []
adders = []
index = 0
def __init__(self, parent):
Frame.__init__(self, parent)
self.indexVar = IntVar()
self.indexVar.set(0)
self.dropdown = Button(self, text="+", command=self.drop)
self.dropdown.grid(row=0, column=0)
self.dropup = Button(self, text="x", command=self.undrop)
def drop(self):
num = -1
self.dropdown.grid_forget()
self.dropup.grid(row=0, column=0)
for i in range(0, len(self.subs)):
num += 1
if i != self.index:
self.subs[i].grid(column=1, row=num+1)
self.adders[i].grid(column=0, row=num+1)
def undrop(self):
for i in range(0, len(self.subs)):
self.subs[i].grid_forget()
self.adders[i].grid_forget()
self.subs[self.index].grid(row=0, column=1)
self.dropup.grid_forget()
self.dropdown.grid(row=0, column=0)
def add(self, Widged):
if len(self.subs) == 0:
self.subs.append(Widged)
self.adders.append(Radiobutton(self,
variable = self.indexVar,
command = self.change,
value = 0))
self.change()
else:
self.subs.append(Widged)
self.adders.append(Radiobutton(self,
variable = self.indexVar,
command = self.change,
value = len(self.subs) - 1))
def change(self):
self.index = self.indexVar.get()
self.undrop()
and here is how I called it in my first example, where it is perfectly working:
root = Tk()
multi = MultiInput(root)
multi.add(Label(multi, text="Label 1"))
multi.add(Label(multi, text="Label 2"))
multi.add(Label(multi, text="Label 3"))
multi.pack()
As soon as I ad this to the end of the code (to add mere than one MultiInput), it starts doind the stuff from my second example:
multi2 = MultiInput(root)
multi2.add(Label(multi2, text="Label 4"))
multi2.add(Label(multi2, text="Label 5"))
multi2.add(Label(multi2, text="Label 6"))
multi2.pack()
multi3 = MultiInput(root)
multi3.add(Label(multi3, text="Label 7"))
multi3.add(Label(multi3, text="Label 8"))
multi3.add(Label(multi3, text="Label 9"))
multi3.pack()
So here comes my question: How can I get the buttons to call the function they should call so that they only use it on the Object they are supposed to use it on? Or do I have an entirely wrong idea of using classes in Python?

Python - TKinter - Destroying widgets not lowering frame height

I am having and issue where I have a frame in a game that displays the current progress of the game (let's call this frame; "results").
If the player chooses to start a new game all the widgets inside results get destroyed and the frame is forgotten to hide it until it is used again.
Now the issue I am having is; When results gets called back it is in-between two other frames. However, it has remained the size it was in the previous game when it has contained all the widgets, before the widgets were destroyed. The widgets are not shown in the frame but it's still the size it was when the widgets were there.
As soon as a new widget is placed in results the size is corrected but I can't figure out how to make the height = 0. I have tried results.config(height=0) but that hasn't worked.
Does anyone know how to "reset" the size of the frame to 0?
Sorry for the proverbial "wall-of-text" but I couldn't find a way to provide the code in a compact way.
Thanks
If I completely understand what you want, then this illustration is correct:
The blue is the results frame
The results removed, everything else resized:
And the corresponding code for this is something like:
import tkinter
RESULTS_WIDTH = 128
root = tkinter.Tk()
left_frame = tkinter.Frame(root, height=64, bg='#cc3399')
right_frame = tkinter.Frame(root, height=64, bg='#99cc33')
def rem_results(event):
# Remove widget
results.destroy()
# Resize other widthets
left_frame.config(width=128 + RESULTS_WIDTH/2)
right_frame.config(width=128 + RESULTS_WIDTH/2)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=1)
def add_results(event):
# Create results widget
global results
results = tkinter.Frame(root, width=RESULTS_WIDTH, height=64, bg='#3399cc')
results.grid(row=0, column=1)
# Resize other widgets
left_frame.config(width=128)
right_frame.config(width=128)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=2)
# Initialize results
add_results(None)
# Bind actions to <- and -> buttons
root.bind( '<Left>', rem_results )
root.bind( '<Right>', add_results )
#$ Enter eventloop
root.mainloop()

get multiple items chosen in a listbox and populate new list with the values, using Tkinter

I am trying to take the selection from a listbox and populate a new list with it, and it will be multiple items. I can't figure this out, here's what i have so far (and I need the actual strings in the list, not the indices). Also, how can I completely get rid of the Tkinter widgets after making the selection - it closes out but it seems like there is a ghost of it still hanging around after it closes.
def execute(*events):
UsrFCList = []
selctd_indices = lbox.curselection()
lst_select = list(selctd_indices)
for i in lst_select:
lbox.get(i)
UsrFCList.append(i)
lbox.quit()
fc_lb = Tk()
scrollbar = Scrollbar(fc_lb)
scrollbar.pack(side=RIGHT, fill=Y)
lbox = AutoSzLB(fc_lb,selectmode=EXTENDED)
for item in lb_list:
lbox.insert(END, *item)
button = Button(fc_lb, text="Analyze selected feature classes", command=execute)
lbox.autowidth(250)
lbox.pack()
button.pack()
lbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=lbox.yview)
mainloop()
I figured it out, instead of
for i in lst_select:
lbox.get(i)
UsrFCList.append(i)
It was
for i in lst_select:
UsrFCList.append(lbox.get(i))

Categories

Resources