我有一个GUI,其中有一个在新窗口中打开的属性窗口。在某些时候,(随机而不是确定性地可重复)当我打开窗口时,它会给出一个错误的错误:
grab failed: window not viewable
除了打印该信息外,它不会干扰程序的正常功能,也不会对任何内容产生任何影响。
创建新窗口的代码是:
proc _prop_menu_make_top {{elem {}}} {
toplevel .prop_menu
#...initialize some variables...
wm title .prop_menu "Properties for $_prop_attr(name)"
#...create and display the window widgets...
bind .prop_menu <Key-KP_Enter> {_prop_menu_ok_button}
bind .prop_menu <Return> {_prop_menu_ok_button}
bind .prop_menu <Escape> {_prop_menu_cancel_button}
# catch presses on the window's `x` button
wm protocol .prop_menu WM_DELETE_WINDOW {
_prop_menu_cancel_button
}
# make the top window unusable
center_the_toplevel .prop_menu
focus .prop_menu.main_frame.model_name.entry
grab release .
grab set .prop_menu
}
proc center_the_toplevel { window } {
if { [string equal $window [winfo toplevel $window]] } {
set width [winfo reqwidth $window]
set height [winfo reqheight $window]
set x [expr {([winfo vrootwidth $window] - $width) / 2}]
set y [expr {([winfo vrootheight $window] - $height) / 2 }]
wm geometry $window +${x}+${y}
}
return
}
proc _prop_menu_ok_button {} {
#....saving the needed data...
_prop_menu_cancel_button
}
proc _prop_menu_cancel_button {} {
destroy .prop_menu
# make the top window usable again
grab set .
# redraw the canvas
nlv_draw
}
有没有人知道造成这个问题的原因是什么? 有没有人对如何让bug更容易再现有任何建议?
修改 运行Tcl版本8.4.6 for 64bit,不知道哪个tk版本。
答案 0 :(得分:5)
由于各种原因(一些技术原因,一些设计原则),Tk只允许在映射到屏幕上的窗口上设置抓取。这几乎可以肯定你想要的;鼠标点击应该是一个你可以看到的窗口。
你遇到的问题是你过早地试图抓住。特别是,Tk推迟为每个小部件创建底层X11 / OS窗口(取决于平台),直到它完成决定该小部件的配置将是什么,这在Tk变为“空闲”时被视为。空闲定义为输入事件循环并且没有待处理的待处理事件。此时,Tk告诉基本系统图形引擎分配一块矩形的屏幕区域(窗口)并将其放在屏幕上。这反过来会触发一系列事件和处理(当时有很多事情发生),最终会向你展示窗口;只有在显示窗口时才可以设置抓取它。
那么你怎么知道何时可以设置一个抓斗?好吧,你必须等待窗口出现。这意味着等待一个事件:您可能关心此任务的关键事件是<Map>
,<Visibility>
和<Expose>
。它们分别指示窗口何时逻辑上存在于根窗口内,何时实际可查看的内容发生变化,以及何时需要重绘。 (在Windows上有相应的第一个和最后一个,Tk在内部重新映射,但Windows根本没有告诉你实际的可见性变化。永远。)
等待小部件变为可抓取(然后进行抓取)的最简单方法是使用:
tkwait visibility $theWidget
grab set $theWidget
只是在事件循环中等待<Visibility>
事件才会出现在该窗口上。 (虽然因为该平台上没有事件类型,但它在Windows上不起作用。)您可以重写上述内容:
bind $theWidget <Visibility> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Visibility> {} ;# remove the binding again!
grab set $theWidget
如果您使用的是Windows [*] ,则必须使用第二种技术,但将<Visibility>
替换为<Map>
。
bind $theWidget <Map> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Map> {} ;# remove the binding again!
grab set $theWidget
我不记得Tk 8.4中的脚本是否可以使用<Expose>
;这是Tk正常处理的事件。在任何情况下,<Map>
都有效,<Visibility>
在X11上非常完美。
您还应该知道,tkwait
和vwait
都可能导致重入事件处理出现问题 - 如果您能提供帮助,则不需要它! - 所以要小心。您可以通过将其全部重写为延续传递样式来处理问题,在这种情况下恰好相当容易:
bind $theWidget <Map> {
bind %W <Map> {}
grab set %W
}
但如果你做的不仅仅是设置抓取,它可能变得非常复杂。 (Tcl 8.6的协同程序可以帮助解决这个问题,但它们绝对不能移植到8.4。)
[*]我忘记了OSX与非X11版本的Tk是否存在问题。如果你在意,请自行检查。