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

发布于 12-28 03:51 字数 1701 浏览 3 评论 0原文

我有一个 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
}

有谁知道可能导致此问题的原因吗? 有谁对如何使错误更容易重现有任何建议吗?

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

I have a GUI, in it I have a properties window that opens in a new window. In some of the times, (randomly and not deterministicly reproducible) when I open the window it gives the fallowing error:

grab failed: window not viewable

It doesn't interfere with the normal function of the program nor doesn't seem to have any affect on anything besides printing that message.

The code for creating the new window is:

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
}

Does anyone has any idea as to what may be causing this problem?
Does anyone has any advice as to how make the bug easier to reproduse?

EDIT:
running Tcl version 8.4.6 for 64bit, don't know which tk version.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

绝影如岚2025-01-04 03:51:25

解释

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

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

那么你怎么知道什么时候可以抓取呢?好吧,你必须等待窗口出现。这意味着等待事件:此任务中您可能关心的关键事件是

。它们分别指示窗口何时逻辑上存在于根窗口内、何时实际可见内容发生变化以及何时需要重绘。 (Windows 上有第一个和最后一个的等效项,Tk 在内部重新映射,但 Windows 根本不会告诉您实际的可见性变化。永远。)

解决方案

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

tkwait visibility $theWidget
grab set $theWidget

它只是在事件循环中等待 事件在该窗口上出现。 (但它在 Windows 上不起作用,因为该平台上缺少事件类型。)您可以将上面的内容重写为:

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

如果您使用的是 Windows[*],您将必须使用第二种技术,但将 替换为

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

我不记得 是否可用于 Tk 8.4 中的脚本;这是 Tk 通常为您处理的事件。无论如何,

可以正常工作, 在 X11 上非常完美。

您还应该意识到,tkwaitvwait 都可能导致可重入事件处理出现问题 — 如果您能控制的话,您就不会想要它! ——所以要小心。您可以通过将其全部重写为连续传递样式来解决问题,在这种情况下这恰好相当简单:

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

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


[*] 我忘记了使用非 X11 构建的 Tk 的 OSX 是否存在问题。自己检查一下是否关心。

Explanation

For various reasons (some technical, some design principles), Tk only permits grabs to be set on windows that are mapped onto the screen. This is almost certainly what you want; mouse clicks should be going to a window you can see after all.

The problem you've got is that you're trying to do the grab too early. In particular, Tk postpones the creation of the underlying X11/OS window (depending on platform) for each widget until it has finished deciding what the configuration of that widget will be, which is taken to be when Tk becomes “idle”. Idle is defined to be when the event loop is entered and there are no pending events to be serviced. At that point, Tk tells the basic system graphics engine to allocate a rectangular chunk of screen estate (the window) and to put it on the screen. That in turn triggers a whole cascade of events and processing (there's a lot going on at that point) that ends up with the window being shown to you; it's only once the window is shown that you can set a grab on it.

So how do you know when you can set a grab? Well, you've got to wait for the window to turn up. That means waiting for an event: the key ones that you might care about for this task are <Map>, <Visibility> and <Expose>. They indicate respectively when the window is logically present within the root window, when there is a change in what is actually viewable, and when there is something to redraw. (There are equivalents on Windows to the first and last, which Tk remaps internally, but Windows doesn't tell you about actual visibility changes at all. Ever.)

Solution

The simplest way to wait for a widget to become grabbable (and then do the grabbing) is to use:

tkwait visibility $theWidget
grab set $theWidget

That just waits in the event loop for a <Visibility> event to turn up on that window. (It doesn't work on Windows though because of the absence of the event type on that platform.) You could rewrite the above as this:

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

If you're on Windows[*], you'll have to use that second technique, but replacing <Visibility> with <Map>.

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

I can't remember if <Expose> is available to scripts in Tk 8.4; that's an event that Tk handles for you normally. In any case, <Map> works and <Visibility> is perfect on X11.

You should also be aware that both tkwait and vwait can cause problems with reentrant event handling — you don't want it if you can help it! — so take care. You can deal with the problems by rewriting it all into continuation-passing style, which happens to be fairly easy in this case:

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

But if you're doing anything more than setting a grab, it can become really complex. (Tcl 8.6's coroutines can help untangle this mess, but they're definitely not portable to 8.4.)


[*] I forget whether there are issues round here with OSX with a non-X11 build of Tk. Check for yourself if you care.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文