在Tcl/Tk中抓取一个新窗口

SIM*_*MEL 1 tk-toolkit tcl

我有一个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版本.

Don*_*ows 5

说明

由于各种原因(一些技术原因,一些设计原则),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完美.

你也应该知道,这两个tkwaitvwait可能导致折返事件处理的问题-你不想要它,如果你能帮助它! - 所以要小心.您可以通过将其全部重写为延续传递样式来处理问题,在这种情况下恰好相当容易:

bind $theWidget <Map> {
    bind %W <Map> {}
    grab set %W
}
Run Code Online (Sandbox Code Playgroud)

但是如果你做的不仅仅是设置抓取,它可能变得非常复杂.(Tcl 8.6的协同程序可以帮助解决这个混乱,但它们绝对不能移植到8.4.)


[*]我忘记了OSX与非X11版本的Tk是否存在问题.如果你在乎,请检查自己.