tkinter top-level menues not showing with macOS - python-3.x

Here is the classic code from effbot, which informed:
Toplevel menus are displayed just under the title bar of the root or any other toplevel windows (or on Macintosh, along the upper edge of the screen). To create a toplevel menu, create a new Menu instance, and use add methods to add commands and other menu entries to it.
But not working, No menues found. I expect there are Welcome and Quit to the right side of python. Any thing missed?
System info
Python 3.7.5
macOS Catalina
TkVersion 8.6
import tkinter as tk
def hello():
print('Hi~')
root = tk.Tk()
root.title("MacOS Catalina")
menubar = tk.Menu(root)
root.configure(menu=menubar)
menubar.add_command(label='Welcome', command=hello)
menubar.add_command(label='Quit', command=root.quit)
root.mainloop()

On OSX, you cannot put commands on the root menu. Apple simply doesn't allow it. You can only put other menus (cascades). For example:
...
menubar = tk.Menu(root)
root.configure(menu=menubar)
menu = tk.Menu(menubar)
menu.add_command(label="Welcome", command=hello)
menu.add_command(label="Quit", command=root.quit)
menubar.add_cascade(label="My Menu", menu=menu)

Related

Menu bars of Tkinter in Mac aren't working

I am trying to learn tkinter and am on the menubars part. I am creating a simple GUI window and trying to display the menu bar. But it is not displaying there. In manY places they say that the menu bar will be showing in the mac one and not in the window itself but mine is not showing there also. Here is the code I am using
from tkinter import *
root = Tk()
root.geometry("1000x700")
root.minsize(1000, 700)
root.maxsize(1000, 700)
mainmenu = Menu(root)
mainmenu.add_command(label="hello", command=quit)
mainmenu.add_command(label="exit", command=lambda:print("hello"))
root.config(menu=mainmenu)
root.mainloop()
I tried to create a window with a menubar with this code:
from tkinter import *
root = Tk()
root.geometry("1000x700")
root.minsize(1000, 700)
root.maxsize(1000, 700)
mainmenu = Menu(root)
mainmenu.add_command(label="hello", command=quit)
mainmenu.add_command(label="exit", command=lambda:print("hello"))
root.config(menu=mainmenu)
root.mainloop()
Instead it resulted a blank window with no menubar even in the mac one.
OSX doesn't allow you to put commands directly on the main menu. On the main menu you can only add cascades. This is subtly mentioned in the official documentation:
On the Macintosh, whenever the toplevel is in front, this menu's cascade items will appear in the menubar across the top of the main monitor. On Windows and Unix, this menu's items will be displayed in a menubar across the top of the window.
Notice how it only mentions cascade items for the Mac, but all items for the other platforms.
mainmenu = Menu(root)
root.config(menu=mainmenu)
filemenu = Menu(mainmenu)
mainmenu.add_cascade(label="File", menu=filemenu)
filemenu.add_command(label="hello", command=quit)
filemenu.add_command(label="exit", command=lambda:print("hello"))

Weird white Stripe appearing next to tkinter scrollbar

