我有一个GUI,在其中我有一个在新窗口中打开的属性窗口.在某些时候,(随机而不是确定性地可重复)当我打开窗口时,它会给出以下错误:
grab failed: window not viewable
Run Code Online (Sandbox Code Playgroud)
除了打印该消息之外,它不会干扰程序的正常功能,也不会对任何内容产生任何影响.
创建新窗口的代码是:
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
}
Run Code Online (Sandbox Code Playgroud)
有没有人知道可能导致这个问题的原因是什么?有没有人对如何让bug更容易复制有任何建议?
编辑: 运行Tcl版本8.4.6 for 64bit,不知道哪个tk版本.
由于各种原因(一些技术原因,一些设计原则),Tk只允许在映射到屏幕上的窗口上设置抓取.这几乎可以肯定你想要的; 鼠标点击应该是一个你可以看到的窗口.
你遇到的问题是你过早地试图抓住.特别是,TK推迟底层X11/OS窗口(取决于平台)的创建为每个插件,直到它已完成该决定窗口小部件的配置会是什么,这被认为是当Tk的变为"空闲".空闲定义为输入事件循环并且没有待处理的待处理事件.此时,Tk告诉基本系统图形引擎分配一块矩形的屏幕区域(窗口)并将其放在屏幕上.这反过来会触发一系列事件和处理(当时有很多事情发生),最终会向你展示窗口; 只有在显示窗口时才可以设置抓取它.
所以你怎么知道什么时候可以设置一个抓斗?好吧,你必须等待窗口出现.这意味着等待一个事件:你可能关心这个任务的关键事件是<Map>
,<Visibility>
和<Expose>
.它们分别指示窗口何时逻辑上存在于根窗口内,何时实际可查看的内容发生变化,以及何时需要重绘.(Windows上有相应的第一个和最后一个,Tk在内部重新映射,但Windows根本没有告诉你实际的可见性变化.永远.)
等待窗口小部件变为可抓取(然后执行抓取)的最简单方法是使用:
tkwait visibility $theWidget
grab set $theWidget
Run Code Online (Sandbox Code Playgroud)
只是在事件循环中等待事件<Visibility>
在该窗口上出现.(虽然因为该平台上没有事件类型,但它在Windows上不起作用.)您可以重写上面的内容:
bind $theWidget <Visibility> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Visibility> {} ;# remove the binding again!
grab set $theWidget
Run Code Online (Sandbox Code Playgroud)
如果你在Windows [*]上,你将不得不使用第二种技术,但替换<Visibility>
为<Map>
.
bind $theWidget <Map> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Map> {} ;# remove the binding again!
grab set $theWidget
Run Code Online (Sandbox Code Playgroud)
我不记得<Expose>
Tk 8.4中的脚本是否可用; 这是Tk正常处理的事件.在任何情况下,<Map>
工作,并<Visibility>
在X11完美.
你也应该知道,这两个tkwait
和vwait
可能导致折返事件处理的问题-你不想要它,如果你能帮助它! - 所以要小心.您可以通过将其全部重写为延续传递样式来处理问题,在这种情况下恰好相当容易:
bind $theWidget <Map> {
bind %W <Map> {}
grab set %W
}
Run Code Online (Sandbox Code Playgroud)
但是如果你做的不仅仅是设置抓取,它可能变得非常复杂.(Tcl 8.6的协同程序可以帮助解决这个混乱,但它们绝对不能移植到8.4.)
[*]我忘记了OSX与非X11版本的Tk是否存在问题.如果你在乎,请检查自己.