I am trying to build an app that generates dynamic visual models (using Python 3 with PyCharm on Windows). I am using Arcade for the main viewing / user interaction window, and Tkinter for preliminary data input, model parameters, numeric output, errors and warnings etc.
I find that if I open a tk window (e.g. messagebox.showinfo or messagebox.error) while the Arcade window is open, the application hangs. Here is a minimal snippet that recreats the problem:
import tkinter.messagebox
import arcade
tkinter.messagebox.showinfo("Greetings", "hello")
app = arcade.Window(500, 300, "Let's play")
tkinter.messagebox.showinfo("Greetings", "hello again")
The second messagebox never opens, and a whopping 30% of the CPU is active while Python is doing nothing except (in theory) waiting for user input.
Following solution works for the static text in Arcade. It won't update text dynamically. See Ethan Chan's comment below.
You can launch tkinter app from the arcade:
import arcade
import tkinter as tk
class ArcadeApp(arcade.Window):
def __init__(self):
super().__init__(400, 300)
self.root = None
def on_closing(self):
self.root.destroy()
self.root = None
def on_draw(self):
arcade.start_render()
arcade.draw_text('Click to launch Tkinter', 200, 150, arcade.color.RED, 30, align='center', anchor_x='center')
def on_mouse_release(self, x, y, button, key_modifiers):
if not self.root:
self.root = tk.Tk()
self.root.geometry('400x300')
self.root.protocol('WM_DELETE_WINDOW', self.on_closing)
label = tk.Label(self.root, text='Greetings from Tkinter!')
label.config(font=('', 20))
label.place(relx=0.5, rely=0.5, anchor='center')
self.root.mainloop()
ArcadeApp()
arcade.run()
Output:
Related
I'm working on a tkinter GUI in Python to produce error messages in a new window. When running the code as shown below, the error noise plays, then it pauses for several seconds before opening the window. If I comment out the line with winsound, it opens it just fine.
import tkinter as tk
import winsound
class Error_Window:
def __init__(self, txt):
self.root = tk.Tk()
self.root.title("Error")
self.lbl = tk.Label(self.root, text=txt)
self.lbl.pack()
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
self.root.mainloop()
I suspect that it may be due to the error noise playing in full before reaching the mainloop command. One solution to this could be running the sound in a separate thread, but I've heard multithreading with tkinter should be avoided. Any tips on getting it to open smoothly at the same time as the noise is played?
Try this, the reason why it does that is the whole program is should we say in ONE THREAD/ MAIN THREAD so it would do first or execute first the sound then pop up the window. I think there's no problem with working with threads in tkinter just like what #jasonharper said
import tkinter as tk
import winsound
import threading
class Error_Window:
def __init__(self, txt):
self.root = tk.Tk()
self.root.title("Error")
self.lbl = tk.Label(self.root, text=txt)
th = threading.Thread(target=self.__play_sound,args=[])
th.start()
self.lbl.pack()
self.root.mainloop()
def __play_sound(self):
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
Error_Window("Hi")
I want to push some configlines through a telnetsession from a tkinter gui.
Simplified; in my GUI I have a button. After pressing this button the telnet session starts (it fetches the config lines from an external fileshare).
But at this moment it is not very user friendly, there is now way to tell whether it is still busy or not. I want to fix this by a progress bar popup.
I've got my main script with all the fetching of the configuration files, adapting and sending it to a switch.
I got a "standalone" script with the popup progress bar.
Now I want to combine those two, but it won't work. I've read about multi-threading, but since I'm new to coding I need some help to understand how this works, if it is needed in my case
The mainscript:
#window stuff
window = Tk()
window.geometry('700x500')
window.title("Switchconfig generator")
window.lift()
def btntelnetclicked():
try:
telnet()
except:
messagebox.showinfo("Status", "Something went wrong, config was not pushed.")
#buttons
btntelnet= Button(window, text="Push config", command=btntelnetclicked)
btntelnet.grid(column=2, row=4, padx=10, pady=10)
This is offcourse just a little piece of code
The progressbar
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def arbitrary():
btntelnetclicked()
gui = GUI_Core()
gui.root.mainloop()
I want to create a GUI module that I can import in my main program without having to import tkinter there, letting just the module handle everything. Here's how I imagine that it might work:
main.py
import gui as g
def update():
#Update the GUI with new Data from this main program
GUI = g.gui()
gui.after(1000, update)
gui.mainloop()
gui.py
import tkinter as tk
class viewer(tk.Frame):
#Some variables
def __init__(self, parent):
tk.Frame.__init(self, parent)
self.parent = parent
self.initialize(400, 100)
def initialize(self, width, height):
#Initialize some widgets, place them on grid, etc
def start(self):
#Do some other stuff, make a main window, configurations, etc
print('Started!')
Edit: "Don't ask for opinion"
How do I make this work?
import tkinter as tk
import gui as g
root = tk.Tk()
GUI = g.gui(root)
GUI.after(1000, update)
GUI.mainloop()
The above is what I don't want.
I used a workaround that seemed plausible to me:
main.py
import gui
GUI = gui.start()
GUI.after(1000, update)
GUI.mainloop()
gui.py
import tkinter as tk
def start():
root = tk.Tk()
run = viewer(root) # <- The class provided above
return run
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.
I have a Tkinter window whenever the minimize button is pressed I'd like to run a command, how do I do this?
I know w.protocol("WM_DELETE_WINDOW", w.command) will run a command on exit.
You can bind to the <Unmap> event.
For example, run the following code and then minimize the main window. The tool window should disappear when the main window is minimized.
import Tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
tk.Label(self.root, text="main window").pack()
self.t = tk.Toplevel()
tk.Label(self.t, text="tool window").pack()
self.root.bind("<Unmap>", self.OnUnmap)
self.root.bind("<Map>", self.OnMap)
self.root.mainloop()
def OnMap(self, event):
# show the tool window
self.t.wm_deiconify()
def OnUnmap(self, event):
# withdraw the tool window
self.t.wm_withdraw()
if __name__ == "__main__":
app=App()