How to remember tkinter window position in python 3? - python

I have a tkinter gui and I would like it to retain the original window position and size upon relaunching.
Here is an answer that illustrates how to set a specific position and dimensions, but not a word about remembering the settings: How to specify where a Tkinter window opens?
Highly appreciate any help.

The only way to remember settings from session to session is to write them into a file. So, get the root window geometry (it's a string) and write it into a file. If you want the function to be executed automatically as a hook, bind it to the "<Configure>" event:
def save_size(event):
with open("myapp.conf", "w") as conf:
conf.write(root.geometry()) # Assuming root is the root window
root.bind("<Configure>",save_size)
You can later read the geometry from the file and restore it.

#Here I save the x and y position of the window to a file "myapp.conf"
#Here I see if the file exists.
if os.path.isfile("myapp.conf"):
#Here I read the X and Y positon of the window from when I last closed it.
with open("myapp.conf", "r") as conf:
root.geometry(conf.read())
else:
#Default window position.
root.geometry('610x270+0+0')
def on_close():
#custom close options, here's one example:
#close = messagebox.askokcancel("Close", "Would you like to close the program?")
#if close:
#Here I write the X Y position of the window to a file "myapp.conf"
with open("myapp.conf", "w") as conf:
conf.write(root.geometry())
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_close)

