I have the following problem. I created a desktop application to generate invoices for my dad's company. The machine I wrote this application for is an iMac from 2011 running macOS High Sierra (10.13.6). Because I have no Mac to test on and the age of the target system, I choose tkinter to implement the GUI mainly for its cross-platform compatibility (and because i taught that tkinter is kind of old so it has good chances to run on an old osx).
Now that my application is nearly finished and I had a chance to test it under OS X, I found a bug regarding the display of pages. My application has several pages that the user can select from. The pages are implemented as frames with the corresponding contents placed on top. For switching between pages, I wrote a widget called "pageSwitcher". This widget stores the current page object in a class variable (self.currentlyDisplayedPage), and if a page switch is triggered, .grid_remove() the current page, stores the page to switch to (new current page) into the variable, and displays the new current page via a .grid() call.
Issue stated here
Under Linux (Ubuntu 24.04.3 LTS), this works just fine. But under OSX (10.13.6), I get a different behavior. When I first switch to a page, it displays correctly. But if I try to switch to a page that was displayed before in the current runtime, it is just not displayed until I do one of the following:
If I click outside the application (desktop, for example), the page will be displayed correctly. Also, if I move the mouse over elements that have the <Enter> or <Leave> event binds, the page will display as soon as I move the mouse over them. Additionally, interactive elements like entries and buttons are working; as soon as I click into an entry or on a button, the page is displayed, and in the case of a button, the corresponding functionality is executed. As I see it, the new page is set, and all the widgets are there, but they are not shown until i perform one of the operations detailed above.
I did try to fix this by:
Using .update_idletasks() to force the page to be displayed. This made it sometimes better, sometimes worse (the page will display, but it can take minutes at worst). So I decided this is not a solution. I also tried to use a different Python version (from 3.12.12 to 3.14.2), hoping this was a bug in a specific version (which it seems it was not). I tested this under OSX Sequoia (15.7.3) with (sadly) the exact same results so i assume it is not a bug in a specific OSX version.
I do not know why this happens or how to fix it. Here is a minimal example that produces the error using empty (colored) frames as pages:
import tkinter as tk
class PageSwitcher(tk.Frame):
def __init__(self, parent:tk, desiredHeight:int, desiredWidth:int) -> None:
self.managedPages:list[tk.Frame] = []
self.currentlyDisplayedPage:tk.Frame = None
tk.Frame.__init__(self, parent, height=desiredHeight, width=desiredWidth)
self.grid_propagate(False)
self.rowconfigure(index=(0,2), weight=1)
self.columnconfigure(index=(0,2), weight=1)
def addPage(self, pageToAdd:tk.Frame) -> None:
self.managedPages.append(pageToAdd)
def displayPage(self, pageId:int) -> None:
if self.currentlyDisplayedPage != None:
self.currentlyDisplayedPage.grid_remove()
self.currentlyDisplayedPage = self.managedPages[pageId]
self.currentlyDisplayedPage.grid(row=1, column=1)
class PageSelector(tk.Frame):
def __init__(self, parent:tk, desiredHeight:int, desiredWidth:int, pageSwitchFunction) -> None:
self.pageSwitchFunction = pageSwitchFunction
self.width = desiredWidth
self.managedSelectors:list[PageSelector.Selector] = []
tk.Frame.__init__(self, parent, height=desiredHeight, width=desiredWidth)
self.grid_propagate(False)
def registerPage(self, pageName:str, pageId:int):
selector = self.Selector(
parent=self,
desiredWidth=self.width-10, #For 5px of padding on each side of selector
name=pageName,
pageId=pageId,
selectFunction=self.select
)
selector.grid(row=self.grid_size()[1], column=0, padx=5, pady=2)
self.managedSelectors.append(selector)
def select(self, pageId) -> None:
self.pageSwitchFunction(pageId)
class Selector(tk.Frame):
def __init__(self, parent:tk, name:str, pageId:int, selectFunction, desiredWidth:int) -> None:
self.pageId = pageId
self.selectFunction = selectFunction
tk.Frame.__init__(self, parent, height=32, width=desiredWidth, border=2, relief="raised")
self.grid_propagate(False)
self.name = tk.Label(self, text=name, font="Bold")
self.name.grid(row=1, column=0, padx=5)
self.bind("<Button-1>", self.select)
self.name.bind("<Button-1>", self.select)
def select(self, event:tk.Event) -> None:
self.selectFunction(self.pageId)
def main():
height = 600
width = 600
window = tk.Tk()
window.geometry(f"{width}x{height}")
pageSwitcher = PageSwitcher(window, height, width//2)
pageSelector = PageSelector(window, height, width//2, pageSwitcher.displayPage)
pageSwitcher.grid(row=0, column=1)
pageSelector.grid(row=0, column=0)
pageNamesAndColors = ["red", "orange", "yellow", "green", "lime", "lightBlue", "blue", "purple", "pink"]
for index, color in enumerate(pageNamesAndColors):
newPage = tk.Frame(pageSwitcher, height=height, width=width//2, bg=color)
pageSwitcher.addPage(newPage)
pageSelector.registerPage(pageName=f"{color} Page", pageId=index)
window.mainloop()
main()
I use Python 3.14.2 with Tcl/Tk 8.6.17. I use the python and tkinter from macports.
I build a standalone ondir application using pyinstaller. But the error arises when running the code directly and when running the app build with pyinstaller.
If somebody knows a solution that does not need me to fix Tcl/Tk myself (I read somewhere that Tcl/Tk is bugged under OSX) or to rewrite my entire app in a different GUI framework that "I know works under OSX", please let me know.
focus_forceon the widget?updateor some sort of loop or anything out of the ordinary ?<Enter>/<Leave>events and then move the mouse programmatically to trigger those events hopefully temporarily fixing the issue