Running a command on Window minimization in Tkinter - python

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()

Related

Exit Python Tkinter app cleanly while also using "wait_variable" function on a button in an "after" loop

I have a problem similar to this post: Exit program within a tkinter class
My variation on the problem involves the wait_variable being used on a button to control "stepping forward" in an app, but also allowing the app to close cleanly.
See my code below:
# To see output unbuffered:
# python -u delete_win_test.py
import tkinter as tk
from tkinter import *
class GUI(Tk):
def __init__(self):
super().__init__()
# Close the app when the window's X is pressed
self.protocol("WM_DELETE_WINDOW", self.closing)
# When this var is set to 1, the move function can continue
self.var = tk.IntVar()
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
command=self.destroy)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
self.step_button = tk.Button(self, text="Step",
command=lambda: self.var.set(1))
self.step_button.place(relx=.5, rely=.75, anchor="c")
def move(self):
print("doing stuff") # simulates stuff being done
self.step_button.wait_variable(self.var)
self.after(0, self.move)
def closing(self):
self.destroy()
app = GUI()
app.move()
app.mainloop()
The window shows correctly
"Stepping forward" works because "doing stuff" prints to terminal on button click
Exiting the window by both pressing X or using the "exit" button both work
The problem: the Python app never exits from the terminal and requires a closing of the terminal.
How can I make the Python program exit cleanly so the user does not need to close and re-open a new terminal window?
Related references for animation, etc:
Animation using self.after: moving circle using tkinter
Button wait: Making Tkinter wait untill button is pressed
The original "exit" code: Exit program within a tkinter class
UPDATE (the solution):
(Credit to both response answers below)
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
- command=self.destroy)
+ command=self.closing)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
...
def closing(self):
self.destroy()
+ self.var.set("")
+ exit(0)
This allows the native window's "X" to close the window and the Tk button to close the window while still closing the Python app cleanly in the terminal.
Your closing function needs to set the variable to cause the app to stop waiting.
def closing(self):
self.destroy()
self.var.set("")
in the closing function, you need to call exit to exit the program.
def closing(self):
self.destroy() #closes tkinkter window
exit(0) #exits program

Using Python Arcade and Tkinter together hangs the application

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:

Tkinter: Keep from moving focus to window

I am trying to make an on screen keyboard. It works for widgets in the window but when I press a button it moves focus from the window I am trying to type in to the window that has the buttons. How do I prevent python from moving?
from tkinter import *
from pynput.keyboard import Key, Controller
keyboard = Controller()
class App:
def __init__(self, master):
self.entry = Entry()
self.buttonOne = Button(text='1')
self.buttonTwo = Button(text='2')
self.buttonThree = Button(text='3')
self.buttonOne.bind("<Button-1>", lambda event, keyPressed='1': self.pressed(event, keyPressed))
self.buttonTwo.bind("<Button-1>", lambda event, keyPressed='2': self.pressed(event, keyPressed))
self.buttonThree.bind("<Button-1>", lambda event, keyPressed='3': self.pressed(event, keyPressed))
self.entry.grid(row=0, column=0, columnspan=3)
self.buttonOne.grid(row=1, column=0)
self.buttonTwo.grid(row=1, column=1)
self.buttonThree.grid(row=1, column=2)
def pressed(self, event, keyPressed):
keyboard.press(keyPressed)
keyboard.release(keyPressed)
root = Tk()
app = App(root)
root.mainloop()
I would suggest using withdraw() and deiconify(). This will make it so the window with the button is invisible once you call it on that window. Once you use deiconify() it reverses this and makes it visible again.
More information can be found here.
Question: Keep from moving focus to window
On X11, you can set the -type attribute:
self.wm_attributes("-type", 'dock')
'dock' will working for me not to grab the focus, but are not supported by all window managers.
Reference:
wm attributes
Communicate with window manager
A list of types
-type
Requests that the window should be interpreted by the window manager as being of the specified type(s). This may cause the window to be decorated in a different way or otherwise managed differently, though exactly what happens is entirely up to the window manager.
'dock'
indicates a dock/panel feature,
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.wm_attributes("-type", 'dock')
for n in range(64, 68):
btn = tk.Button(self, text=chr(n), takefocus=0)
btn.bind("<Button-1>", self.on_click)
btn.pack()
def on_click(self, event):
w = event.widget
print(w['text'])
if __name__ == '__main__':
App().mainloop()

Python: How to create a dialog Window prior to main app Windows

