Save and Load GUI-tkinter - python-3.x

I want to save and load my GUI.
I have made a GUI and I want that when I click on the save button.
It should save the GUI in some blob of data and when I click on load button then it will load the same GUI again.
My GUI has various text widgets, drop down Option Menu.
I am new to python, so someone can help me on this, please?
I have tried pickle module, too.

You can't do what you want to do without doing the work yourself. You'll need to write a function that gathers all the data you need in order to restore the GUI, and then you can save that to disk. Then, when the GUI starts up you can read the data and reconfigure the widgets to contain this data.
Tkinter gives you pretty much everything you need in order to accomplish it, but you have to do all the work yourself. Pickling the GUI won't work.
Here's a contrived example. Enter a few expressions in the window that pops up. Notice that they are added to the combobox. When you exit, the current expression, the saved expressions, and the current value are all saved. The next time you start the GUI, these values will be restored.
try:
import Tkinter as tk
import ttk
except ModuleNotFoundError:
import tkinter as tk
import tkinter.ttk as ttk
import pickle
FILENAME = "save.pickle"
class Example(tk.Frame):
def __init__(self, parent):
self.create_widgets(parent)
self.restore_state()
def create_widgets(self, parent):
tk.Frame.__init__(self, parent, borderwidth=9, relief="flat")
self.previous_values = []
l1 = tk.Label(self, text="Enter a mathematical expression:", anchor="w")
l2 = tk.Label(self, text="Result:", anchor="w")
self.expressionVar = tk.StringVar()
self.expressionEntry = ttk.Combobox(self, textvariable=self.expressionVar, values=("No recent values",))
self.resultLabel = tk.Label(self, borderwidth=2, relief="groove", width=1)
self.goButton = tk.Button(self, text="Calculate!", command=self.calculate)
l1.pack(side="top", fill="x")
self.expressionEntry.pack(side="top", fill="x", padx=(12, 0))
l2.pack(side="top", fill="x")
self.resultLabel.pack(side="top", fill="x", padx=(12, 0), pady=4)
self.goButton.pack(side="bottom", anchor="e", pady=4)
self.expressionEntry.bind("<Return>", self.calculate)
# this binding saves the state of the GUI, so it can be restored later
root.wm_protocol("WM_DELETE_WINDOW", self.save_state)
def calculate(self, event=None):
expression = self.expressionVar.get()
try:
result = "%s = %s" % (expression, eval(expression))
self.previous_values.append(expression)
self.previous_values = self.previous_values[-8:]
self.expressionVar.set("")
self.expressionEntry.configure(values=self.previous_values)
except:
result = "invalid expression"
self.resultLabel.configure(text=str(result))
def save_state(self):
try:
data = {
"previous": self.previous_values,
"expression": self.expressionVar.get(),
"result": self.resultLabel.cget("text"),
}
with open(FILENAME, "wb") as f:
pickle.dump(data, f)
except Exception as e:
print
"error saving state:", str(e)
root.destroy()
def restore_state(self):
try:
with open(FILENAME, "rb") as f:
data = pickle.load(f)
self.previous_values = data["previous"]
self.expressionEntry.configure(values=self.previous_values)
self.expressionVar.set(data["expression"])
self.resultLabel.configure(text=data["result"])
except Exception as e:
print
"error loading saved state:", str(e)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Related

Add Checkbutton "remember my answer" to messagebox askokcancel

