Tkinter windows popping up and not able to close them - python

I have a tkinter GUI python code that creates a gui interface to my code, in the code later snack sound toolkit is used (which also uses Tk and creates an instance using root = Tk()). As, mainloop of the previously GUI application is already running to everytime snack function is called a new empty default tk window pops up. As this happens quite a lot, there are hundreds of empty tk windows on screen when this code executes. I have tried to close them using numerous methods root.destroy,root.withdraw, WM_DELETE_WINDOW etc. but to no solution.
Is there any way this can be done in tkinter?
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1 = thread.start_new_thread(run, (PATH_TO_WAVE_FILE,))
def run(path):
for k in range(10):
PITCH_VALUES = snack_work(path)
print PITCH_VALUES
def snack_work(INPUT_WAVE_FILE):
# initializing the snack tool
root = tk.Tk()
tkSnack.initializeSnack(root)
# root.withdraw()
mysound = tkSnack.Sound()
# processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES = mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()

Make run() and snack_work() into instance methods of your app object, so that they can easily access that object's attributes. To work with a more minimal MCVE that doesn't rely on external libraries or files, I tested the following with simple print() (I'm on Python 3) and after() calls rather than snack stuff, as all I wanted to check was that the other functions could access a tkinter object.
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1=thread.start_new_thread(self.run,(PATH_TO_WAVE_FILE,))
def run(self, path):
for k in range(10):
PITCH_VALUES = self.snack_work(path)
print PITCH_VALUES
def snack_work(self, INPUT_WAVE_FILE):
## initializing the snack tool
tkSnack.initializeSnack(self) # use self instead of the separate root object
# self.withdraw()
mysound=tkSnack.Sound()
## processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES= mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()

Related

Python/Tkinter: need code to define / import a widget as a class outside root window

I have been wrestling for a very long time with the issue of creating a Tkinter gui in modular fashion using classes. While there are many examples on this site - and believe me, I have read them all - they have all been too complex for me to understand. In particular, I could not work out how the imported modules could 'talk to' functions in the main application. I have finally had a eureka moment - I created a class in a module that defines a root window and a button. Then, I wrote a main python file that imports the root window / button module, and tests interactions between the imported module and the main app. All of those tests were successful, which is huge progress for me, as far as it goes. Here is the module code, saved as 'fmod.py':
import tkinter as tk
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = tk.Button(self, text='Click Me')
self.button.pack()
Here is the python file written to import the module, and test all the interactions I was interested in understanding:
import tkinter as tk
# import module
import fmod
# create gui root window as an instance of imported MainWindow class
MainWin = fmod.MainWindow()
# define a local function for testing purposes
def ButtonClicked():
print("Button clicked")
# alter attributes of imported root/button from within this file
MainWin.geometry("700x400")
MainWin.config(bg = "yellow")
MainWin.button.config(bg="lightblue")
# add a new widget to root from within this file
NewLabel = tk.Label(MainWin,text="Label")
NewLabel.pack()
# connect an imported widget to the above local function
MainWin.button.config(command=ButtonClicked)
# mainloop
MainWin.mainloop()
As mentioned, all of the tests in the above code worked successfully. The question is this: I would rather have the MainWindow class define the root window and nothing else. So rather than including the button in that code, I'd like to write another entirely separate class that simply defines a button, which could be imported into my app separately. Would anyone be kind enough to help me write that code? I tried copying code from the MainWindow class, and it worked, but it opened an entirely new window (probably because of the init / super init code, which I do not fully understand, and don't really need to understand at the moment - it works, and I'm fine with that). I want code for a simple button that I could import into the main app, as a widget, and that I could place in the MainWin window inside the app.
You can define the class in a separate module and then import it, but you will still want to initiate the button inside of your main window like you are doing now. After all the button does belong on the window.
To create a button class it would be similar to how you created the MainWindow...
import tkinter as tk
class MyButton(tk.Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
... do something
def buttonClicked(self, *args):
print("Button Clicked")
Then you could just leave you mainwindow the way it is except switch out the class for your class. I also suggest moving a lot of your logic that is in the global scope inside of your MainWindow, and minimizing your use of the global scope as much as possible. For example:
import tkinter as tk
from mybuttonmodule import MyButton
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = MyButton(self, text='Click Me')
self.button.pack()
self.button.config(command=self.button.buttonClicked)
self.newlabel = tk.Label(self, text="Label")
self.newlabel.pack()
self.geometry("700x400")
self.config(bg = "yellow")
self.button.config(bg="lightblue")
if __name__ == "__main__":
window = MainWindow()
window.mainloop()

tkinter Toplevel window won't open from system tray menu

I'm trying to open a Toplevel window with tkinter, from a system tray menu.
from cmath import phase
from tkinter import *
from tkinter import messagebox, messagebox
from tracemalloc import start
from pystray import MenuItem as item
import pystray
from PIL import ImageTk,Image
import pickle
def quit_window(icon, item):
icon.stop()
root.destroy()
exit()
def hidden():
global my_img1
top=Toplevel()
top.title("Secret menu, shhh :^)")
top.overrideredirect(True)
top.attributes('-alpha', 0.9)
w = 1100
h = 450
ws = top.winfo_screenwidth()
hs = top.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/3) - (h/2)
top.geometry('%dx%d+%d+%d' % (w, h, x, y))
top.iconbitmap('screen.ico')
my_img1 = ImageTk.PhotoImage(Image.open("ITEXTRA.png"))
label1=Label(top,image=my_img1).place(relx=0.01,rely=0.01)
button2=Button(top,text="Close window",bg='#ff4a65',command=top.destroy, relief=GROOVE).place(relx=0.9,rely=0.9)
# Marks window as used
hiddenwindow=1
pickle.dump(hiddenwindow, open("window.dat", "wb"))
Button(root, text="Developer Options", padx=57, bg="#86b3b3",fg="black", command = hidden).grid(row=3,column=0)
def hide_window():
root.withdraw()
image=Image.open("screen.ico")
menu=(item('Dev window', hidden),item('show window', show_window),item('Exit app', quit_window))
icon=pystray.Icon("ITExtra", image, "Program", menu)
icon.run()
def show_window(icon, item):
icon.stop()
root.after(0,root.deiconify())
root.after(0,root.focus_force)
root = Tk()
root.title("ITextra")
root.geometry("400x400")
root.protocol('WM_DELETE_WINDOW', hide_window)
hidden()
root.mainloop()
But this unfortunately will not work, it won't pull up the toplevel window, nor the main one.
If I then open the root window myself, the toplevel window will open, but be unresponsive.
EDIT
Alright, so I tried adding the topwindow as class, but I keep getting error 'Top' object has no attribute 'tk'.
I pasted the updated code below. Any help is always greatly appreciated!
from cmath import phase
from tkinter import *
from tkinter import messagebox, messagebox
from tracemalloc import start
from pystray import MenuItem as item
import pystray
from PIL import ImageTk,Image
import pickle
class Top():
def __init__(self,master=None):
self.hide = True
def hidden(self):
if self.hide:
global my_img1
self.top=Toplevel(root)
self.top.title("Secret menu, shhh :^)")
self.top.attributes('-alpha', 0.9)
w = 1100
h = 450
ws = self.top.winfo_screenwidth()
hs = self.top.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/3) - (h/2)
self.top.geometry('%dx%d+%d+%d' % (w, h, x, y))
self.top.iconbitmap('screen.ico')
my_img1 = ImageTk.PhotoImage(Image.open("ITEXTRA.png"))
label1=Label(self.top,image=my_img1).place(relx=0.01,rely=0.01)
button2=Button(self.top,text="Close window",bg='#ff4a65',command=self.top.destroy, relief=GROOVE).place(relx=0.9,rely=0.9)
# Marks window as used
hiddenwindow=1
pickle.dump(hiddenwindow, open("window.dat", "wb"))
self.top.mainloop()
def somewhereelse():
top.hide = True
top.hidden()
def quit_window(icon, item):
icon.stop()
root.destroy()
exit()
def show_window(icon, item):
icon.stop()
root.after(0,root.deiconify())
root.after(0,root.focus_force)
def hide_window():
root.withdraw()
image=Image.open("screen.ico")
try:
if pickle.load(open("window.dat","rb")) ==1:
menu=(item('Dev window', top.hidden),
item('show window', show_window),
item('Exit app', quit_window))
else:
menu=(item('Exit app', quit_window))
except:
menu=(item('Exit app', quit_window))
icon=pystray.Icon("ITextra", image, "Program", menu)
icon.run()
root = Tk()
root.title("ITextra")
root.geometry("400x400")
top = Top(root) #in main part
root.protocol('WM_DELETE_WINDOW', hide_window)
Button(root, text="Developer Options", padx=57, bg="#86b3b3",fg="black", command =top.hidden).grid(row=3,column=0)
root.mainloop()
Top window still unresponsive
It's not when root is open, but when top is open by itself, again it remains unresponsive. It responds however when I click a button, and drag my mouse. I tried adding a mainloop in top, but neither a self.top.mainloop nor a root.mainloop will work.
I tried using binds, but they also showed the same behaviour.
Am I creating something that won't work?
The app I'm creating is multithreaded, and my question is; would this complicate things with other classes? I am very new to coding, and quite frankly don't know.
I have the whole project in a pastebin here, for anyone who's interested. I think it's quite a mess, but I'm still pretty proud of it for a beginner.
The Toplevel() remains unresponsive because it has no event loop attached (mainloop()) because in this code the Toplevel acts as a standalone main window.
Need to attach this Toplevel to the root - top = Toplevel(root) where root is passed as argument to hidden(root). This way the root event loop works for all widget children such as a Toplevel. This would help towards your main part of the question.
(#added...) so there is no need for top.mainloop() because now that the root is the master/parent top is inside root.mainloop().
The event loop is for checking in to any events that happen on your widget which you would normally program with bind(). eg
top.bind('<Button>',dosomething) where dosomething is a defined function.
(...#added)
If you want a title for top then you need to create your own title baror label if you are using overrideredirect(True) because this removes the platform window manager.
(#added...)
The platform window manager is not so much removed as it is not being used when using overrideredirect(True). This is probably another reason why your window seems unresponsive with this stage of code. Need to code for events attached to the widget yourself - as you have done with the Button widget to close.
(...#added)
For main part of question:
there is nothing that refers to top widget in show_window in this code.
(#added...)
could look at making top a class and instantiate that in the root. The default status of hidden for top could be an attribute of this class. Then you can change the class attribute to hide or show functionally inside the body of the code somewhereelse.
eg skeleton sketch:
class Top():
def __init__(self,master=None):
...
self.hide = True
...
def hidden(self):
if self.hide:
...
def somewhereelse():
top.hide = true
top.hidden()
top = Top(root) #in main part
!!! obviously very brief general idea that needs work here of a way to maintain your design which seems quite good to me. There are several ways to incorporate the Toplevel widget into the class but that digresses a bit from the original question.
(...#added)
added 28Jan...
I recommend to study class more thoroughly rather than only putting in my example. But here is a bit more
class Top():
def __init__(self,master=None):
super().__init__()
self.master = master
self.hide = True
def hidden(self):
...
self.top = Toplevel(self.master)
...
In my words, but please check Python docs, super().__init__() will call the initialising function of the inherited object which in this case goes back to self.master which is root and then back through to tk.__init__ which is called in Tk().
I recommend looking at the code __init__.py file in the Lib\tkinter\ folder in the Python download to get a good understanding of how tkinter works.
I think this is definitely achievable but might need a different GUI - agree it is an excellent start for a beginner and thus not really a mess!!
Using class is not essential to achieving what you want to do but classes are very useful for encapsulating an object so that any extra attributes and methods relevant to that object can be customised for your project. This makes further or future development easier.
...added 28Jan

Is there some way I can pass information to a tkinter application after calling .mainloop()?

This is a very simple version of my application. Basically I have a Tkinter application I want to start from another python application, but I do not have the option to have tkinter as my main window.
So my question is, if it's possible to pass some information from my main class to my tkinter application. Not once, but repeatedly.
I tried something like this, but this does not work, probably because of the .mainloop() method.
Right now I just try to print the variable "labelContent" whenever the button is pressed.
Is there some way I can pass information to my tkinter application after calling .mainloop() (besides using external files like .txt because this would obviously work)?
gui.py
import tkinter
class BasicWindow:
def __init__(self):
tk = tkinter.Tk()
label = tkinter.Label(tk, text="Hello World!")
label.pack()
self.labelContent = ""
button = tkinter.Button(tk,text="OK",command=self.buttonMethod)
button.pack(side="bottom")
tk.mainloop()
def buttonMethod(self):
print(self.labelContent)
main.py
from gui import BasicWindow
import time
class Main:
def __init__(self):
self.gui = BasicWindow()
self.gui.labelContent = "This is the new label content"
mainApp = Main()
while True:
mainApp.gui.labelContent = time.time()
Thanks in advance! :)

How do I get the Tkinter event-listener to work?

I'm using Tkinter for the GUI of a little tool I wrote with Python. Basically I just want a callback-method to be executed as soon as the contents of an entry widget have changed. This can be done with Tkinter's own variable classes (StringVar, BooleanVar, etc. - see documentation for details: http://effbot.org/tkinterbook/variable.htm).
So I couldn't get the mechanism to work and I found a snippet online, where it works perfectly fine. Now I'm trying to figure out why my version does not work.
As you can see in the two code examples the only difference is, that I'm using the event-listening functionality inside a class, whereas the snippet I found online only demonstrates it in a straight top-to-bottom manner.
Here's what I've already tried:
I instantiated the Tk instance directly in the constructor of my GUI class - same behaviour.
I inherited directly from the Tk class (instead of Frame) - same behaviour.
I placed the callback outside of the class - same behaviour.
The only idea I have is that the problem might be scope related, which I tried to verify.
Working code snippet:
from tkinter import *
import tkinter as tk
def text_changed(*args):
print("Text changed.")
top = tk.Tk()
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", text_changed)
entry_widget = tk.Entry(top, textvariable = string_listener)
entry_widget.pack()
top.mainloop()
Not working code snippet
from tkinter import *
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
Like in the working example, my code ought to print Text changed., everytime a character is either deleted from or appended to the string in the extry-widget.
The problem is that string_listener is a local variable, and python is destroying the variable when __init__ finishes running. This doesn't happen in your original code since the variable is created in the global scope.
A simple solution is to save a reference as an attribute of the class:
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
self.string_listener = tk.StringVar()
self.string_listener.set("Init Text")
self.string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=self.string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
note: I also changed StringVar to tk.StringVar so that I could remove the redundant wildcard import of tkinter.

How to do loading screen in tkinter? [duplicate]

I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.

Categories