1

I need to find out how to keep a constant aspect ratio for a window on Tkinter.

I tried below code, but it only works when dragging bottom or top edges, but not for edges to the right or left. On all cases when dragging, the window is resized accordingly, but for non-ok cases when releasing, window's size returns to the size it had when I started dragging:

import tkinter as tk

class Window():
    _windowAspectRatio = None
    _resizing = False
    def __init__(self):
        self._root = tk.Tk()
        
        self._root.bind("<Configure>", self._on_resize)
        self._root.resizable(True,True)
        
        self._root.state("zoomed")

        self._root.update()#

        self._windowWidth = self._root.winfo_width()
        self._windowHeigth = self._root.winfo_height()
        self._windowAspectRatio = self._windowWidth/self._windowHeigth

        if 742/self._windowAspectRatio < 450:
            minHeight = 450
            minWidth = int(450 * self._windowAspectRatio)
        else:
            minHeight = int(742/self._windowAspectRatio)
            minWidth = 742

        self._root.minsize(minWidth,minHeight)

    def _on_resize(self,event):
        if event.widget == self._root:
            if not self._resizing:
                self._resizing = True
                widthToApply = self._root.winfo_width()
                heightToApply = self._root.winfo_height()
                if self._windowAspectRatio != None:
                    desiredWidth = int(event.height * self._windowAspectRatio)
                    desiredHeight = int(event.width / self._windowAspectRatio)
                    if abs(event.width - self._windowWidth) > abs(event.height - self._windowHeigth):
                        widthToApply = event.width
                        heightToApply = desiredHeight
                    else:
                        widthToApply = desiredWidth
                        heightToApply = event.height
                    self._root.geometry(f"{widthToApply}x{heightToApply}")
                    self._windowWidth = self._root.winfo_width()
                    self._windowHeigth = self._root.winfo_height()
                self._root.update()
                self._resizing = False

window = Window()

def on_close():
    global running
    running = False

window._root.protocol("WM_DELETE_WINDOW", on_close)

running = True

while running:
    window._root.update()

1 Answer 1

2

There are several issues with your code. However, here is the corrected version:

import tkinter as tk

class Window():
    def __init__(self):
        self._root = tk.Tk()
        self._root.title("Fixed Aspect Ratio Window")
        
        self._root.state("zoomed")
        self._root.update()
        
        self.width = self._root.winfo_width()
        self.height = self._root.winfo_height()
        self.aspect_ratio = self.width / self.height
        
        self._root.minsize(400, int(400 / self.aspect_ratio))
        
        self._root.bind("<Configure>", self._enforce_aspect_ratio)
        
    def _enforce_aspect_ratio(self, event):
        if event.widget != self._root:
            return
            
        if hasattr(self, "_updating_geometry"):
            return
            
        new_width = event.width
        new_height = event.height
        
        width_changed = (new_width != self.width)
        height_changed = (new_height != self.height)
        
        if not (width_changed or height_changed):
            return
            
        if width_changed and not height_changed:
            desired_height = int(new_width / self.aspect_ratio)
            self._updating_geometry = True
            self._root.geometry(f"{new_width}x{desired_height}")
            self._updating_geometry = False
        elif height_changed and not width_changed:
            desired_width = int(new_height * self.aspect_ratio)
            self._updating_geometry = True
            self._root.geometry(f"{desired_width}x{new_height}")
            self._updating_geometry = False
        else:
            width_diff = abs(new_width - self.width)
            height_diff = abs(new_height - self.height)
            
            if width_diff > height_diff:
                desired_height = int(new_width / self.aspect_ratio)
                self._updating_geometry = True
                self._root.geometry(f"{new_width}x{desired_height}")
                self._updating_geometry = False
            else:
                desired_width = int(new_height * self.aspect_ratio)
                self._updating_geometry = True
                self._root.geometry(f"{desired_width}x{new_height}")
                self._updating_geometry = False
        
        self.width = self._root.winfo_width()
        self.height = self._root.winfo_height()

window = Window()

def on_close():
    global running
    running = False

window._root.protocol("WM_DELETE_WINDOW", on_close)

running = True
while running:
    window._root.update_idletasks()
    window._root.update()

You may like this simple approach also:

import tkinter as tk
from fractions import Fraction

class Window:
    def __init__(self):
        self._root = tk.Tk()
        width = 800
        height = 600
        self._root.geometry(f"{width}x{height}")
        aspect_ratio = Fraction(width, height).limit_denominator()
        self._root.wm_aspect(aspect_ratio.numerator, aspect_ratio.denominator,
                             aspect_ratio.numerator, aspect_ratio.denominator)
        self._root.minsize(400, int(400 * aspect_ratio.denominator / aspect_ratio.numerator))
        self._root.mainloop()

Window()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.