What's the easiest way (if any) of adding "Remember my answer" Checkbutton to askokcancel (tkinter messagebox) dialog? I see nothing short of making my own dialog (Toplevel window with the layout similar to askokcancel). I don't think it will be easy to conform the dialog's style. I may popup other dialog (instead of breaking the loop) to ask user if he wants that his answer was applies to all successive "error", but it looks to me no better.
if lg.name in db:
if askokcancel(message=f'"{args.db}" already has "{lg.name}", continue?'):
pass
else:
break
Using Python 3.9.2, Debian GNU/Linux 11 (bullseye)
UPDATE 2023-02-20
Not quite a solution, a workaround
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import textwrap
import tkinter as tk
from tkinter.simpledialog import Dialog
class AskOkCancel(Dialog):
def __init__(self, master, message=None, parent=None, title="askokcancel"):
self.message = message
self._parent = parent
self._title = title
super().__init__(master)
def body(self, master):
self.title(self._title)
master.rowconfigure(0, weight=1)
tk.Label(master, text=textwrap.fill(self.message, width=40), height=3,
relief=tk.SUNKEN).grid(column=0, columnspan=2, row=0)
self.chkvar = tk.IntVar()
self.chk = tk.Checkbutton(master, variable=self.chkvar,
text="Remember my answer")
self.chk.grid(column=0, row=1, columnspan=2, sticky=tk.EW)
self.lift(aboveThis=self._parent)
return self.chk # initial focus
def ok(self, event=None):
super().ok(event)
self.result = True, self.chkvar.get()
def cancel(self, event=None):
super().cancel(event)
self.result = False, self.chkvar.get()
I use it as below:
if lg.name in db and not (lg.name in confirmed):
_d = AskOkCancel(
lv, parent=lv, title='Duplicated record',
message=(f'"{args.db}" already has "{lg.name}", '
f'continue?'))
if _d.result[0]:
if _d.result[1]:
confirmed.add(lg.name)
else:
break
Where confirmed is a set()
I think you would like reading this:
https://stackoverflow.com/questions/69559208/tkinter-messagedialog-askyesno-with-a-checkbox```

Tkinter how update main window combobox values from Toplevel list python3.8

I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.

TkInter - Can't Get Frames to work correctly and resize

