Binding Callbacks to Minimize and Maximize Events in Toplevel Windows

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.

Sample Image

How it works

  • When the window appears (the application starts), We use wmctrl -lp to get the window's id 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 of xprop -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



Leave a reply



Submit