So while 'designing' my tkinter application i noticed this white stripe appearing next to the scrollbar there:
It is not disappearing when the scrollbar gets to an active state but it is definetly part of the Scrollbar itself because there is nothing under it in my programm which has a white background.
It seems to appear no matter if I use grid or pack. In this case I use grid - here the little extract of my code:
class App(Tk):
def __init__(self):
#other stuff
self.hvf=hvFrame(self,sticky=EW,showfocus=S,bg='white',padx=5,pady=5)
self.hvf.grid(row=1,column=0,columnspan=2,sticky=NSEW,pady=5,padx=(0,5))
sb=Scrollbar(self,orient=VERTICAL,command=self.hvf.yview,bd=0,highlightthickness=0)
sb.grid(row=1,column=2,sticky=NSEW,pady=5)
self.hvf.config(yscrollcommand=sb.set)
If you ned more, then here you go. (Should not be executable for you because you do not have the extras file but you should still be able to understand it.)
EDIT:
A little reproduceable example:
from tkinter import Tk,Frame,Scrollbar,VERTICAL,NS
root = Tk()
frame = Frame(root,height=300,width=500)
frame.grid(row=0,column=0,padx=(0,5),pady=5)
sb = Scrollbar(root,orient=VERTICAL)
sb.grid(row=0,column=1,sticky=NS,pady=5)
root.mainloop()
After testing a few things like changing the windows theme or changing the root and frame background to black the while line still comes up. I cannot be 100% sure but I believe this is due to fact that tkinter on Windows pulls the scrollbar design from Windows itself and it is simply part of that design. It may be a design choice to give the scroll bar some visual depth. That said you cannot do anything to change the design of the scrollbar within a Windows environment so you as stuck with this unless you write a custom scrollbar.
Example code:
import tkinter as tk
root = tk.Tk()
root['bg'] = 'black'
frame = tk.Frame(root, height=300, width=500, background='black')
frame.grid(row=0, column=0)
sb = tk.Scrollbar(root, orient=tk.VERTICAL)
sb.grid(row=0, column=1, sticky=tk.NS)
root.mainloop()
Results from overlay of white and black backgrounds:
Below is the code I use for a custom scrollbar (but did not write myself). You can find the post where I got the code here.

tkinter menu accelerator duplicating callbacks

I'm seeing some strange behavior running Python 3.7.6 and tkinter 8.6 on MacOS 10.14.6.
I've bound "Delete" to a function, and also added a menu command that triggers the same function. I've also added an accelerator option to the menu command, which only displays the text and doesn't actually create the shortcut (as noted here).
Except, in this case, it does seem to create a shortcut: pressing the "Delete" key triggers my function twice: once for the bound command, and a second time from the accelerator. Removing either the binding or the accelerator cause it to only trigger once; of course, removing both causes the keybind to no longer activate. It works fine when triggered from the menu, and this doesn't seem to happen with other keys or key combos.
I'm not sure whether I've missed a detail about Tkinter programming or I'm encountering a technical issue. I made sure to try a different keyboard, and am unsure whether it might be a bug with Tkinter on Mac.
The following code consistently exhibits my issue:
from tkinter import *
def bar(event=None):
print("Called bar")
def foo(event=None):
print("Called foo")
root = Tk()
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
#works fine
filemenu.add_command(label="Something else", command=bar, accelerator="Control-s")
root.bind("<Control-s>", bar)
#triggers twice when using the "Del" key
filemenu.add_command(label="Delete a thing", command=foo, accelerator="Delete")
root.bind("<Delete>", foo)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)
root.mainloop()
Triggering each shortcut once generates the following output:
Called bar
Called foo
Called foo
So this leaves me with two questions:
Can this be replicated by other people?
How might I resolve or work around it?
I also had this issue, and looking at the code it appears that it definitely does add bindings for your accelerators. In my app I worked around it by not binding the key presses on OSX but it's hardly ideal..
There is a Tk bug for this: https://core.tcl-lang.org/tk/tktedit?name=ead70921a9665f57d2fbcfb3cdee51391858bc96
Your code worked fine on my windows system running Python 3.7, but believing there are some issues with tkinter on MacOS, you can try the using the themed tk ttk Menubutton widget and see if it works any better. The look of the ttk Menu might slightly differ from the tk Menu design.
Here is your posted code with the ttk Menubutton:
import tkinter as tk
from tkinter import ttk
def bar(event=None):
print("Called bar")
def foo(event=None):
print("Called foo")
root = tk.Tk()
# create a ttk menubutton
mb = ttk.Menubutton(root, text="File")
# create a cascade of menu options for this menubutton mb
mb.menu = tk.Menu(mb, tearoff=0)
mb.menu.add_command(label="Something else", command=bar, accelerator="Control-s")
root.bind("<Control-s>", bar)
mb.menu.add_command(label="Delete a thing",command=foo, accelerator="Delete")
root.bind("<Delete>", foo)
mb.configure(menu=mb.menu)
mb.pack(side='left')
root.mainloop()
I hope it works!