TkInter's frames are driving me crazy. My goal is to have an options frame where I can select some options, then press "Archive" and the TkInter window changes to showing the output from the rest of my script.
I cannot get this to size correctly - there appears to be some additional frame taking up space in the window.
import string
from tkinter import *
import tkinter as tk
import threading
def main(argv):
print("In Main")
for arg in argv:
print(arg)
class TextOut(tk.Text):
def write(self, s):
self.insert(tk.CURRENT, s)
self.see(tk.END)
def flush(self):
pass
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, frameClass):
# make new frame - for archive output
self._frame = frameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, newFrameClass):
# make new frame - for archive output
self._frame = newFrameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class OptionsFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test")
master.geometry("325x180")
self.selectedProject = None
self.initUI(master)
def initUI(self, master):
frame1 = Frame(master)
frame1.pack(fill=BOTH, expand=True)
self.label1 = Label(frame1, text="Select Project to Archive, then click Archive")
self.projectListbox = tk.Listbox(frame1, width=60, height=100)
self.projectListbox.bind("<<ProjectSelected>>", self.changeProject)
# create a vertical scrollbar for the listbox to the right of the listbox
self.yscroll = tk.Scrollbar(self.projectListbox,command=self.projectListbox.yview,orient=tk.VERTICAL)
self.projectListbox.configure(yscrollcommand=self.yscroll.set)
# Archive button
self.archiveBtn=tk.Button(frame1,text="Archive",command=self.ArchiveButtonClick)
# Do layout
self.label1.pack()
self.projectListbox.pack(fill="both", expand=True)
self.yscroll.pack(side="right", fill="y")
self.archiveBtn.pack(side="bottom", pady=10, expand=False)
choices = ["test 1", "test 2", "test 3", "test 4", "test 5", "test 6"]
# load listbox with sorted data
for item in choices:
self.projectListbox.insert(tk.END, item)
def getSelectedProject(self):
# get selected line index
index = self.projectListbox.curselection()[0]
# get the line's text
return self.projectListbox.get(index)
# on change dropdown value
def changeProject(self,*args):
self.selectedProject = self.getSelectedProject()
def ArchiveButtonClick(self):
# Switch to second frame - for running the archive
self.changeProject(None)
# Hide existing controls
self.label1.pack_forget()
self.projectListbox.pack_forget()
self.yscroll.pack_forget()
self.archiveBtn.pack_forget()
newFrame = self.master.change(ArchivingOutputFrame)
newFrame.args = [ "-n", self.selectedProject]
newFrame.start()
# Frame shown while archive task is running
class ArchivingOutputFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test Frame 2")
master.geometry("1000x600")
# Set up for standard output in window
self.var = tk.StringVar(self)
lbl = tk.Label(self, textvariable=self.var)
#lbl.grid(row=0, column=0)
lbl.pack(anchor="nw")
def start(self):
t = threading.Thread(target=self.process)
t.start()
def process(self):
main(self.args)
if __name__=="__main__":
# If command line options passed - skip the UI
if len(sys.argv) > 1:
main(sys.argv[1:])
else:
app=Mainframe()
text = TextOut(app)
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
Here is what I get in the UI; note this is showing the UI hierachy from Microsoft's Spy++ - there is a frame I didn't create (at least I don't think I did) that is at the bottom of the window and taking up half of the UI area; this is the yellow highlight. My options pane is thus squeezed into the top half.
Resize also doesn't work - if I resize the window, I get this:
When I click the button and the code to remove the options frame and put in the frame that is capturing stdout/stderr from the main script runs, I get this:
Now the extra space appears to be at the top!
Thanks for any ideas - I know I could switch to using the "Grid" UI layout engine, but this seems so simple - I'm not doing anything sophisticated here that shouldn't work with pack.
That was a lot of complicated code. It would be easier to debug if you provide a Minimal, Complete, and Verifiable example.
However; the bottom Frame is the TextOut() widget that you pack after Mainframe():
if __name__=="__main__":
app = Mainframe()
text = TextOut(app) # This one
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
You'll have an easier time debugging if you give each widget a bg colour and then give them all some padding so you can easier identify which widget is inside which widget.

Python 3.5 tkinter confirmation box created progress bar and reads in csv not quite working

I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()

Why doesn't the program wait for the function result? [duplicate]

I have a tkinter class:
class DBCreatorWin():
def closeWindow(self):
tkMessageBox.showinfo("Ilmiont SQLite Database Manager", "This window cannot be closed.\nEnter a database name and press Continue.")
def returnName(self):
dbName = self.entry.get()
self.window.destroy()
return dbName
def __init__(self):
self.window = Toplevel()
self.window.transient(tkRoot)
self.window.grab_set()
self.window.resizable(width=False, height=False)
self.window.title("Ilmiont SQLite Database Manager")
self.window.protocol("WM_DELETE_WINDOW", self.closeWindow)
self.label = Label(self.window, text="Enter the name of the database to be created: ")
self.entry = Entry(self.window, width=30)
self.button = Button(self.window, text="Continue", command=self.returnName)
self.label.grid(row=0, column=0)
self.entry.grid(row=0, column=1)
self.button.grid(row=1, column=0, columnspan=2)
I want to create an instance of this class within my main code and wait for the return value. The user types a name into the entry field and presses the Continue button. At that point, the value should be returned to where the class was originally instantiated. How do I go about this? I can't seem to make it work in a normal way and am new to tkinter.
Thanks in advance,
Ilmiont
There are a couple of ways to do this. The basic idea is to use a tkinter method to wait for a specific event before returning. Tkinter provides two methods to do just that: wait_window and wait_variable. The most common method is to open a window and then wait for it to be destroyed. Some good examples can be found on the effbot site, on a page titled Dialog Windows.
Here's a simple illustration. It's not production-ready, but illustrates the general idea. At the very least you'll want to add a grab on the dialog so that you can't interact with the main window while the dialog is open, since you said you want the dialog to be modal.
import Tkinter as tk
class MyDialog(object):
def __init__(self, parent):
self.toplevel = tk.Toplevel(parent)
self.var = tk.StringVar()
label = tk.Label(self.toplevel, text="Pick something:")
om = tk.OptionMenu(self.toplevel, self.var, "one", "two","three")
button = tk.Button(self.toplevel, text="OK", command=self.toplevel.destroy)
label.pack(side="top", fill="x")
om.pack(side="top", fill="x")
button.pack()
def show(self):
self.toplevel.deiconify()
self.toplevel.wait_window()
value = self.var.get()
return value
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.button = tk.Button(self, text="Click me!", command=self.on_click)
self.label = tk.Label(self, width=80)
self.label.pack(side="top", fill="x")
self.button.pack(pady=20)
def on_click(self):
result = MyDialog(self).show()
self.label.configure(text="your result: %s" % result)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
You can't.
The whole way the tkinter works is with callbacks. The command that you're using is the callback and you'll have to use the value inside the class. Here is an example:
def do_stuf(self):
tkMessageBox.showinfo("Foo", returnName())
....................
self.button = Button(self.window, text="Continue", command=self.do_stuff)

Resources