Search code examples
pythontkinterresizetkinter-text

Tkinter - How to resize frame containing a text widget (in all directions)?


I'm trying to resize certain layered tkinter components, mostly because I'm curious. Right now, seems stuck in trying to resize a frame containing a text widget. here is my attempt:

import tkinter as tk

def make_draggable(widget):
    widget.bind("<Button-1>", on_drag_start)
    widget.bind("<B1-Motion>", on_drag_motion)
    widget.bind("<Button-3>", on_resize_start)
    widget.bind("<B3-Motion>", on_resize_motion)

def on_drag_start(event):
    widget = event.widget
    widget._drag_start_x = event.x
    widget._drag_start_y = event.y

def on_drag_motion(event):
    widget = event.widget
    x = widget.winfo_x() - widget._drag_start_x + event.x
    y = widget.winfo_y() - widget._drag_start_y + event.y
    widget.place(x=x, y=y)

def on_resize_start(event):
    widget = event.widget
    widget._resize_start_x = event.x
    widget._resize_start_y = event.y
    widget._resize_width = widget.winfo_width()
    widget._resize_height = widget.winfo_height()

def on_resize_motion(event):
    widget = event.widget
    width = widget._resize_width + event.x - widget._resize_start_x
    height = widget._resize_height + event.y - widget._resize_start_y
    widget.place(width=width, height=height)
    widget.winfo_children()[0].configure(width=width, height=height)

main = tk.Tk()

frame = tk.Frame(main, bd=4, bg="grey")
frame.place(x=10, y=10)
make_draggable(frame)

notes = tk.Text(frame)
notes.pack()

main.mainloop()

It is based on this other answer on SO.

This works, but only when right-clicking and dragging the mouse on the bottom and right side of the frame (the gray part). I don't know how to make it work on the other directions (eg: top and left, and if possible, the edges too)

How can this be done for all directions?

Note: I'm using 3.8.10 and Tk version 8.6.9 (patch level), on Win10


Solution

  • When you resize from the top or the left you should also be changing the position. In addition, the resizing is reversed. If you drag from the left to the right, the width becomes less and the x increases. So first you need to check which border you are dragging.

    def on_resize_motion(event):
        widget = event.widget
        if widget._resize_start_x < 4:
            x = widget.winfo_x() - widget._resize_start_x + event.x
            width = widget.winfo_width() - (event.x - widget._resize_start_x)    
        else:
            x = widget.winfo_x()
            width = widget._resize_width + event.x - widget._resize_start_x
        if widget._resize_start_y < 4:
            y = widget.winfo_y() - widget._resize_start_y + event.y
            height = widget.winfo_height() - (event.y - widget._resize_start_y)
        else:
            y = widget.winfo_y()
            height = widget._resize_height + event.y - widget._resize_start_y    
    

    This subtracts the difference in mouse position if you started on the left or top border. The else part is the same as what you had.

    Finally, you need to update both x and y and the size.

        widget.place(x=x, y=y, width=width, height=height)