I am building a relatively large application based on Tkinter. Whenever I make a code change, I have to reload the application manually, and then use the GUI to go back to the desired state.
For instance, if I had open an image in the app before changing the code, and I want to open it again, I have first to open the GUI, select the image from the drop-down list and so on... This is a waste of time. Is there a way to automatically reload the application/GUI after a code change in the last state it was?
This question has been asked a number of times
Proper way to reload a python module from the console
You can use reload(module) for this, but beware of nasty side effects. For example, existing code will be based on the original code, it will not magically get new attributes or baseclasses added.
Another great resource on this would be https://code.activestate.com/recipes/580707-reload-tkinter-application-like-a-browser/
After you've described you're problem a bit further in the comments, I was able to reconstruct the issue better on my end.
Here's my basic implementation for a solution to your issue:
from tkinter import *
import json
application_state = {"background": "white"}
background_colors = ["white", "red", "blue", "yellow", "green"]
def main():
root = Tk()
root.title("Test")
root.geometry("400x400")
reload_state(root)
for color in background_colors:
def change_background_wrapper(color=color, root=root):
change_background(color, root)
Button(root, text=color,command=change_background_wrapper).pack()
root.mainloop()
def change_background(color, window):
print("new color: " + color)
window.configure(bg=color)
application_state["background"] = color
update_config()
def reload_state(window):
config_file = open("config.json")
conf = json.load(config_file)
window.configure(bg=conf["background"])
def update_config():
with open("config.json", "w") as conf:
conf.write(json.dumps(application_state))
if __name__ == "__main__":
main()
In this instance, we're able to update the background color of the GUI and it will persist on each rerun of the script, until it gets manually changed again. You can apply this concept to pretty much any sort of state you have inside your application, I think this should get you started!
Related
I have a main program which does some cool stuff and I am currently setting up a 'settings editor' to let the user change some GUI related stuff and default values. It reads values from a text file, which is read in correctly and saves them to a dictionary self.propertiesDict. Some of the options are on/off switches, so I use checkbuttons for them. What is puzzling me is following behavior: the code works perfectly fine, when I execute the settingsEditor.py (the script creating the settings window) directly. All the checkbuttons are set to active / True. However, when I include my settingsEditor in my main program and call it, it creates fine but all the checkbuttons show the wrong value: False. I read a lot of topics here to find an answer, but I think I avoided the most common errors:
I use the tk variables
tk variables are created and set prior to the buttons
variables are not only in local scope (prefixed self.)
As you can see, I tried with an IntVar and a BooleanVar, but neither is working correctly. Something else is strange, when I use ttk.checkbuttons, I get the issue described here. I use Visual Studio for debugging and I can't see any difference in the process when going trough line by line, except for the wrong display result. I am happy for any suggestion. Sorry for not providing a full MWE, I will do, if nobody can help me from this here.
settingsEditor.py
import tkinter as tk
from tkinter import ttk
...
class mySettingsEditor:
def __init__(self):
...
def createGUI(self):
# Show main options on startup on/off
self.showOptionsVar = tk.IntVar()
self.showOptionsVar.set(str2int(self.propertiesDict['showMainOptionsExpanded']))
print(self.showOptionsVar.get())
self.checkBtn1 = tk.Checkbutton(Frame, text='Main Options Section', variable=self.showOptionsVar)
self.checkBtn1.grid(column=0,row=2)
# Show main STL section on startup on/off
self.showMainSTLVar = tk.BooleanVar()
self.showMainSTLVar.set(str2bool(self.propertiesDict['showMainSTLSectionExpanded']))
print(self.showMainSTLVar.get())
self.checkBtn2 = tk.Checkbutton(Frame, text='Main STL Section', variable=self.showMainSTLVar)
self.checkBtn2.grid(column=0,row=3)
main.py
from settingsEditor import mySettingsEditor
...
settEditor = mySettingsEditor()
This is how it looks in the GUI when executed separately (terminal with print output to the left):
Thats the result when I add it in main.py. The boxes are unchecked, but .get() tells me the values are correctly assigned to the tk variables.
As suggested by jasonharper, switching to Toplevel() for the child windows fixed the issue. Thanks alot!
("General Kenobi")
Working with the tkinter module of python, I'm trying to recreate a quite complex graphical interface from something existing in VB. Everything was okay until I decided to make things a little more nice and clean with an initialization function including global variables. Since, every single PanedWindow won't show.
This is my initialization function :
def init():
Tk().withdraw()
global DL_COM1_NAME
DL_COM1_NAME = StringVar()
And the main function with the graphical stuff :
def frame_Menu(init):
init()
# graphical user interface (GUI)
# main frame
main_frame = Tk()
...
DL_COM_Panel1 = PanedWindow(orient='horizontal')
DL_COM1 = Label(DL_COM_Panel1,bg='light slate blue',fg='white',font=('Arial','10','bold'),text='first :',anchor='w',relief='sunken',width=8)
DL_COM_Panel1.add(DL_COM1)
DL_COM1_Choice = Entry(DL_COM_Panel1,textvariable=DL_COM1_NAME,width=90)
DL_COM_Panel1.add(DL_COM1_Choice)
DL_COM1_Select = Button(DL_COM_Panel1,text='Browse',command=set_COM1filename)
DL_COM_Panel1.add(DL_COM1_Select)
DL_COM_Panel1.grid(row=7,column=0,columnspan=4,sticky='we',padx=(15,15),pady=(2.5,2.5))
...
main_frame.mainloop()
The set_COM1filename function I'm using in "command" asks for a file by askopenfilename.
The PanedWindow I defined here won't open, and it's the same for every other ones, while everything was working with local variables. Furthermore, when I use the command prompt to run this, the process doesn’t stop when I click the quit button of my interface. Do you have any idea of the reason ?
Thank you very much
We have a functioning program that uses Tkinter as its GUI. Everything works fine however different branches of the code are now using different hardware which realistically need different buttons. Hence we'd like to have the main GUI import modules representing the buttons depending on what hardware is being used.
I've cut out some of the code below, I'm interested in removing the makemenu() function to a separate module, hence when it is called in the Application __init__ (self.makemenu(master)) I would like to make that a reference to a separate module. I've tried doing this and am having trouble. Is this even possible?
I'm a little confused on the parent structure, what needs to be passed to my button module, etc.? I know this is a poorly constructed question but if anyone is able to advise if this is possible and put my on the right track that would be great. For example if someone could show how to modify this code to have the buttons defined in a separate module I could figure out how to do the same in my module.
# Import necessary libraries
import sys
import os
import Tkinter as tk
class Application(tk.Frame):
##################################################################
## Final functions are designed to initialize the GUI and
## connect various mouse movements to useful functions.
##################################################################
def definevars(self):
'''Original definition of all of the key variables that
we need to keep track of while running the GUI
'''
self.disable = True
self.savimgstatus = 'off'
self.mode = 'Standby'
self.status = 'Not Ready'
def makemenu(self,master):
''' Function to create the main menu bar across
the top of the GUI.
'''
self.menubar = tk.Menu(master)
## Motor Submenu
motormenu = tk.Menu(self.menubar,tearoff=1)
motormenu.add_command(label='ALT',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('alt'))
motormenu.add_separator()
motormenu.add_command(label='AZ',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('az'))
self.menubar.add_cascade(label='Tracker Motors',menu=motormenu)
## Set the big menu as the main menu bar.
master.config(menu=self.menubar)
def __init__(self,tcpconn,DOME,TRACKERSTAGE, master=None):
'''Main function to initialize the GUI. Will scale
the size of the GUI to fit any size screen... to a
point. It will not allow it to be smaller than
600x800.
'''
self.buf = 1024
## Check resolution of screen. Make GUI 2/3rds of size
## unless that means under 600x800.
fh = round(master.winfo_screenheight()*2./3.)
fw = round(master.winfo_screenwidth()*2./3.)
if fh < 600: fh = 600
if fw < 800: fw = 800
print 'GUI resolution set to {0} x {1}'.format(fw,fh)
self.fw = fw
self.fh = fh
self.imwidth = int(0.45*self.fw)
self.imheight = int(0.45*self.fh)
self.imcentx = self.imwidth/2
self.imcenty = self.imheight/2this
## Initialize Frame
tk.Frame.__init__(self, master, height=fh,width=fw)
self.grid()
self.grid_propagate(0)
## Initialize Various variables.
self.definevars()
## Create buttons, etc.
self.createWidgets()
self.makemenu(master)
self.disableall()
## Main Loop function
self.checkoutput()
###################################################################
# Initialize GUI window.
root = tk.Tk()
root.title('Hardware') # window title
app = Application(master=root)
app.mainloop() # go into the main program loop
sys.exit()
If you want to move makemenu to a separate module, that should be pretty simple. However, you'll need to change a few things.
Since makemenu no longer has a reference to self (or has a different reference, if you implement it as a separate class), you need to replace calls like command=lambda: self.geterror('alt')) to be command=lambda: master.geterror('alt')).
The other thing I recommend is to remove the call to add the menu to the root. I believe that modules shouldn't have side effects like this -- the function should make a menu and return it, and let the caller decide how to use it, ie:
self.menubar=makemenu(master)
master.configure(menu=self.menubar)
Roughly speaking, this is a variation of the MVC (model/view/controller) architectural pattern where the Application instance is your controller (and also part of the view unless you make modules of all your UI code). The menu is part of the view, and forwards UI functions to the controller for execution.
Your application then looks something like this:
from makemenu import makemenu
class Application(...):
def __init__(...):
...
self.menubar = makemenu(master)
master.config(menu=self.menubar)
...
I'm new to programming & new to python. I've just developed my first script, it prosesses file, but at the moment only from the commandline.
This is just a hobby to me so my job does not depend on it :-)
I've spent a few days now trying to get my head around python gui development & have come to the conclusion that I must be stupid.
I've looked at wxpython & Tkinter & do not understand either, although Tkinter seems to be the easier out of the two. I've even looked at wysiwyg tools like Boa Contrictor & wxglade. I do not even understand how to use those. I would prefer to just stick with my editor & code manually anyway.
My problem is this:
I would like to create a desktop window with either 1 or two objects, depending on what works best. If just one object then a text box of some sort, if 2 objects then a text box & an image.
I want to be able to drag file from a file manager & drop them on my script window, this is just to pass the filenames to my script.
I than want to redirect stdout to an object within my desktop window so that all script output appears within the desktop window.
I'm not sure if one object can do both things or not. If it can than just a text box would suffice, else drop files onto image & have redirected output going to the text box.
I have found drag & drop examples on the web but nothing which incorporates stdout redirect, & I've not been able to successfully modify any of the examples that I've come across.
If somee kind sole has the time to demonstrate how to achieve what I want & explain how its works I would greatfully appreciate it!
----EDIT ----
I've been playing around with 2 examples & have managed to hash the 2 together in order to get what I wanted working. Code is below. It's not cleaned up yet ( old comments etc... ), but it works.
#!/usr/bin/python
# The next two lines are not necessary if you installed TkDnd
# in a proper place.
import os
from Tkinter import *
os.environ['TKDND_LIBRARY'] = '/home/clinton/Python/tkdnd2.6/'
import Tkinter
from untested_tkdnd_wrapper import TkDND
class Redir(object):
# This is what we're using for the redirect, it needs a text box
def __init__(self, textbox):
self.textbox = textbox
self.textbox.config(state=NORMAL)
self.fileno = sys.stdout.fileno
def write(self, message):
# When you set this up as redirect it needs a write method as the
# stdin/out will be looking to write to somewhere!
self.textbox.insert(END, str(message))
root = Tkinter.Tk()
dnd = TkDND(root)
textbox = Tkinter.Text()
textbox.pack()
def handle(event):
event.widget.insert(END, event.data)
content = textbox.get("0.0",Tkinter.END)
filename = content.split()
dnd.bindtarget(textbox, handle, 'text/uri-list')
#Set up the redirect
stdre = Redir(textbox)
# Redirect stdout, stdout is where the standard messages are ouput
sys.stdout = stdre
# Redirect stderr, stderr is where the errors are printed too!
sys.stderr = stdre
# Print hello so we can see the redirect is working!
print "hello"
# Start the application mainloop
root.mainloop()
Examples are: python drag and drop explorer files to tkinter entry widget
And also the example provided kindly by Noelkd.
In order for this code to work you must create the wrapper from first example. Also currently code just displays dragged file in window, however variable is in place to be passed onto the script which runs behind the gui interface.
If you want to use Tkinter:
from Tkinter import *
import tkFileDialog
class Redir(object):
# This is what we're using for the redirect, it needs a text box
def __init__(self, textbox):
self.textbox = textbox
self.textbox.config(state=NORMAL)
self.fileno = sys.stdout.fileno
def write(self, message):
# When you set this up as redirect it needs a write method as the
# stdin/out will be looking to write to somewhere!
self.textbox.insert(END, str(message))
def askopenfilename():
""" Prints the selected files name """
# get filename, this is the bit that opens up the dialog box this will
# return a string of the file name you have clicked on.
filename = tkFileDialog.askopenfilename()
if filename:
# Will print the file name to the text box
print filename
if __name__ == '__main__':
# Make the root window
root = Tk()
# Make a button to get the file name
# The method the button executes is the askopenfilename from above
# You don't use askopenfilename() because you only want to bind the button
# to the function, then the button calls the function.
button = Button(root, text='GetFileName', command=askopenfilename)
# this puts the button at the top in the middle
button.grid(row=1, column=1)
# Make a scroll bar so we can follow the text if it goes off a single box
scrollbar = Scrollbar(root, orient=VERTICAL)
# This puts the scrollbar on the right handside
scrollbar.grid(row=2, column=3, sticky=N+S+E)
# Make a text box to hold the text
textbox = Text(root,font=("Helvetica",8),state=DISABLED, yscrollcommand=scrollbar.set, wrap=WORD)
# This puts the text box on the left hand side
textbox.grid(row=2, column=0, columnspan=3, sticky=N+S+W+E)
# Configure the scroll bar to stroll with the text box!
scrollbar.config(command=textbox.yview)
#Set up the redirect
stdre = Redir(textbox)
# Redirect stdout, stdout is where the standard messages are ouput
sys.stdout = stdre
# Redirect stderr, stderr is where the errors are printed too!
sys.stderr = stdre
# Print hello so we can see the redirect is working!
print "hello"
# Start the application mainloop
root.mainloop()
What this does is creates a window with a button and a text box, with stdout redirect.
Currently in Tkinter you can't drag and drop files on to the open tk window(you can if you use tkdnd), so I have included a different way of getting the path of a file.
The way I have included to select a file is the askopenfilename dialog from tkFileDialog, this opens up a file browser and the path file selected is returned as a string.
If you have any questions or this doesn't quite do what your looking for please leave a comment!
Have a look at GTK. It is a really powerful library. Not the simplest, that's a fact, but once you get to understand how things work, it becomes much easier.
Here's the official tutorial
If oyu are still using Python2, I think you should use PyGTK but it has been replaced by gl (which is described in the above tutorial). A good tutorial for PyGTK can be found here.
For a static interface, you can use glade which produces XML files that are then read by GTKBuilder to create the "real" interface. The first tutorial that I've found is available here
I'm writing an app that doesn't have a main window (it runs inside a Python interpreter in another app), and I thought I had a good solution for getting Tkinter to cooperate--I made the Tkinter.Tk class into a Borg.
class RootWindow(Tk):
""" Invisible window that serves as the default parent
of all others.
"""
groupDict = {}
def __init__(self):
self.__dict__ = self.groupDict
if self.__dict__ == {}: # first instance
Tk.__init__(self)
self.withdraw()
Then I have my Windows, which are subclassed from Tkinter.Toplevel, default to parent=RootWindow(). The result should be that I can create any number of Windows and they'll use the same root.
It works once fine for the first Window, but after that things get all messed up. :(
see pic
What am I doing wrong? Is this even a feasible solution?
Thanks
EDIT: I should add that even though there's other stuff running in the picture, the problem can be duplicated just by using RootWindow as the parent of a Tkinter.Toplevel.
EDIT: I overrode Window.mainloop so everything uses the RootWindow event loop.
def mainloop(self):
self.master.wait_window(self)
Then I create each visible window like this:
test = Window()
test.mainloop()
It seems to work because the windows do show up, but their contents are packed in an odd way that's hard to describe. It alternates between no contents at all and having everything squished horizontally and expanded vertically.
One problem appears to be that you are forgetting to start the event loop. Or, based on further edits of your question, you may be starting more than one main loop. You need exactly one main loop that is run from the root window.
Tkinter certainly allows an arbitrary number of top level windows, but your question doesn't appear to have enough details to show what is wrong unless my first guess is correct.