Preface:
I have a Python ControlGPIO code with a working GUI (let us call it MainGUI).
I wish to have a dialog pop up window, prior to running MainGUI, in a way that the user can enable/ disable features in MainGUI. BUT MainGUI should start running only after dialog pop up window is closed.
My question is: How can I make a popup window that will postpone MainGUI untill it is closed?
Code below- boot_windows is my dialog pop up window (where all the enable/disable checkboxes will be ), BUT obviously does not postpone App as I need
root = Tk()
#output_gpioPins = [4,22,6,26]
#input_gpioPins = [3,21,5,27]
#ip = '192.168.2.112'
boot_windows = Toplevel(root)
text1 = ttk.Label(boot_windows, text="Hello World !!!")
text1.grid()
App = ContorlGPIOWindow(root, ip = '192.168.2.113', with_sf_bt=1, with_hw_bt=1, switch_names=['Light Kitchen','Light Room1', 'Window1', 'Window2'])
root.mainloop()
You can't do precisely what you want. Widgets exist in a tree-like structure. All windows except the root require a root window. The root window must be created first (which is why it's called the root window).
If you don't want the user to see it, you can hide it until it is ready to be displayed.
import tkinter as tk
root = tk.Tk()
root.withdraw()
boot_window = tk.Toplevel(...)
...
You can then call root.deiconify() when you are ready for it to be visible.
Another common solution is to use the root window for your dialog or splash screen or whatever, and then simply replace its contents with the real contents when you're ready.
As for how to wait for the popup... the root window has a method named wait_window which will enter the event loop and not return until the given window has been destroyed.
Here's an example of it's use:
import Tkinter as tk
class MainGUI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Hello, world!")
label.pack(fill="both", expand=True, padx=20, pady=20)
class Popup(tk.Toplevel):
def __init__(self, root):
tk.Toplevel.__init__(self, root)
label = tk.Label(self, text="Click to continue...")
label.pack(fill="both", expand=True, padx=20, pady=20)
button = tk.Button(self, text="OK", command=self.destroy)
button.pack(side="bottom")
if __name__ == "__main__":
root = tk.Tk()
root.withdraw()
popup = Popup(root)
root.wait_window(popup)
main = MainGUI(root)
main.pack(fill="both", expand=True)
root.deiconify()
root.mainloop()

python tkinter treeview not allowing modal window with direct binding like on_rightclick

Treeview with mouseclick binding that directly calls a function to open a modal window fails on grab_set()
tkinter.TclError: grab failed: window not viewable
The same works perfectly well if the mouseclick first opens a popup menu that calls the modal window function.
Is it not possible to open a modal window directly from a mouseclick (preferably doubleclick)?
Linux Mint 17/Ubuntu 14.04, 64bit, Python 3
My simple code example below. Please note that though I call two different functions, the code in each is essentially the same. When called from the popup menu it gives a modal window, when called directly from the treeview, it fails miserably. Is that expected behavior?
import tkinter as tk
from tkinter import ttk
class PopupWindow(tk.Toplevel):
def __init__(self,parent):
tk.Toplevel.__init__(self,parent)
self.parent=parent
self.protocol("WM_DELETE_WINDOW",self.destroy)
self.attributes('-topmost',True)
self.transient()
mainWindow=tk.Frame(self)
tFrame=tk.Frame(mainWindow)
self.tLabel=tk.Label(tFrame,text='Is this modal?',height=3,bd=10)
self.tLabel.pack()
self.tLabel2=tk.Label(tFrame,text=' ',height=3,bd=10)
self.tLabel2.pack()
bFrame=tk.Frame(mainWindow,bd=5)
bOK=tk.Button(bFrame,bd=1,text='Ok',command=self.destroy)
bOK.pack(side='left')
tFrame.grid(row=0,column=0)
bFrame.grid(row=1,column=0,sticky=tk.E)
mainWindow.pack()
class App:
def __init__(self):
self.root = tk.Tk()
self.tree = ttk.Treeview()
self.tree.pack()
for i in range(10):
self.tree.insert("", "end", text="Item %s" % i)
self.tree.bind("<Double-1>", self.onDoubleClick)
self.tree.bind("<Button-3>", self.onRightClick)
self.aMenu = tk.Menu(self.root, tearoff=0)
self.aMenu.add_command(label="Show 'Modal' Window",
command=lambda selection=self.tree.selection(): self.showIsModal(selection))
self.aMenu.add_separator()
self.root.mainloop()
def onRightClick(self, event):
try:
self.aMenu.selection = self.tree.identify_row(event.y)
self.aMenu.post(event.x_root, event.y_root)
finally:
self.aMenu.grab_release()
def showIsModal(self, item):
pup=PopupWindow(self.root);
pup.tLabel2['text']="Absolutely!"
pup.grab_set()
self.root.wait_window(pup)
def onDoubleClick(self, event):
item = self.tree.identify('item',event.x,event.y)
self.tree.grab_release()
self.showIsNotModal(item)
def showIsNotModal(self, item):
print("you clicked on", self.tree.item(item,"text"))
pup=PopupWindow(self.root);
pup.tLabel2['text']="Sadly no: grab_set() fails"
# following line will fail:
pup.grab_set()
self.root.wait_window(pup)
if __name__ == "__main__":
app = App()
I use Linux Mint 17/Ubuntu 14.04, 64bit, Python 3 too.
You can always check source code of existing dialogs and see how it works.
For example check filedialogs - path to file with source code:
import tkinter.filedialog
print(tkinter.filedialog.__file__)
# /usr/lib/python3.5/tkinter/filedialog.py
and you will see
self.top.wait_visibility() # window needs to be visible for the grab
self.top.grab_set()
So you have to use wait_visibility() before grab_set()
def showIsNotModal(self, item):
print("you clicked on", self.tree.item(item, "text"))
pup = PopupWindow(self.root);
pup.tLabel2['text'] = "Sadly no: grab_set() fails"
pup.wait_visibility() # <-----
pup.grab_set()
self.root.wait_window(pup)

Categories

Resources