0

I previously found that there are some limitations with native menus that I'm not happy with (that is, they steal many events and won't share).

Bryan Oakly suggested that I write my own menu class, which can share the events that a native menu steals. I figured I'd do it by putting a Listbox on a featureless Toplevel. One of the behaviors that you'd expect from a Menu that isn't built into the Listbox is having it highlight items as you mouse over them.

Here's the code I wrote to accomplish this:

import Tkinter as tk

class Menu(tk.Listbox):
    def __init__(self, master, items, *args, **kwargs):
        tk.Listbox.__init__(self, master, exportselection = False, *args, **kwargs)
        self.pack()

        for item in items:
            self.insert(tk.END, item)

        self.bind('<Enter>',  lambda event: self.snapHighlightToMouse(event))
        self.bind('<Motion>', lambda event: self.snapHighlightToMouse(event))
        self.bind('<Leave>',  lambda _: self.unhighlight())

    def snapHighlightToMouse(self, event):
        self.selection_clear(0, tk.END)
        self.selection_set(self.nearest(event.y))

    def unhighlight(self):
        self.selection_clear(0, tk.END)

m = Menu(None, ['Alice', 'Bob', 'Caroline'])

tk.mainloop()

This kind of works. Sometimes the highlight follows the cursor. Sometimes it seems to get stuck for no particular reason. It's not consistent, but I find the most reliable way to get it stuck is to enter from the left or right side onto Caroline, then move up to Bob and/or Alice - Caroline will remain the highlighted item.

Similarly, entering from the top often doesn't trigger an Enter event - Alice doesn't get highlighted (nor do the others as I mouse over them).

Is there something I'm doing wrong? Is there something I can do to make the highlight more reliably follow the mouse cursor?

Leave seem to always trigger without fail - it's just Motion and Enter that are hit or miss.

Community
  • 1
  • 1
ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
  • I was just playing around with it testing what you are saying and everything is working fine for me in Python 3.4.1 on Windows 7 (after changing `Tkinter` to `tkinter`). Maybe it's version / platform specific? – Rob Murray Jan 18 '16 at 09:01
  • @R.Murray - I tested it in Python 2.7.10 on OS X 10.11. I'll try it out on Windows 7 later today. – ArtOfWarfare Jan 18 '16 at 12:46
  • @R.Murray - Huh... trying it now on Windows 7 (still Python 2.7.10) it does seem to work perfectly. I wonder what's going wrong in OS X 10.11 and how I can get around that... possibly I should poll? I don't really intend for this widget to be drawn for long periods of time. – ArtOfWarfare Jan 18 '16 at 14:34
  • I'm not sure tbh, I'm no expert! I tested it and it worked fine so I thought I'd let you know... not sure I can help you any further. Sounds like a problem for Bryan Oakley... – Rob Murray Jan 18 '16 at 14:40
  • one thing it could be is what version of Tk is in use, i know that OS X uses another version for some things that has caused other people problems in the past... – James Kent Jan 19 '16 at 16:15
  • @JamesKent - Interesting thought. I thought Tk came with Python, and that for a given version of Python, all platforms would be using the same version of Tk? How can I check the Tk version? Does the Tk version ever change with 0.0.1 updates of Python? – ArtOfWarfare Jan 19 '16 at 16:20
  • 1
    have a look at this link for installing the latest version: https://www.python.org/download/mac/tcltk/ and this question shows you how to check the version from the interpreter: http://stackoverflow.com/questions/30609214/how-to-change-the-tk-version-of-your-python-installation – James Kent Jan 19 '16 at 16:23
  • The problem seems to occur much more often when Safari is on screen than when it isn't. I guess maybe I'll just ignore it for now... I can look into it if/when customers start noticing, or at least when I'm further along this project and closer to shipping. – ArtOfWarfare Jan 19 '16 at 23:06

1 Answers1

0

Its worth noting that when you bind the event is passed to the function that gets called anyway, so you could change your code to remove the lambdas something like this:

import Tkinter as tk

class Menu(tk.Listbox):
    def __init__(self, master, items, *args, **kwargs):
        tk.Listbox.__init__(self, master, exportselection = False, *args, **kwargs)
        self.pack()

        for item in items:
            self.insert(tk.END, item)

        self.bind('<Enter>',  self.snapHighlightToMouse)
        self.bind('<Motion>', self.snapHighlightToMouse)
        self.bind('<Leave>',  self.unhighlight)

    def snapHighlightToMouse(self, event):
        self.selection_clear(0, tk.END)
        self.selection_set(self.nearest(event.y))

    def unhighlight(self, event=None):
        self.selection_clear(0, tk.END)

m = Menu(None, ['Alice', 'Bob', 'Caroline'])

tk.mainloop()

I know this isn't strictly speaking an answer to your problem, but it could be over complicating/tripping up your code and wouldn't easily fit in a comment. I don't have access to a mac, but could be worth a try.

James Kent
  • 5,763
  • 26
  • 50
  • I will see if removing the overhead from the indirection introduced by the lambdas solves anything when I get home from work later. Thanks for the suggestion. – ArtOfWarfare Jan 19 '16 at 16:19