0

I have a problem with MenuSelect event in Tcl/Tk 8.6.11 (tried in Linux, Debian 10.7).

In fact, it doesn't fire at all for the main and tearoff-ed menus. Though working fine in Tcl/Tk 8.6.9, and even in 8.6.11 - only while menus are not tearoff-ed.

A test code:

package require Tk
proc ::somehandler {w wt} {
  puts "Step [incr ::step]: $w / $wt, index=[$wt index active]"
}
set w [menu .m -tearoff 1]
$w add command -label {Item 1}
$w add command -label {Item 2}
bind $w <<MenuSelect>> [list ::somehandler $w %W]
pack [button .b -text "Click me" \
  -command {tk_popup .m [winfo pointerx .] [winfo pointery .]}]

I tried the following (idiotic though) replacement:

event delete <<MenuSelect>>
event add <<MenuSelect>> <Motion>
bind $w <<MenuSelect>> [list ::somehandler $w %W]

... with the same results.

Seemingly, it's related to menu pathes dealt in Tk somewhat tricky, as seen in the above example.

I'm too lazy to change a standard code at switching from 8.6.9 to 8.6.11/12, 8.7 etc.

TIA for any hints.

Alex P
  • 96
  • 1
  • 6

2 Answers2

0

This is probably related to the fact that menus use clones for tearoffs and the menubar. From the documentation:

When a menu is set as a menubar for a toplevel window, or when a menu is torn off, a clone of the menu is made. This clone is a menu widget in its own right, but it is a child of the original. Changes in the configuration of the original are reflected in the clone. Additionally, any cascades that are pointed to are also cloned so that menu traversal will work right. Clones are destroyed when either the tearoff or menubar goes away, or when the original menu is destroyed.

I can't remember how exactly clones are really named, but you don't normally interact with them directly; it's only with event handling that you ever really see them. (I've only ever had to deal with them when doing tooltips for menus.)

Normally, it's considered best to avoid using <<MenuSelect>> and instead just set a -command on the entries that can be selected (or to just set the model variables right for checkbutton and radiobutton entries). And avoid tearoffs entirely; they're a style of menu interaction that went out of fashion over 25 years ago.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • That's the 8.7 docs, but menus have been functionally the same for a very long time; we see very little reason to change them. (Clones exist because a menu can be a tearoff, a menubar and a direct popup from a menubutton, all visible at the same time. You probably don't want to do that, but it's possible.) – Donal Fellows Dec 04 '21 at 16:19
  • Hi Donal, thank you for your answer, I hoped for it and got it. Though, alas, it is not satisfactory. I need not commands on menu items but to watch on their selections. It's all about showing tooltips for them. I have an advice by Csaba how to workaround this issue and after trying it I'll post it here as an answer, just for those who would need it too. Still, I hoped for a cardinal solution of this problem as it's a real bug imho introduced to Tk in 8.6.11. – Alex P Dec 05 '21 at 05:48
0

For Tk 8.6.11+, bind Menu should be used instead of bind $w (for individual menu items). It adds some acrobatics to an event handler that should calculate what's a menu item to be dealt with.

I.e. we have something like:

bind Menu <<MenuSelect>> [list ::somehandler %W]

The %W wildcard is passed to ::somehandler as a "cloned" name, if the menu item is in a cloned menu.

And ::somehandler should calculate who is the %W in reality.

Csaba Nemethi advises to use a procedure like clonename (from utils.tcl of BWidget package). This procedure gets a clone name from a "normal" menu item's path.

Here is a bit modified version of it:

proc clonename {mnu} {
  # Gets a clone name of a menu.
  #   mnu - the menu's path
  # This procedure is borrowed from BWidget's utils.tcl.

  set path [set menupath {}]
  set found 0
  foreach widget [lrange [split $mnu .] 1 end] {
    if {$found || [winfo class "$path.$widget"] eq {Menu}} {
      set found 1
      append menupath # $widget
      append path . $menupath
    } else {
      append menupath # $widget
      append path . $widget
    }
  }
  return $path
}

As an example of use, see test.tcl of:

http://chiselapp.com/user/aplsimple/repository/baltip/zip/trunk/baltip.zip

Thank you Donal and Csaba for your hints.

Alex P
  • 96
  • 1
  • 6