I have built a Python tkinter GUI application which is an application for running different tasks. The application window is divided into 2 halves horizontally, first half shows the options the user can choose for the selected menu option and second half shows the progress of the task by showing the log messages. Each task has a separate menu option, the user selects the menu option and first half is refreshed with user option along with a Submit button.
The GUI is built using the object oriented method where each task in the menu option is an class method of the GUI object.
I now have about 5-6 menu options and working fine but the code size is becoming huge and it is becoming hard to debug any issue or add new features.
Is there any way to write the method of a class in separate file which can be called from within the main class. The logging of messages in the GUI is written in the main class so if the method is written in a separate file the how will the log messages written in the other file appear in the main window.
Please suggest alternatives.
This might not help you completely, but this is what I use. I divide my tkinter code into 2 files. First gui.py contains the GUI components (widgets) and the second methods.py contains the methods.
Both the files should be in same directory.
Here is an example of a simple app that changes the label on a button click. The method change() is stored in a different file.
gui.py
from tkinter import *
from tkinter import ttk
from methods import change #Using absolute import instead of wildcard imports
class ClassNameGoesHere:
def __init__(self,app):
self.testbtn = ttk.Button(app,text="Test",command = lambda: change(self))
#calling the change method.
self.testbtn.grid(row=0,column=0,padx=10,pady=10)
self.testlabel = ttk.Label(app,text="Before Button Click")
self.testlabel.grid(row=1,column=0,padx=10,pady=10)
def main():
root = Tk()
root.title("Title Goes Here")
obj = ClassNameGoesHere(root)
root.mainloop()
if __name__ == "__main__":
main()
methods.py
from tkinter import *
from tkinter import ttk
def change(self):
self.testlabel.config(text="After Button Click")
My GUI app has two files: gui.py that contains all the Tkinter objects and controller.py contains the logic. The logic is one main function def automation(): that nests several other functions. The app is very simple it's only one button that calls automation().
I would like to add the print statements and errors that appear in the terminal in the GUI widget so that the user can see what's going on. I can't find a way to do that for imported modules.
gui.py
import tkinter as tk
from tkinter import *
from controller import automation
root = tk.Tk()
frame_button = tk.Frame(root)
button = Button(frame_button, text="Ship", command=lambda:automation())
lower_frame = tk.Frame(root)
terminal = tk.Label(lower_frame)
frame_button.place()
button.place()
lower_frame.place()
terminal.place()
controller.py
def automation():
def folder_cleaner():
print('Folders cleaned')
def dispatch():
print('Dispatch done')
def ship():
print('Shipment successful')
def process():
folder_cleaner()
dispatch()
ship()
process()
This is very simplified but each function has many different kinds of outputs. How can I redirect all of them to gui.py and inside the terminal widget?
To show the output in the UI, I've added an output function in gui.py. So the function can be used in controller.py, I've added a parameter outputFunc when calling automation. This can then be used instead of print to display the strings in the UI.
gui.py
import tkinter as tk
from tkinter import *
from controller import automation
def output(text):
terminal.insert("end", text + "\n")
root = tk.Tk()
frame_button = tk.Frame(root)
button = Button(frame_button, text="Ship", command=lambda:automation(output))
lower_frame = tk.Frame(root)
#Changed to text widget as it is more ideal for this purpose
terminal = tk.Text(lower_frame)
#Changed place to pack so widgets actually display
frame_button.pack()
button.pack()
lower_frame.pack()
terminal.pack()
controller.py
def automation(outputFunc):
def folder_cleaner():
outputFunc('Folders cleaned')
def dispatch():
outputFunc('Dispatch done')
def ship():
outputFunc('Shipment successful')
def process():
folder_cleaner()
dispatch()
ship()
process()
Here is one approach to Your issue (this works especially well if Your automation runs in a loop or basically is not as fast, it will make sure to update when any change happens):
from tkinter import Tk, Button, Label, Frame
from _thread import start_new_thread
from time import sleep
values = {'random_values': []}
def automation():
for i in range(10):
values['random_values'].append(i)
sleep(3)
def watch_values():
compare = None
while True:
value = str(values['random_values'])
if compare != value:
label.config(text=values['random_values'])
compare = value
root = Tk()
Button(root, text='start', command=lambda: start_new_thread(automation, ())).pack()
label = Label(root)
label.pack()
start_new_thread(watch_values, ())
root.mainloop()
So basically first it starts a thread that watches the values dictionary (it is important for it to interact with tkinter only when there are changes tho because otherwise it will cause issues with tkinter GUI so that is why there is compare).
Whenever there is a change in that particular variable it sets the text of the label as that variable (variable being values['random_values']).
Then there is the automation function. I suggest that You put everything it returns in a dictionary that will be watched by the other thread. You also need to thread Your automation function so that it can work in parallel. (And You can obviously import that function from another file, just add it to the threads, also it may seem weird but those empty tuples there are necessary since that is an argument (in those tuples it is also possible to put the function argument if You have those but in this case there are none))
And You only need one watcher function, just add more compares and checks to that loop and so on (mentioned in case You wanted to create such a function for every key in the dictionary which is completely unnecessary)
I'm working on a database GUI in tkinter but whenever I try to nest some functions inside one another it always makes unpredictable problems, So I'd like to ask if it's possible to make a button run a function that checks for a condition and if it's true it runs another script.py file that opens another window. Is that possible
I've already tried to press them into one file but weird problems appear and the file is too big to post here so I'm looking for a simpler solution
I'm a beginner so I'm not a hundred percent certain but I think it would look something like this
from tkinter import *
if name.get() == user_name AND pword.get() == password:
r = Tk()
my_btn = Button(r, text= "submit",command = open_py)
my_btn.grid(row=0,column=0)
r.mainloop()
Is this kind of thing possible or not.
How would "open_py():" look like
You can move the code of the new window to a different python file and import it.
For example:
import tkinter as tk
def open_dialog():
root = tk.Tk()
button = tk.Text(root, text="Hello!")
button.pack(root)
root.mainloop()
if __name__ == "__main__":
open_dialog()
in hello_dialog.py
import tkinter as tk
from hello_dialog import open_dialog
def main():
root = tk.Tk()
button = tk.Button(root, text="Start!", command=open_dialog)
button.pack(root)
root.mainloop()
in main.py
Both files need to be in the same folder. You can run main.py and it will run just fine even though the code for the Button showing "Hello!" is in a different file. All python files are libraries that you can import functions, classes and variables from. By adding if __name__ == "__main__" you can test whether your function was started directly or if it was imported by another program. To learn more about __name__ and importing other python files take a look at What does if __name__ == "__main__": do?.
as I say in the title, when I click on a button (analyse) another windows open and I don't want it. The problems is, in the analyse function, the first line is an import of my tkinter file.
Thanks in advance for any help.
I tried to delete the import and the second windows does not pop up, so I am pretty sure it is the problem. Moreover I need to do this import in the analyse function because I already import the other module in my tkinter file
tkinter file :
import fileb
def analyser():
output=fileb.analyse(name)
fenetre = Tk()
fenetre.geometry("800x500")
label = Label(fenetre, text='Emotion Video')
label.pack()
boutonanalyse=Button(fenetre, text='analyze', command=analyser)
boutonanalyse.pack(side=BOTTOM)
fileb :
def analyse(name):
import tkinter_essais
When you import your Tkinter file, you are running that file. This means that the code is run twice and so you have two windows opened up. A way to bypass this is by putting your tkinter setup into a function, and having that run if it is the main program only using something like this:
import fileb
def analyser():
output=fileb.analyse(name)
def tkSetup():
fenetre = Tk()
fenetre.geometry("800x500")
label = Label(fenetre, text='Emotion Video')
label.pack()
boutonanalyse=Button(fenetre, text='analyze', command=analyser)
boutonanalyse.pack(side=BOTTOM)
if "__name__" == "__main__":
tkSetup()
The if name == main checks if the program is being run originally (best way I can think to describe it) and so it wont be run if you import the file.
I'm new to programming, Python, this website, and actually using these kinds of websites in general, so hear me out.
I've been writing a module for a larger program using the tkinter module and ttk module, and when I import my own module into the main program, for some reason none of the ttk stuff works as it should. I mean, it appears, but the style I've written for it (s=ttk.Style(); s.configure...etc.) doesn't change it in anyway. When I run the module on its own, everything works fine. When it's imported into the main program, it just doesn't.
Not only this, but when using entry boxes, I've only just discovered that the way I'd been told to use them, with, for example, var=StringVar() as the textvariable (which again works fine when the module is run on its own), now just leaves the variable var as empty when var.get() is called. Now I've sorted this by just removing all mention of StringVar() (wish I'd known how redundant these really are), but I'd still like to know why importing them in to the main program causes them to malfunction so badly. I would give you some sample code but there's so much I'd struggle to be selective enough...
I'd appreciate any guidance you can offer.
EDIT: Would giving you something like this have helped?
stackoverflowmodule.py
import sys
from tkinter import *
from tkinter import ttk
import time
from random import randint, choice
class Decimals():
def Question1(self):
DECFrame.destroy()
frame1=ttk.Frame(DECmaster, height=height, width=width, style="NewFrame.TFrame")
frame1.pack()
Q1Label=ttk.Label(frame1, text="Question 1:", style="TitleLabel.TLabel")
Q1Label.grid(column=0, row=0, pady=(50,0))
answer=StringVar()
entry1=ttk.Entry(frame1, textvariable=answer)
entry1.grid(column=0, row=1, pady=(200,0))
# Typing in Hello should give a correct answer.
def Question1Attempt():
attempt=answer.get()
if attempt!="Hello":
print("Incorrect")
else:
print("Correct")
button=ttk.Button(frame1, text="Ok", command=Question1Attempt)
button.grid(column=0, row=2, pady=(30,0))
def Start():
global DECmaster
global s
global DECFrame
global DEC
global width
global height
DECmaster = Tk()
width=str(1000)
height=str(800)
x1=str(0)
y1=str(0)
DECmaster.geometry(width+"x"+height+"+"+x1+"+"+y1)
DECmaster.configure(bg="#8afff0")
s=ttk.Style()
s.configure("NewFrame.TFrame", background="#8afff0")
s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")
DECFrame=ttk.Frame(DECmaster, style="NewFrame.TFrame")
DECFrame.pack()
TitleLabel=ttk.Label(DECFrame, text="Test for Decimals", style="TitleLabel.TLabel")
TitleLabel.grid(column=1, row=0, pady=(50,0), sticky=N)
DEC=Decimals()
button=ttk.Button(DECFrame, text="Start", command=DEC.Question1)
button.grid(column=2, row=2, pady=(200,0), sticky=N)
DECmaster.mainloop()
stackoverflowprogram.py
from tkinter import *
from tkinter import ttk
import time
import stackoverflowmodule
root = Tk()
width=str(1000)
height=str(800)
x1=str(0)
y1=str(0)
##width=str(1228)
##height=str(690)
##x1=str(-1)
##y1=str(-22)
root.geometry(width+"x"+height+"+"+x1+"+"+y1)
root.configure(bg="#8afff0")
s=ttk.Style()
s.configure("NewFrame.TFrame", background="#8afff0")
s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")
Testframe=ttk.Frame(root, height=height, width=width, style="NewFrame.TFrame")
Testframe.pack()
Titlelabel=ttk.Label(Testframe, text="Start Test:", style="TitleLabel.TLabel")
Titlelabel.grid(column=0, row=0, pady=(50,0))
def StartTest():
stackoverflowmodule.Start()
button=ttk.Button(Testframe, text="Start", command=StartTest)
button.grid(column=0, row=1, pady=(100,0))
root.mainloop()
I realise there's an awful lot there, but I couldn't really demonstrate my point without it all. Thanks again.
The root of your problem is that you're creating more than once instance of Tk. A Tkinter app can only have a single instance of of the Tk class, and you must call mainloop exactly once. If you need additional windows you should create instances of Toplevel (http://effbot.org/tkinterbook/toplevel.htm).
If you want to create modules with reusable code, have your modules create subclasses of Frame (or Toplevel if you're creating dialos). Then, your main script will create an instance of Tk, and place these frames in the main window or in subwindows.
If you want to sometimes use your module as a reusable component and sometimes as a runnable program, put the "runnable program" part inside a special if statement:
# module1.py
import Tkinter as tk
class Module1(tk.Frame):
def __init__(self, *args, **kwargs):
label = tk.Label(self, text="I am module 1")
label.pack(side="top", fill="both", expand=True)
# this code will not run if this module is imported
if __name__ == "__main__":
root = tk.Tk()
m1 = Module1(root)
m1.pack(side="top", fill="both", expand=True)
In the above code, if you run it like python module1.py, the code in that final if statement will run. It will create a root window, create an instance of your frame, and make that frame fill the main window.
If, however, you import the above code into another program, the code in the if statement will not run, so you don't get more than one instance of Tk.
Let's assume you have two modules like the above, and want to write a program that uses them, and each should go in a separate window. You can do that by writing a third script that uses them both:
# main.py
import Tkinter as tk
from module1 import Module1
from module2 import Module2
# create the main window; every Tkinter app needs
# exactly one instance of this class
root = tk.Tk()
m1 = Module1(root)
m1.pack(side="top", fill="both", expand=True)
# create a second window
second = tk.Toplevel(root)
m2 = Module2(second)
m2.pack(side="top", fill="both", expand=True)
# run the event loop
root.mainloop()
With the above, you have code in two modules that can be used in three ways: as standalone programs, as separate frames within a single window, or as separate frames within separate windows.
You can't create two instances of tkinter.Tk. If you do, one of two things will happen.
Most of the code in the script may just not run, because it's waiting for the module's mainloop to finish, which doesn't happen until you quit.
If you structure things differently, you'll end up with two Tk instances, only one of which is actually running. Some of the code in your script will happen to find the right Tk instance (or the right actual Tk objects under the covers), because there's a lot of shared global stuff that just assumes there's one Tk "somewhere or other" and manages to find. But other code will find the wrong one, and just have no effect. Or, occasionally, things will have the wrong effect, or cause a crash, or who knows what.
You need to put the top-level application in one place, either the module or the script that uses it, and have the other place access it from there.
One way to do this is to write the module in such a way that its code can be called with a Tk instance. Then, use the __main__ trick so that, if you run the module directly as a script (rather than importing it from another script), it creates a Tk instance and calls that code. Here's a really simple example.
tkmodule.py:
from tkinter import *
def say_hi():
print("Hello, world!")
def create_interface(window):
hi = Button(window, text='Hello', command=say_hi)
hi.pack()
if __name__ == '__main__':
root = Tk()
create_interface(root)
root.mainloop()
tkscript.py:
from tkinter import *
import tkmodule
i = 0
def count():
global i
i += 1
print(i)
def create_interface(window):
countbtn = Button(window, text='Count', command=count)
countbtn.pack()
root = Tk()
create_interface(root)
window = Toplevel(root)
tkmodule.create_interface(window)
root.mainloop()
Now, when you run tkscript.py, it owns one Tk instance, and passes it to its own create_frame and to tkmodule.create_frame. But if you just run tkmodule.py, it owns a Tk instance, which it passes to its own create_frame. Either way, there's exactly one Tk instance, and one main loop, and everyone gets to use it.
Notice that if you want two top-level windows, you have to explicitly create a Toplevel somewhere. (And you don't want to always create one in tkmodule.py, or when you run the module itself, it'll create a new window and leave the default window sitting around empty.)
Of course an even simpler way to do this is to put all of your GUI stuff into modules that never create their own Tk instance, and write scripts that import the appropriate modules and drive them.