Titansmasher
Titansmasher

Reputation: 75

Tkinter: Window flash when attempting to click away

Ive been trying to do this for a while now, but haven't figured out a way to do it.

I have a tkinter script, that creates a popup window when a button is pressed. However I don't want the user to be able to click away from this window to any previous windows created. I have got this working with root.grab_set(), however there is no indication to the user that they must stay on that window.

class popup(object):
    def __init__(self, parent):
        self.root=Toplevel(parent)
        self.root.grab_set() #prevents the user clicking on the parent window
                             #But the window doesnt 'flash' when an attempt to click away is made

For example, when you have a window created by the filedialogue module, if you attempt to click onto another window the filedialogue window stays on top and has a 'flashing' animation to let the user know they cant click away. Is there a way I can reproduce this effect? Going through the source of filedialogue hasn't been fruitful for me, and neither have Google searches.

Upvotes: 6

Views: 2953

Answers (2)

fhdrsdg
fhdrsdg

Reputation: 10602

Here's a solution for Windows that uses FlashWindowEx from the user32 dll. You need to pass a FLASHWINFO object to it. The grab_set makes sure the popup window stays in focus and disables any widgets in the main window, making the popup transient makes sure it's always on top of the master. The <Button-1> event is used to check mouse clicks, and winfo_containing checks if another window than the popup is clicked. I then set the focus back to the popup and flash the window in focus (which then always is the popup).

You need pywin32 to use this.

import Tkinter as tk
from ctypes import *
import win32con

class popup(object):
    def __init__(self, parent):
        self.parent = parent
        self.root=tk.Toplevel(self.parent)
        self.root.title("Popup")
        self.root.grab_set()
        self.root.transient(self.parent)
        self.root.bind("<Button-1>", self.flash)

    def flash(self, event):
        if self.root.winfo_containing(event.x_root, event.y_root)!=self.root:
            self.root.focus_set()
            number_of_flashes = 5
            flash_time = 80
            info = FLASHWINFO(0,
                              windll.user32.GetForegroundWindow(),
                              win32con.FLASHW_ALL,
                              number_of_flashes,
                              flash_time)
            info.cbSize = sizeof(info) 
            windll.user32.FlashWindowEx(byref(info))

class FLASHWINFO(Structure): 
    _fields_ = [('cbSize', c_uint), 
                ('hwnd', c_uint), 
                ('dwFlags', c_uint), 
                ('uCount', c_uint), 
                ('dwTimeout', c_uint)]

main = tk.Tk()
main.title("Main")
pop = popup(main)
main.mainloop()

As it is now, the flash only occurs when the main window's body is clicked, so clicking the title bar just returns the focus to the popup without flashing. To make it fire also when that happens you could try using the <FocusOut> event, but you would have to make sure it only happens when the focus passes to the main window, but it never really does since the grab_set is used. You might want to figure that out, but as it is now it works quite well. So it's not perfect, but I hope it helps.

Upvotes: 3

James Kent
James Kent

Reputation: 5933

The simplest way i can think to do this is to use an event and the focus commands, along with the windows bell command:

#!python3

import tkinter as tk

class popup(object):
    def __init__(self, parent):
        self.root=tk.Toplevel(parent)
        self.root.title("Popup")
        self.root.bind("<FocusOut>", self.Alarm)

    def Alarm(self, event):
        self.root.focus_force()
        self.root.bell()

main = tk.Tk()
main.title("Main")
pop = popup(main)
main.mainloop()

Upvotes: 3

Related Questions