Tkinter - hide root window

I'm new to stack exchange and am inexperienced with python. I am using python3 and have used SourceFileLoader from importlib.machinery to run another script(which uses tkinter) from my main script. This initially caused an error covered in this question: tkinter.TclError: image "pyimage3" doesn't exist
The solution worked for me in resolving the error, and now the child script runs as intended, except that using the Toplevel() function now also causes the app to create an empty root window - Tk() which I want to hide. I have looked at a number questions' solutions that have not worked: How do I get rid of Python Tkinter root window?
Hide the root window when a Toplevel window is opened and make it reappear when the Toplevel is destroyed
Here is a sample of my code:
from tkinter import *
from PIL import ImageTk
from importlib.machinery import SourceFileLoader
root = Toplevel()
background = Tk()
...
def Puzzle5():
root.overrideredirect(1)
frame = Frame(root, width=320, height=160, borderwidth=2, relief=RAISED)
frame.pack_propagate(False)
frame.pack(side=TOP)
frame1 = Frame(root, width=500, height=150, borderwidth=2, relief=RAISED)
frame1.pack_propagate(False)
frame1.pack(side=BOTTOM)
image = ImageTk.PhotoImage(file="/home/pi/Media/arrowup.png")
image1 = ImageTk.PhotoImage(file="/home/pi/Media/arrowdown.png")
...
background.withdraw()('0x0+0+0')
root.geometry('644x450+150+50')
root.mainloop()
def close():
root.destroy()
background.destroy()
Puzzle5()
Widgets exist in a hierarchy. At the top of that hierarchy is the root window. For any tkinter widget to exist, there must first be a root window.
You can create the root window by creating an instance of Tk. If you do not, then the first time you create a window a root window will be created for you.
Now, consider this code:
root = Toplevel()
background = Tk()
Toplevel is not a root window. For it to exist, there must first be a root window. Since you did not create one, tkinter will create one for you. So, you get a root window, and then you get the instance of Toplevel.
Then you create another root window with the second line, resulting in three windows. Even when you hide background with background.withdraw(), you still have the original root window visible.
The simple solution is to reverse those two lines of code. Create the root window first, and the Toplevel second. Then you only have a single root window, and you can hide it if you wish. However, as the answer to How do I get rid of Python Tkinter root window? explains, an even better solution is to not use a Toplevel at all, but instead put your widgets in root.

Python Tkinter Clickable Text?

I'm wondering if there's a way to make clickable text in Tkinter. Maybe like you would see on a title screen of a game, and where you hover your mouse over the text and it changes color/hightlights itself. All I need the click to do is execute another function.
Are either of these things possible? Thanks!
you are looking for tkinter's events:
tk_widget.bind("<Button-1>",CALLBACK)
The call back needs to take an event argument which is a dictionary containing information about what triggered the event.
This can run into issues with widgets that overlap such as windows in a canvas or labels sometimes triggering the callback for the window behind it.
For hovering the mouse over a widget the event is called "<Enter>" and moving mouse out of widget region is called "<Leave>" for highlighting text effect, if you just want to capture a click anywhere on a window then on the root call root.bind_all("<Button-1>",CALLBACK)
source: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/events.html
example:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
def change_case(event=None):
new_text = str.swapcase(lab["text"])
lab.config(text=new_text)
def red_text(event=None):
lab.config(fg="red")
def black_text(event=None):
lab.config(fg="black")
root = tk.Tk()
lab = tk.Label(root,text="this is a test")
lab.bind("<Button-1>",change_case)
lab.bind("<Enter>",red_text)
lab.bind("<Leave>",black_text)
lab.grid()
root.mainloop()
hope this helps :)

Resources