【问题标题】:Grabbing a new Window in Tcl/Tk在 Tcl/Tk 中抓取一个新窗口
【发布时间】:2012-01-21 04:16:48
【问题描述】:

我有一个 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 更容易重现有任何建议吗?

编辑: 运行64位的Tcl 8.4.6版,不知道哪个tk版本。

【问题讨论】:

  • 你运行的是哪个版本的 Tcl 和 Tk?
  • 也许尝试一些“tkwait可见性”以确保窗口可见? wiki.tcl.tk/10013
  • Tk 版本应该与 Tcl 版本完全匹配到 8.4 系列。
  • 这不是 FWIW 的错误;它绝对是故意这样设计的。在使用抓取时,还要小心键盘焦点管理,因为它会完全忽略抓取。

标签: tcl tk


【解决方案1】:

说明

出于各种原因(一些技术,一些设计原则),Tk 只允许在映射到屏幕上的窗口上设置抓取。这几乎肯定是您想要的;毕竟,鼠标点击应该会转到您可以看到的窗口。

您遇到的问题是您试图过早地进行抓取。特别是,Tk 推迟为每个小部件创建底层 X11/OS 窗口(取决于平台),直到它完成决定该小部件的配置将是什么,这被认为是当 Tk 变为“空闲”时。空闲被定义为当进入事件循环并且没有待处理的事件被服务时。此时,Tk 告诉基本系统图形引擎分配一块矩形的屏幕区域(窗口)并将其放在屏幕上。这反过来又会触发一连串的事件和处理(此时发生了很多事情),最终会向您显示窗口;只有在窗口显示后,您才能对其进行抓取。

那么你怎么知道什么时候可以设置抓取呢?好吧,您必须等待窗口打开。这意味着等待一个事件:您可能关心此任务的关键事件是&lt;Map&gt;&lt;Visibility&gt;&lt;Expose&gt;。它们分别指示窗口何时出现在根窗口中,何时实际可见的内容发生变化,以及何时需要重绘。 (Windows 上有第一个和最后一个等价物,Tk 在内部重新映射,但 Windows 根本不会告诉您实际的可见性变化。永远。)

解决方案

等待小部件变得可抓取(然后进行抓取)的最简单方法是使用:

tkwait visibility $theWidget
grab set $theWidget

这只是在事件循环中等待&lt;Visibility&gt; 事件在该窗口上出现。 (由于该平台上没有事件类型,它在 Windows 上不起作用。)您可以将上面的内容重写为:

bind $theWidget <Visibility> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Visibility> {}    ;# remove the binding again!
grab set $theWidget

如果您使用的是 Windows[*],则必须使用第二种技术,但将 &lt;Visibility&gt; 替换为 &lt;Map&gt;

bind $theWidget <Map> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Map> {}           ;# remove the binding again!
grab set $theWidget

我不记得&lt;Expose&gt; 是否可用于 Tk 8.4 中的脚本;这是 Tk 通常为您处理的事件。无论如何,&lt;Map&gt; 有效,&lt;Visibility&gt; 在 X11 上是完美的。

您还应该知道tkwaitvwait 都可能导致可重入事件处理出现问题——如果您能提供帮助,您就不会想要它! ——所以要小心。您可以通过将其全部重写为持续传递样式来处理这些问题,在这种情况下,这恰好相当容易:

bind $theWidget <Map> {
    bind %W <Map> {}
    grab set %W
}

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


[*] 我忘记了使用非 X11 构建的 Tk 的 OSX 是否存在问题。如果您在乎,请自行检查。

【讨论】:

  • 对于 Python Tkinter 人来说,widget.wait_visibility() 如所述 here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多