0

According to this question, binding a Configure event type to the root window detects the movement of the sash of a tkinter Panedwindow. For example,

root.bind("<Configure>", resize)

However, I inferred from @BryanOakley's answer that it is not the correct approach.

Hence, what is the correct approach to detect the movement of a sash of a ttk.Panedwindow?

Test Script (based on test script by question):

import tkinter as tk
import tkinter.ttk as ttk


class App(ttk.PanedWindow):

    def __init__(self, parent, orient="horizontal"):
        super().__init__(parent, orient=orient)
        self.parent = parent
        self.frame1 = ttk.Frame(self)
        self.frame2 = ttk.Frame(self)
        self.add(self.frame1)
        self.add(self.frame2)

        # create scrollbars
        self.xsb = ttk.Scrollbar(self.frame2, orient='horizontal')  # create X axis scrollbar and assign to frame2
        self.ysb = ttk.Scrollbar(self.frame2, orient='vertical')  # create Y axis scrollbar and assign to frame2
        self.xsb.pack(side=tk.BOTTOM, fill=tk.X )  # bottom side horizontal scrollbar
        self.ysb.pack(side=tk.RIGHT, fill=tk.Y )  # right side vertical scrollbar

        self.t5 = tk.Text(self.frame2, wrap='none',
                          xscrollcommand=self.xsb.set,
                          yscrollcommand=self.ysb.set)
        for line in range(50):
            self.t5.insert(tk.END, str(line+1) + " Now is the time for all good men to come to the aid of their party. Now is the time for all good men to come to the aid of their party.\n")
        self.t5.pack(expand=True, fill='both') # fill frame with Text widget

        self.bind("<Configure>", self.resize)

    def resize(self, event):
        self.update_idletasks()
        print("width", event.width, "height", event.height, "x", event.x, "y", event.y)

if __name__ == "__main__":
    root = tk.Tk()
    root.title("Test")
    root.geometry('600x400+400+350')

    app = App(root)
    app.pack(fill=tk.BOTH, expand=True)
    
    root.mainloop()
Sun Bear
  • 7,594
  • 11
  • 56
  • 102

2 Answers2

2

Probably the simplest solution is to put the binding on the frames that have been added to the paned window. For each movement of the sash your function will get called once for each child since one frame will grow causing another to shrink.

If you only want an event to trigger when the sash has stopped moving for some short period of time, you can generate a custom virtual event using after. Or, you can just bind to one frame if you only have two panes in the paned window.

Here's an example where the event is triggered only after the sash stops moving for half a second. It uses a global variable for simplicity, though it would be better to create a custom class.

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
pw = ttk.PanedWindow(root, orient="horizontal")
pw.pack(fill="both", expand=True)

f1 = tk.Frame(pw, background="red", width=200, height=100)
f2 = tk.Frame(pw, background="green", width=200, height=100)

def schedule_event(event):
    global after_id

    if after_id:
        pw.after_cancel(after_id)
    after_id = pw.after(500, pw.event_generate, "<<SashMoved>>")

def do_something(event):
    print("do_something was called")

pw.add(f1)
pw.add(f2)

after_id = None
f1.bind("<Configure>", schedule_event)
f2.bind("<Configure>", schedule_event)

pw.bind("<<SashMoved>>", do_something)

tk.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • May I know what the `.event_generate` method/callback does and how to use it? It is only from your example that I learned that the `ttk.PanedWindow` widget even has such a method/callback, which is not documented in [John W Shipman's tkinter documentation](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/ttk-PanedWindow.html). Also, where can i find a comprehensive list of all event types? `"<>"` is not in the documentation either. – Sun Bear Jul 01 '23 at 04:58
  • @SunBear: `event_generate` generates virtual events, which use double angle brackets. The event name can be anything you want. You could generate the event `<>` if you want. All widgets have the method. – Bryan Oakley Jul 01 '23 at 05:21
0

I have rewritten @BryanOakley's example script in the form of a class and commented on the relevant sections. I hope these explanations (of what I have learnt from @BryanOakley) can benefit other tkinter users.

Test Script:

import tkinter as tk
from tkinter import ttk

class App(ttk.PanedWindow):

    def __init__(self, parent, orient="horizontal"):
        super().__init__(parent, orient=orient)
        self.parent = parent
        self.after_id = None
        
        self.f1 = ttk.Frame(self, width=200, height=100, style="Left.TFrame")
        self.f2 = ttk.Frame(self, width=200, height=100, style="Right.TFrame")
        self.add(self.f1)
        self.add(self.f2)
        
        # Whenever the frames in the PanedWindow widget changes size
        # (i.e. experience a configuration change), then execute the event
        # handler self.schedule_event. 
        self.f1.bind("<Configure>", self.schedule_event)
        self.f2.bind("<Configure>", self.schedule_event)
        # Whenever the virtual event "<<SashMoved>>" is detected within the
        # PanedWindow widget, run the event handler self.do_something.  
        self.bind("<<SashMoved>>", self.do_something)

    # Event handlers
    def schedule_event(self, event):
        # If self.after_id has an integer value, then cancel callback that is
        # associated to it, which was created previously via the .after()
        # method. In so doing, only the latest callback would be executed.
        if self.after_id:
            self.after_cancel(self.after_id)
        # Use self.event_generate method to create a virtual event called
        # "<<SashMoved>>" 0.5 sec after this method is executed. It will return
        # an integer that is used to track this .after method, which is stored 
        # in self.after_id.
        self.after_id = self.after(500, self.event_generate, "<<SashMoved>>")

    def do_something(self, event):
        print("do_something was called")

if __name__ == '__main__':
    root = tk.Tk()
    ss = ttk.Style()
    ss.configure('Left.TFrame', background="red")
    ss.configure('Right.TFrame', background="green")
    app = App(root)
    app.pack(fill="both", expand=True)
    root.mainloop()
Sun Bear
  • 7,594
  • 11
  • 56
  • 102