Binding callbacks to minimize and maximize events in Toplevel windows
Unmapping on Linux
The term Unmap
has a quite different meaning on Linux than it has on Windows. On Linux, Unmapping a window means making it (nearly) untraceable; It does not appear in the application's icon, nor is it listed anymore in the output of wmctrl -l
. We can unmap / map a window by the commands:
xdotool windowunmap <window_id>
and:
xdotool windowmap <window_id>
To see if we can even possibly make tkinter detect the window's state minimized, I added a thread to your basic window, printing the window's state once per second, using:
root.state()
Minimized or not, the thread always printed:
normal
Workaround
Luckily, if you must be able to detect the window's minimized state, on Linux we have alternative tools like xprop
and wmctrl
. Although as dirty as it gets, it is very well scriptable reliably inside your application.
As requested in a comment, below a simplified example to create your own version of the bindings with external tools.
How it works
- When the window appears (the application starts), We use
wmctrl -lp
to get the window'sid
by checking both name and pid (tkinter
windows have pid 0). - Once we have the
window id
, we can check if the string_NET_WM_STATE_HIDDEN
is in output ofxprop -id <window_id>
. If so, the window is minimized.
Then we can easily use tkinter
's after() method to include a periodic check. In the example below, the comments should speak for themselves.
What we need
We need both wmctrl and xprop to be installed. On Dedian based systems:
sudo apt-get install wmctrl xprop
The code example
import subprocess
import time
from Tkinter import *
class TestWindow:
def __init__(self, master):
self.master = master
self.wintitle = "Testwindow"
self.checked = False
self.state = None
button = Button(self.master, text = "Press me")
button.pack()
self.master.after(0, self.get_state)
self.master.title(self.wintitle)
def get_window(self):
"""
get the window by title and pid (tkinter windows have pid 0)
"""
return [w.split() for w in subprocess.check_output(
["wmctrl", "-lp"]
).decode("utf-8").splitlines() if self.wintitle in w][-1][0]
def get_state(self):
"""
get the window state by checking if _NET_WM_STATE_HIDDEN is in the
output of xprop -id <window_id>
"""
try:
"""
checked = False is to prevent repeatedly fetching the window id
(saving fuel in the loop). after window is determined, it passes further checks.
"""
self.match = self.get_window() if self.checked == False else self.match
self.checked = True
except IndexError:
pass
else:
win_data = subprocess.check_output(["xprop", "-id", self.match]).decode("utf-8")
if "_NET_WM_STATE_HIDDEN" in win_data:
newstate = "minimized"
else:
newstate = "normal"
# only take action if state changes
if newstate != self.state:
print newstate
self.state = newstate
# check once per half a second
self.master.after(500, self.get_state)
def main():
root = Tk()
app = TestWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
Running a command on Window minimization in Tkinter
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()
Why does a Toplevel window get destroyed when the main window is minimized?
With the help of acw1668, I found the answer by myself.
The top-level window does not disappear; instead, it just goes behind all the windows.
There is a way to bring it back:
from tkinter import *
root = Tk()
root.geometry("500x500")
def bring_window_back(e):
toplevel.attributes('-topmost' , 1)
toplevel.attributes('-topmost' , 0)
toplevel = Toplevel(root)
toplevel.attributes("-toolwindow" , 1)
root.bind("<Map>" , bring_window_back)
mainloop()
Note: The <Map>
binding may not work properly on linux. If you are searching for a solution for this, please see: Binding callbacks to minimize and maximize events in Toplevel windows
Hope this helps you all.
Tkinter bind callback to activating a window
I found a solution to this for anyone else with a similar problem. You can create a 'dummy' button in the invisible window that will become active when the window is in focus. You then have that call a function that places the main application window in focus.
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 0.0)
entry = Button()
entry.pack()
entry.focus_set()
entry.pack_forget()
Then add this to __init__
in the MyMain
class:
self.master.bind('<FocusIn>', self.on_root_deiconify)
Detect if user has clicked the 'maximized' button
There is a good way to does it using .bind()
, so let's get started!
As we know, we can maximize the window using the command .state('zoomed')
.
root.state('zoomed')
And we can get whatever window event by .bind("<Configure>", my_function)
So we can create create a simple function that catches a maximize window event, not necessarily a event by click, but it works.
Here's an example:
import tkinter
def window_event(event):
if root.state() == 'zoomed':
print("My window is maximized")
if __name__ == '__main__':
root = tkinter.Tk()
root.title("Maximized")
root.bind("<Configure>", window_event)
root.mainloop()
EDIT 1: New functionality
import tkinter
def window_event(event):
if root.state() == 'zoomed':
print("My window is maximized")
#GET A NORMAL WINDOW EVENT
#You can easily do this by a conditional statement, but remember if you even move the window position,
#this conditional statement will be true, because move the window is a window event
if root.state() == 'normal':
print("My window is normal")
if __name__ == '__main__':
root = tkinter.Tk()
root.title("Window")
root.geometry("620x480")
root.bind("<Configure>", window_event)
root.mainloop()
EDIT 2: New functionality
import tkinter
count = 0
def window_event(event):
global count
if root.state() == 'zoomed':
print("My window is maximized")
count = 0
if root.state() == 'normal' and count < 1:
print("My window is normal")
count +=1
if __name__ == '__main__':
root = tkinter.Tk()
root.title("Window")
root.geometry("620x480")
root.bind("<Configure>", window_event)
root.mainloop()
Take a look at this links, they are another way to work with Python GUI:
- PyGUI
- PySimpleGUI
tkinter python maximize window
If you want to set the fullscreen attribute to True, it is as easy as:
root = Tk()
root.attributes('-fullscreen', True)
However, it doesn't show the title bar. If you want to keep it visible, you can resize the Tk element with the geometry()
method:
root = Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
With winfo_width()
and winfo_height()
you can get the width and height or the window, and also you can bind an event handler to the <Configure>
event:
def resize(event):
print("New size is: {}x{}".format(event.width, event.height))
root.bind("<Configure>", resize)
How do I set the initial window size as the minimum window size in tkinter?
You can also force an update right away without entering your mainloop, by using something like this:
root = Tk()
# set up widgets here, do your grid/pack/place
# root.geometry() will return '1x1+0+0' here
root.update()
# now root.geometry() returns valid size/placement
root.minsize(root.winfo_width(), root.winfo_height())
Description of update() at effbot tkinterbook:
Processes all pending events, calls event callbacks, completes any
pending geometry management, redraws widgets as necessary, and calls
all pending idle tasks. This method should be used with care, since it
may lead to really nasty race conditions if called from the wrong
place (from within an event callback, for example, or from a function
that can in any way be called from an event callback, etc.). When in
doubt, use update_idletasks instead.
I've used this a good deal while messing about trying to figure out how to do things like get the size/position of widgets before jumping into the main loop.
Related Topics
Find Broken Symlinks with Python
Better Way to Script Usb Device Mount in Linux
Linux/Python: Encoding a Unicode String for Print
Pyserial Works Fine in Python Interpreter, But Not Standalone
Running Python Script as a Systemd Service
/Usr/Bin/Env: Python2: No Such File or Directory
Python Linux Selenium: Chrome Not Reachable
Set the Hardware Clock in Python
How to Install Lxml for Python Without Administative Rights on Linux
A Way to "Listen" for Changes to a File System from Python on Linux
High Kernel CPU When Running Multiple Python Programs
Send Sigint in Python to Os.System
Running a Python Script Using Cron
How to Check the Data Transfer on a Network Interface in Python