It took me pretty much time to get my head around actual implementation of this. So I wanted to share my final code. Based on DyZ suggestion.
I didn't use <Configure> event as suggested because only saving before quit is enough for me.
class Window(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# ...
# bla bla bla
# My GUI codes came here
# ...
# Try to load last saved window data
self.statusbar['text']="reading ini file..."
ini_file_path = "mapedit.ini"
try:
# if the file is there
# get geometry from file
ini_file = open(ini_file_path,'r')
self.geometry(ini_file.read())
self.statusbar['text']= "ini file read"
ini_file.close()
except:
# if the file is not there, create the file and use default
# then use default geometry.
self.statusbar['text']="ini file not found. New file created."
ini_file = open(ini_file_path, 'w')
ini_file.close()
self.geometry("640x400+100+200")
def client_exit(self):
self.save_geo()
self.destroy()
def save_geo(self):
# save current geometry to the ini file
try:
with open("mapedit.ini", 'w') as ini_file:
ini_file.write(self.geometry())
self.statusbar['text']="geo sv"
ini_file.close()
except:
statusbar['text']="ini file not found"
'''
This is where I created GUI instance
'''
if __name__ == "__main__":
win = Window()
# attach deletion handler to 'client_exit' method
win.protocol("WM_DELETE_WINDOW", win.client_exit)
win.mainloop()

Related

Tkinter filedialog clearing previous input

I just started programming with Python and I'm trying to create a GUI using tkinter where it would ask the user to select a zip file and file destination to send it to after extracting. What I have noticed is that when a user re-enters a destination, it would still store the previous file directory as well. How do I prevent this?
import tkinter as tk
from tkinter import filedialog
# Setting the window size
screenHeight = 450
screenWidth = 350
root = tk.Tk()
# get the location of the zip file
def input_dir():
input_filedir = filedialog.askopenfilename(initialdir='/', title='Select File',
filetypes=(("zip", "*.zip"), ("all files", "*.*")))
my_label.pack()
return input_filedir
# get location of destination for file
def output_dir():
output_filename = filedialog.askdirectory()
# Setting the canvas size to insert our frames
canvas = tk.Canvas(root, height=screenHeight, width=screenWidth)
canvas.pack()
# Setting the frame size to insert all our widgets
frame = tk.Frame(root, bg='#002060')
frame.place(relwidth=1, relheight=1)
# button to get user to chose file directory
openFile = tk.Button(frame, text='Choose the file path you want to extract', command=input_dir)
openFile.pack(side='top')
# button to get destination path
saveFile = tk.Button(frame, text="Chose the location to save the file", command=output_dir)
saveFile.pack(side='bottom')
extractButton = tk.Button(frame, text="Extract Now")
root.mainloop()
I have tried adding this line of code in the def input_dir function but it changed the positioning of the buttons. I'm still working on the code for extracting the zip.
for widget in frame.winfor_children():
if isinstance(widget, tk.Label):
widget.destroy()
The files/directories the user clicks are not saved really. Your variables input_filedir and output_filename are garbage collected when their respective functions are completed. If you are talking about how the dialog boxes go back to the most recent places opened in a dialog, that is not really something you can mess with too much. The easy answer is you can add the keyword 'initialdir' when you make the dialog to just pick where it goes like this:
output_filename = filedialog.askdirectory(initialdir='C:/This/sort/of/thing/')
The long answer is that filedialog.askdirectory actually creates an instance of filedialog.Directory and in that class, it saves that information for later in a method called _fixresult (but that method also does other things that are important.) You could overwrite this by doing something like this:
class MyDirectory(filedialog.Directory):
def _fixresult(self, widget, result):
"""
this is just a copy of filedialog.Directory._fixresult without
the part that saves the directory for next time
"""
if result:
# convert Tcl path objects to strings
try:
result = result.string
except AttributeError:
# it already is a string
pass
self.directory = result # compatibility
return result
def askdirectory (**options):
"Ask for a directory, and return the file name"
return MyDirectory(**options).show()
and then using your own askdirectory function instead of filedialog.askdirectory, but that is really convoluted so I recommend using initialdir instead if you can.
P.S. When a button is clicked, it calls the function you set after "command=" when you made the button; it seems you got that. But these functions are 'void', in that their return is just ignored. That is to say, the "return input_filedir" line does nothing.

Use variable outside of function or in other function?

Ive rewritten this for more context, in a logical order this is what i want the program to do
1 by pressing open file it needs to open a specified file and put it into the text widget (done)
2 by pressing the extract file it needs to extract something from a specified subtree(in an xml file)
3 export the extracted data to a text file
but lets go back to point nr 2 as i have not yet written the extractor(easy part) first i need to refference a file i want to edit, and that is where i run into my problem. inside extract it cant acess vars in openfile and i dont want to reopen the file again.
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import tkinter as tk
interface = tk.Tk()
interface.geometry("500x500")
interface.title("Text display")
def openfile():
filename = filedialog.askopenfilename()
print(filename)
file = open(filename)
txt = file.read()
print(txt)
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return txt
def extract():
print(txt)
button = ttk.Button(interface, text="Open text File", command=openfile) # <------
button.grid(column=1, row=1)
buttonex = ttk.Button(interface, text="Extract subtitles", command=extract) # <------
buttonex.grid(column=2, row=1)
interface.mainloop()
NameError: name 'txt' is not defined (when i press extract)
As the edit of the initial questions shows that a GUI is planned I would suggest to move your TK widgets into an interface class as in the tkinter documentation. Further, if you plan to do more complex manipulations you should make an own class to hold your data resp. manipulate it.
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
# Create your buttons and connect them to the methods
self.button_load = tk.Button(self)
self.button_load["command"] = self.loadTextFromFile()
self.button_load["text"] = "Load Data"
self.button_load.gird(column=1, row=1)
self.buttonex = tk.Button(self)
self.buttonex["text"] = "Extract subtitles"
self.buttonex["command"] = self.extract()
self.buttonex.grid(column=2, row=1)
# Create the text widget
self.text_widget = tk.Text(self, height=10, width=50)
self.text_widget.grid(column=1, row=2)
def loadTextFromFile(self):
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
file.close()
self.text_widget.insert(tk.END, txt)
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
def extract(self):
# Now you have access to self.text_widget and it holds your read in text
do_something(self.text_widget)
# Maybe at the following functions to make your file importable without directly executing it. Could come in handy later on.
def run():
# create the application
myapp = App()
#
# here are method calls to the window manager class
#
myapp.master.title("My Do-Nothing Application")
myapp.master.maxsize(1000, 400)
# start the program
myapp.mainloop()
if __name__ == '__main__':
run()
Have a look at the tkinter documentation for further examples: https://docs.python.org/3/library/tkinter.html
The additional if clause checks if your module is the main module and executes the run function. This defines an entry point if you directly run your module and prevents functions from execution at import time if you intend to import the module into another module. A more detailed explanation can be found here:
What does if __name__ == "__main__": do?
---------- Old Answer --------------
As pointed out in the comment you need to somehow return your local variables back to the calling function in order to be able to use them somewhere else. This could be achieved by a simple return statement:
def openfile():
""" It is the responsibility of the caller to close the return value """
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return file
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
would for example return the open fileid for further manipulation. E.g.:
def extract():
with openfile() as file:
txtex = file.read()
print (txtex)
If your goal is howeer, to manipulate the file content and read it later you would need to save your manipulations back into the file otherwise your second read would not see the changes. Dont forget to close your file again.
I changed three lines where I believe to have come across typos:
From: self.button_load["command"] = self.loadTextFromFile() to: self.button_load["command"] = self.loadTextFromFile
From: self.button_load.gird(column=1, row=1) to: self.button_load.grid(column=1, row=1)
From: self.buttonex["command"] = self.extract() to: self.buttonex["command"] = self.extract
Thank you for this example!

Why doesn't save() write to file first time?

I am making an text editor and I am working on the save/save-as buttons and have found something that I first thought worked but then found a problem with. The save and save as buttons in the menu don't save the text in the text widget to the file but just creates one. I have put in both functions self.f1.write(text) but only after a few clicks does the text actually save. It is not a time thing because I waited about five minutes and it still didn't work. I have a Mac on Yosemite.
Why doesn't it work?
Here is the script:
#modules
from Tkinter import *
from Tkinter import TclError
import tkFont
import tkMessageBox
import tkFileDialog
class Main(object):
def __init__(self, root):
root.title("PyText")
#menu for the file cascade
self.m1=Menu(root)
self.appmenu = Menu(self.m1, name="apple", tearoff=0)
self.m1.add_cascade(menu=self.appmenu)
self.appmenu.add_command(label="About PyText")
self.appmenu.add_separator()
self.fm=Menu(self.m1, tearoff=0)
self.fm.add_command(label="New", command=self.saveas)
self.fm.add_command(label="Open", accelerator="Cmd+O", command=self.open)
#these two don't work first time...
self.fm.add_command(label="Save", accelerator="Cmd+S", command=self.save)
self.fm.add_command(label="Save As", command=self.saveas)
self.fm.add_separator()
self.fm.add_command(label="Exit", command=root.quit)
self.m1.add_cascade(label="File", menu=self.fm)
root.config(menu=self.m1)
#Main text widget
self.t1=Text(root)
self.t1.config(width=90, height=40, undo=True, highlightbackground="black", cursor="ibeam")
self.t1.grid(row=1)
# Here is the problem.
# this command creates the file but does not
# save the text to the file.
def saveas(self):
text = self.t1.get(0.0, END)
self.savelocation=tkFileDialog.asksaveasfilename()
self.file=open(self.savelocation, "w+")
self.file.write(text)
# this also has
# the same problem. Once save as has
# been called, it does not save when pressed
# in first click but after a few clicks it
# finaly saves.
def save(self):
try:
text = self.t1.get(0.0, END)
self.f1=open(self.file, "w+")
self.f1.write(text)
except IOError:
text = self.t1.get(0.0, END)
self.f1=open(self.savelocation, "w+")
self.f1.write(text)
except Exception:
tkMessageBox.showinfo("Error", "Please save-as first.")
raise
#works fine!
def open(self):
self.file=tkFileDialog.askopenfilename()
self.OpenFile=file(self.file) # get a file handle
self.ReadFile= self.OpenFile.read() # read the file to variable
self.OpenFile.close() # close file handle
self.t1.delete(0.0, END)
self.t1.insert(END, self.ReadFile)
root = Tk()
app = Main(root)
root.mainloop()
Your code is saving data. However, you aren't closing the file and python buffers output, so the file on disk may not actually have any data until something causes the file to be closed (such as exiting the program normally).
The simple solution is to make sure you close the file after writing to it, and the easiest way to do that is by using open with the with statement.
def save(self):
try:
with open(self.file, "w+") as f1:
texts = self.t1.get("1.0", "end-1c")
f1.write(text)
...
In the above code, when the with block finishes, the file is guaranteed to be closed and the context flushed to disk.
Note: your use of the index 0.0 is incorrect. The index must be a string, and the first character is "1.0" not "0.0". Also, Tkinter always adds an extra newline to the text widget. If you want exactly what the user entered you need to get all but the very last character, which is what "end-1c" means (end, minus one character).

How can I save and load a text in a ComboBox with Python?

Hello Im writing a program in python and pyqt4 to control a amplifier.The program connects with a serial port(pyserial module). Now I want to modify my version that it can be used on other platforms and computers. I have already loaded and add a list with all serial ports to a ComboBox. Because its exhausting to choose and connect the port every time Im starting the program, I want the ComboBox to save the last chosen Port and connect to it. I'm new in Python and have no idea.How can I save and load the last String chosen in a ComboBox?
All I can think of is doing some File I/O.
Say, you have a file index.txt . You would need to store the index, so, each time the combo box is activated, you open the file in read mode, read the number inside, close the file, change the integer to the current item's index, open the file in write mode, write the new integer to it and close the file again. This way you always have the latest chosen item's index stored in a file.
Then, at startup, you open the file again and read the string inside. You set the combobox's current index to that of this string using .setCurrentIndex(). This will automatically connect to a combo box's currentIndexChanged() signal.
Here an example program:
import sys
from PyQt4 import QtGui, QtCore
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.initUI()
def initUI(self):
centralwidget = QtGui.QWidget()
self.combo = QtGui.QComboBox(self)
self.combo.addItem("Serial 1")
self.combo.addItem("Serial 2")
self.combo.addItem("Serial 3")
self.combo.addItem("Serial 4")
self.combo.addItem("Serial 5")
self.combo.currentIndexChanged[str].connect(self.Show)
f = open("index.txt","rt")
index = f.read()
f.close()
self.combo.setCurrentIndex(int(index))
grid = QtGui.QGridLayout()
grid.addWidget(self.combo,0,0)
centralwidget.setLayout(grid)
self.setGeometry(300,300,280,170)
self.setCentralWidget(centralwidget)
def Show(self, item):
print("Connected to: ",item)
f = open("index.txt","rt")
index = f.read()
f.close()
index = self.combo.currentIndex()
f = open("index.txt","wt")
f.write(str(index))
f.close()
def main():
app = QtGui.QApplication(sys.argv)
main= Main()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Note: For this to work you need to create an index.txt file with a number in it, in the same directory as your program is.

Python Tkinter Child Window Issue

Hello,
I'm struggling using multiple tkinter windows in python.
Basicly, I have two classes which are also related to two different windows.
The main class shows the main window (parentWindow) and the other class shows a secondWindow (childWindow).
The following code starts the MainWindow:
#START THE APPLICATION
root = Tkinter.Tk()
root.title ("GEMEINDESTECKBRIEF-Menü")
# My main Application
runGUI = MainWorkspaceConfig (root)
root.mainloop ()
So far there aren't any problems!
Now I'm trying to open a second Window calling a function in the Main Class (kind of onClickFunction to open the Window)
def opendirFactsheetHochwasserGebaeude (self) :
#validates the workspace resp. database directory and
#print self.checkFactsheet2.get()
#print self.inputSpace1.get()
try:
if self.checkFactsheet2.get()==1 :
if self.inputSpace1.get() or self.inputSpace2.get() != "":
#write workspace environment to __initFile__
if self.inputSpace1.get() != "":
self.writeWorkspEnv(self.inputSpace1.get())
#Copy file in seperate thread
start_new_thread(self.copyDefaultFactoWorkspace,())
if self.inputSpace2.get() != "":
self.writeWorkspEnv(self.inputSpace2.get())
# !!!!!!! START SECOND WINDOW !!!!!
facthwgeb = Tkinter.Tk()
facthwgeb.title ("Factsheet Hochwasser-Gebäude")
runGUI = Factsheet_hochwassergebaeude (facthwgeb)
facthwgeb.mainloop ()
#facthwgeb.protocol('WM_DELETE_WINDOW', runGUI.closeFactsheetHochwGeb)
else:
#self.inputSpace1.get() and self.inputSpace2.get () =="":
tkMessageBox.showwarning ("Keine Arbeitsumgebung festgelegt", "Bitte entweder einen neuen Workspace anlegen oder eine bestehende Datenbank auswählen!")
self.selectBox1.deselect()
Still everything works just fine!! The window opens as expected and also the GUI-widgets are displayed and useable.
After finishing the given tasks the Window has to be closed and HERE ALL TROUBLE STARTS!!!
To quit the Window I'm using a button with a command function which looks like this:
def closeFactsheetHochwGeb (self):
try:
if self.inputSpace1.get() and self.inputSpace2.get() != "":
with open('%s/__initFile__.txt'%os.path.dirname(os.path.realpath(__file__)), 'r') as file:
# read a list of lines into data
data = file.readlines()
data[13] = self.inputSpace1.get()+"\n"
data[14] = self.inputSpace2.get()+"\n"
# and write everything back
with open('%s/__initFile__.txt'%os.path.dirname(os.path.realpath(__file__)), 'w') as file:
file.writelines( data )
file.close()
# self.tkinterFrame.destroy()
self.tkinterFrame.quit()
The self.tkinterFrame.quit() closes not just the secondWindow (childWindow) it also closes the MainWindow (parentWindow) too. The self.tkinterFrame.destroy() function clears all widget from the window but the window still is active and visible!!
So, any Ideas how to solve the problem?
Would be thankful for any solutions!!!!!
Don't make a second Tk() instance; you can/should have only one root.
Use a Toplevel widget for facthwgeb instead. Also, get rid of the facthwgeb.mainloop() call, as again, there should only be one call to this.
YEESSS, Finally I found the solution to my issue!!!!
First Step: In the Main Class which starts the ChildWindow I changed the code in the function def opendirFactsheetHochwasserGebaeude (self) : from Tkinter.Tk() to Tkinter.Toplevel(parent) => the parent references the root Window. After changing the Tkinter typ the facthwgeb.mainloop()was also cleared because it is provided by the MainWindow (Parent)
Second Step: In the Second Class which implements the ChildWindow the function def closeFactsheetHochwGeb (self):did privously owned the commands self.tkinterFrame.destroy()which cleared the widget of the frame but not the window itself and the self.tkinterFrame.quit()closes the MainWindow and the ChildWindow => so both commands are useless!!
Final Step: The final resolution is to change the self.tkinterFrame.destroy ()to self.tkinterFrame.master.destroy() !!
Sometimes complex things can be very simple!! :-)
Try this self.Frame1.destroy()
Or whatever your frame name maybe
Sometimes you could have this
self.Frame1 = tk.Frame(top)

Categories

Resources