Last active
July 23, 2022 14:26
-
-
Save israel-dryer/a8796c3cec7da1553ecb5425e0dd202b to your computer and use it in GitHub Desktop.
How to position a topside relative to another widget with and without titlebar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tkinter as tk | |
from tkinter import ttk | |
""" | |
The demo shows how to position a topside window relative to a button widget from multiple directions. The purpose | |
is to show that one could theoretically create a custom menu from a toplevel that could be positioned correctly | |
aligned to the parent widget with and without a titlebar. | |
The problem with positioning a topside relative to another widget is due to the fact that, normally, window | |
decorations are added to the window. This adds additional horizontal a vertical space that is not accounted for in | |
the screen coordinates calculated by `winfo_rootx` and `winfo_x`. However, using these two measurements together, | |
you can calculate the amount of space allocated to the borders and titlebar and adjust your screen position | |
accordingly with these offsets. | |
The calculate the offset of the x and y position by taking `winfo_x/y` - `winfo_rootx/y`. This difference is then | |
added to the x or y position to adjust for the border and titlebar depending on how you want the topside aligned to | |
the parent. | |
""" | |
class Application(tk.Tk): | |
def __init__(self, show_menu_titlebar=False): | |
super().__init__() | |
self.title('Custom Menu Demo') | |
self.geometry('+500+500') | |
self.style = ttk.Style() | |
self.style.theme_use('clam') | |
self.style.configure('menu.TFrame', relief='raised') | |
self.frame = ttk.Frame(self, padding=20) | |
self.frame.pack() | |
ttk.Label(self.frame, text='Choose a direction and push the button').pack() | |
values = ['wn','ws','sw','se','es','en','ne','nw'] | |
self.cbo = ttk.Combobox(self.frame, values=values, foreground='black') | |
self.cbo.current(0) | |
self.cbo.pack(fill=tk.X, padx=10, pady=10) | |
self.titlevar = tk.BooleanVar(value=show_menu_titlebar) | |
self.show_titlebar = ttk.Checkbutton(self.frame, text="Show titlebar", variable=self.titlevar) | |
self.show_titlebar.pack(padx=10, pady=10) | |
self.mb = ttk.Button(self.frame, text='Show Menu', command=self.show_menu) | |
self.mb.pack(fill=tk.X, padx=10, pady=30) | |
self.top = tk.Toplevel() | |
self.top.protocol('WM_DELETE_WINDOW', self.hide_menu) | |
self.top.state('withdrawn') | |
self.topframe = ttk.Frame(self.top, padding=5, style='menu.TFrame') | |
self.topframe.pack(fill=tk.BOTH) | |
self.option_var = tk.Variable(value=1) | |
for x in range(5): | |
b = ttk.Radiobutton(self.topframe, text=f'Option {x+1}', value=x, variable=self.option_var) | |
b.configure(command=self.hide_menu) | |
b.grid(row=x, sticky=tk.EW) | |
def hide_menu(self): | |
self.top.state('withdrawn') | |
def show_menu(self): | |
"""Update the toplevel position and display""" | |
# calculate offset for window decorations if not overrideredirect | |
show_titlebar = bool(self.titlevar.get()) | |
if show_titlebar: | |
self.top.overrideredirect(0) | |
self.offset_x = self.winfo_x() - self.winfo_rootx() | |
self.offset_y = self.winfo_y() - self.winfo_rooty() | |
else: | |
self.top.overrideredirect(1) | |
self.offset_x = 0 | |
self.offset_y = 0 | |
self.top.state('normal') | |
direction = self.cbo.get() | |
parent_x = self.mb.winfo_rootx() | |
parent_y = self.mb.winfo_rooty() | |
parent_height = self.mb.winfo_height() | |
parent_width = self.mb.winfo_width() | |
self.update_idletasks() | |
top_width = self.top.winfo_width() | |
top_height = self.top.winfo_height() | |
self.top.focus() | |
self.top.lift() | |
if direction == 'wn': | |
# West-North | |
xpos = parent_x - top_width | |
ypos = parent_y | |
elif direction == 'ws': | |
# West-South | |
xpos = parent_x - top_width | |
ypos = parent_y + parent_height - top_height + self.offset_y | |
elif direction == 'sw': # this is likely what is needed for option menu | |
# South-West | |
xpos = parent_x + self.offset_x | |
ypos = parent_y + parent_height | |
elif direction == 'se': | |
# South-East | |
xpos = parent_x + parent_width - top_width + self.offset_x | |
ypos = parent_y + parent_height | |
elif direction == 'es': | |
# West-South | |
xpos = parent_x + parent_width + self.offset_x | |
ypos = parent_y + parent_height - top_height + self.offset_y | |
elif direction == 'en': | |
# East-North | |
xpos = parent_x + parent_width + self.offset_x | |
ypos = parent_y | |
elif direction == 'ne': | |
# North-East | |
xpos = parent_x + parent_width - top_width + self.offset_x | |
ypos = parent_y - top_height + self.offset_y | |
elif direction == 'nw': | |
# North-West | |
xpos = parent_x + self.offset_x | |
ypos = parent_y - top_height + self.offset_y | |
self.top.geometry(f'+{xpos}+{ypos}') | |
if __name__ == '__main__': | |
app = Application(show_menu_titlebar=False) | |
app.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment