1

I've made a Tkinter widget that is essentially a Combobox whose list 1) is visible during typing, 2) shortens during typing and 3) allows for arrow key/enter functionality. I used these two posts (first & second) to figure out some of the functionality. See below for class code and MRE.

My question: My Search widget works for Windows but not Mac OS, and I believe it is due to the fact that the .wm_overrideredirect() method behaves differently in Mac OS (at least for Big Sur) vs Windows. My intended functionality is to have a TopLevel widget only contain & show the Text widget and not receive focus. The code below achieves this in Windows but not in Mac OS. What is an alternative for Mac OS? In other words, are there any alternatives for allowing widgets to extend outside of root?

Clicking on entry widget when running on Windows 10 does the following: windows has dropdown

Clicking on entry widget when running on Mac OS Big Sur does the following: macos has no dropdown

Thanks!

Search Widget

from tkinter import Entry, Text, StringVar, Toplevel, WORD, END

class Search: 
    '''A widget with features of Entry and Combobox that allows for a dropdown list 
    that 1) dynamically reduces listed options to those that match user input and
    2) allows for arrow key/enter and mouseover/click functionality.'''
    def __init__(self, root, lst, width=15, allow_unlisted=True):
        self.root = root 
        self.lst = lst 
        self.current_lst = lst 
        self.width = width 
        self.allow_unlisted = allow_unlisted 
        self.indices = [0.0] * 2

        self.stringvar = StringVar() 
        self.stringvar.trace_add("write", self.updateList) 
        self.entry = Entry(root, width=self.width, font=("Arial", 9), textvariable=self.stringvar) 
        self.entry.bind("<FocusIn>", self.showList) 
        self.entry.bind("<FocusOut>", self.hideList) 
        self.entry.bind("<Up>", self.keyList) 
        self.entry.bind("<Down>", self.keyList) 
        self.entry.bind("<Return>", self.hideList)

        self.toplevel = None 
        self.text = None
        self.updateList()
    
    def showList(self, *args): 
        self.toplevel = Toplevel(self.root) 
        self.toplevel.wm_overrideredirect(True) 
        x = self.entry.winfo_rootx() 
        y = self.entry.winfo_rooty() + self.entry.winfo_height()
        self.toplevel.wm_geometry(f"+{x}+{y}")

        self.text = Text(self.toplevel, wrap=WORD, foreground="#333333")
        self.text.config(font=("Arial", 9), width=self.width)
        self.text.tag_configure("highlight", background="dodger blue", foreground="white") 
        self.text.bind("<Motion>", self.motionList) 
        self.text.bind("<Up>", self.keyList) 
        self.text.bind("<Down>", self.keyList) 
        self.text.bind("<Button-1>", self.hideList) 
        self.text.bind("<Return>", self.hideList)
        self.text.pack()
        self.updateList()
    
    def hideList(self, *args): 
        if self.text and self.text.winfo_ismapped():
            self.select()
        if self.toplevel:
            self.toplevel.destroy()
            self.toplevel = self.text = None
        self.root.focus_set()
    
    def updateList(self, *args): 
        var = self.stringvar.get() 
        self.indices = [0.0] * 2 
        self.current_lst = [item for item in self.lst if var.lower() in item.lower()] 
        if self.text: 
            self.text.tag_remove("highlight", 1.0, END)
            self.text.config(height=min(10, len(self.current_lst)))
            self.text.delete(1.0, END)
            self.text.insert(END, "\n".join(self.current_lst))
    
    def motionList(self, event): 
        index1 = float(self.text.index(f"@{event.x},{event.y} linestart"))
        self.highlight(index1) 
    
    def keyList(self, event):
        if event.keysym == "Up" and self.indices[0] > 0: 
            self.indices[0] -= 1 
        elif event.keysym == "Down" and self.indices[0] < len(self.current_lst): 
            self.indices[0] += 1
        if self.indices[0]: 
            self.highlight(self.indices[0]) 
        else: 
            self.indices = [0.0] * 2 
            self.text.tag_remove("highlight", 1.0, END) 
    
    def highlight(self, index1): 
        self.indices = [index1, float(self.text.index(f"{index1} lineend"))] 
        self.text.tag_remove("highlight", 1.0, END) 
        self.text.tag_add("highlight", *self.indices) 
    
    def select(self, *args):
        if self.indices[0]: 
            self.stringvar.set(self.text.get(*self.indices))
    
    def get(self): 
        user_input = self.stringvar.get() 
        if user_input in self.lst or self.allow_unlisted: 
            return user_input 
        else: return "" 
    
    def set(self, user_input): 
        self.stringvar.set(user_input) 
    
    def __getattr__(self, name): 
        return lambda *args, **kwargs: getattr(Entry, name)(self.entry, *args, **kwargs)

MRE

from tkinter import Tk, Label
from Search import Search # or however you want to import this

# example options for widget
options = ["Option 1", "Option 2", "Option 3", "Rabala", "Ploplepla",
           "This is a very long option compared to others",
           "OPTION 1", "option 2", "option 3", "option1", "option2",
           "option3", "oPTION1"]

root = Tk()

l0 = Label(root, text="Widget:")
r = Search(root, lst=options)
l0.grid(row=0, column=0, padx=10, pady=50)
r.grid(row=0, column=1, padx=10, pady=50)

root.mainloop()
TimH
  • 423
  • 4
  • 14
  • Why do you think this doesn't work on Big Sur? It seems to work for me. How is the behavior different than on other platforms? – Bryan Oakley May 25 '21 at 18:50
  • Have you tried using: `.attributes("-type", "splash")`? I have no MacOS devices to test it but I think it should work. – TheLizzard May 25 '21 at 19:00
  • @BryanOakley It doesn't crash on MacOS if that's what you mean. I've added pictures above to show the functionality differences. – TimH May 25 '21 at 19:03
  • @TheLizzard Thanks for the suggestion, I just gave it a shot and got this error: `_tkinter.TclError: bad attribute "-type": must be -alpha, -fullscreen, -modified, -notify, -titlepath, -topmost, or -transparent`. Maybe that is OS-dependent too? – TimH May 25 '21 at 19:06
  • I'm still not clear what the difference is. Are you saying that clicking on the entry -- without typing -- should make the dropdown appear? It behaves that way for me. – Bryan Oakley May 25 '21 at 19:21
  • @BryanOakley Yes that is the behavior I'm looking for. Glad to hear that it works on your Mac, maybe it is just an issue with my Mac. I'll go ahead and answer this question and accept it when it lets me. – TimH May 25 '21 at 20:06

1 Answers1

0

Looks like some people can get it to work on MacOS Big Sur. Must just be an issue with my machine. Thanks for y'alls help.

TimH
  • 423
  • 4
  • 14