Tkinter:尝试点击其他地方时窗口闪烁

6

我一直在尝试做这件事,但是还没有找到方法。

我有一个 tkinter 脚本,当按下按钮时会创建一个弹出窗口。然而,我不希望用户能够点击该窗口以外的任何之前创建的窗口。我已经使用 root.grab_set() 实现了这一点,但是用户并没有收到必须留在该窗口的提示。

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

例如,当您使用文件对话框模块创建一个窗口时,如果您试图单击另一个窗口,文件对话框窗口会保持在顶部,并有闪烁的动画来告知用户他们不能退出。我是否可以复制这种效果?查看文件对话框源代码并没有给我带来收获,Google搜索也没用。
2个回答

3
我能想到的最简单的方法是使用事件和焦点命令,以及Windows的 bell 命令:
#!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()

2
好吧,我猜那是一种方法。窗口不会闪烁,但至少有一些指示表明他们需要留在窗口上。然而,使用这种方法仍然可以与父窗口进行交互,但子窗口只是非常快地重新获得焦点。 - Titansmasher

3
这是一个针对Windows的解决方案,它使用user32 dll中的FlashWindowEx。您需要向其传递一个FLASHWINFO对象。 grab_set确保弹出窗口保持焦点并禁用主窗口中的任何小部件,使弹出窗口变为瞬态,确保它始终位于主控之上。<Button-1>事件用于检查鼠标单击,winfo_containing检查是否点击了除弹出窗口以外的其他窗口。然后将焦点设置回弹出窗口并闪烁处于焦点的窗口(这总是弹出窗口)。
您需要pywin32才能使用此功能。
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()

现在,只有在单击主窗口的正文时才会出现闪烁,因此单击标题栏只会将焦点返回到弹出窗口而不会闪烁。要使其在这种情况下也触发闪烁,您可以尝试使用<FocusOut>事件,但您必须确保它仅在焦点转移到主窗口时才会发生,但实际上从未发生,因为使用了grab_set。您可能需要解决这个问题,但目前它运行得相当不错。因此它并不完美,但我希望它能有所帮助。

1
理想情况下,我希望只使用默认的Python库来完成这个任务,因为这需要一个易于移植的系统。 - Titansmasher
另外,我之前见过self.root.transient(self.parent)的用法,但是当我尝试使用它时,窗口却没有显示出来。这可能与父窗口在弹出窗口创建时已经可见有关吗? - Titansmasher
我刚刚编辑了事件触发器并进行了一些检查,以便在我看来它可以更好地工作。很抱歉,我无法想到只使用默认Python的真正解决方案。也许其他人可以。我会让这个答案留在这里,供其他试图做同样事情的人参考,他们可能不介意使用pywin32。至于“瞬态”问题,这可能值得提出另一个问题。 - fhdrsdg
我发现 self.root.focus_set() 并没有真正做到我想要的效果。但至少在 Linux 上,调用 self.root.focus_force()(也许使用 self.root.after(...) 进行时间延迟)效果相当不错... - Matthew Daws

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接