Visual artifacts when resizing PanedWindow in tkinter

748 Views Asked by At

I am working on an App in tkinter. Can not post the code because it is too complicated and the artifacts would not be visible in simplified version.

You can see on the attached video that if I resize the whole window, it looks not bad. It is not perfect smooth but it is ok. But when I use PanedWindos sashes to resize, a lot off artifacts is visible during resizing. In the end when I release the mouse button it looks ok.

Do you have experience with this? Some tips what could cause such a behavior?
youtube video with resizing

resizing

EDIT:

I have created minimal version which reproduces the behavior. ScrollFrame class makes the artifacts more noticeable.

import tkinter as tk
import random


class ScrollFrame(tk.Frame):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)

        self.canvas = tk.Canvas(self, borderwidth=0, highlightthickness=0)
        self.canvas.bind("<Enter>", self._bind_mouse)
        self.canvas.bind("<Leave>", self._unbind_mouse)
        self.viewPort = tk.Frame(self.canvas)
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas_window = self.canvas.create_window((4, 4), window=self.viewPort,
                                                       anchor="nw",
                                                       tags="self.viewPort",
                                                       )

        self.viewPort.bind("<Configure>", self.onFrameConfigure)
        self.canvas.bind("<Configure>", self.onCanvasConfigure)

        self.onFrameConfigure(None)

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox(
            "all"))

    def onCanvasConfigure(self, event):
        '''Reset the canvas window to encompass inner frame when required'''
        canvas_width = event.width
        self.canvas.itemconfig(self.canvas_window,
                               width=canvas_width)

    def _bind_mouse(self, event=None):
        self.canvas.bind_all("<4>", self._on_mousewheel)
        self.canvas.bind_all("<5>", self._on_mousewheel)
        self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)

    def _unbind_mouse(self, event=None):
        self.canvas.unbind_all("<4>")
        self.canvas.unbind_all("<5>")
        self.canvas.unbind_all("<MouseWheel>")

    def _on_mousewheel(self, event):
        """Linux uses event.num; Windows / Mac uses event.delta"""
        if self.vsb.get() != (0, 1):
            if event.num == 4 or event.delta > 0:
                self.canvas.yview_scroll(-1, "units")
            elif event.num == 5 or event.delta < 0:
                self.canvas.yview_scroll(1, "units")


class OptionAttribute:
    def __init__(self, target_widget, name, options, unit):
        self.name = tk.Label(target_widget, text=name)
        self.input = tk.Button(target_widget, text=options[0], anchor='w',
                               command=lambda: self.show_options(self.input), relief='flat', bg='white', bd=0)
        self.menu = tk.Menu(target_widget, tearoff=0)
        for o in options:
            self.menu.add_command(label=o, command=lambda selected=o: self.input.configure(text=selected))
        self.unit = tk.Label(target_widget, text=unit)

    def show_options(self, widget):
        self.input.focus_set()
        self.menu.post(widget.winfo_rootx(), widget.winfo_rooty())


if __name__ == '__main__':
    root = tk.Tk()
    root.minsize(width=640, height=480)
    root.grid_columnconfigure(0, weight=1)
    root.grid_rowconfigure(0, weight=1)

    pw = tk.PanedWindow(root, orient='horizontal', sashrelief='ridge', sashwidth=8)
    f1 = tk.Frame(pw)
    pw.add(f1, stretch='always', minsize=200, width=400)

    f2 = tk.Frame(pw)
    f2.grid_columnconfigure(0, weight=1)
    f2.grid_rowconfigure(0, weight=1)

    f2_scroll = ScrollFrame(f2)
    f2_scorr_inner = f2_scroll.viewPort
    f2_scorr_inner.grid_columnconfigure(1, weight=80)
    f2_scorr_inner.grid_columnconfigure(2, weight=20)
    f2_scroll.grid(sticky='nsew')

    option_attrs = [OptionAttribute(f2_scorr_inner,
                                    str(random.randint(0, 100000)),
                                    [str(random.randint(0, 100000)) for __ in range(10)],
                                    random.choice(['mm', 'nm', 'kg', 'h', 'km', ])
                                    ) for _ in range(100)]
    r = 0
    for oa in option_attrs:
        oa.name.grid(column=0, row=r, sticky='w', padx=(0, 10))
        oa.input.grid(column=1, row=r, sticky='w')
        oa.unit.grid(column=2, row=r, sticky='w')
        r += 1
        tk.Frame(f2_scorr_inner, height=1, bg="gray80").grid(column=0, row=r, sticky="ew", columnspan=3)
        r += 1

    pw.add(f2, stretch='always', minsize=200, width=400)
    pw.grid(sticky='nsew')

    root.mainloop()

enter image description here

1

There are 1 best solutions below

0
daikini On

This is not a solution, but rather a way to mask gui's ugly behavior towards the user.

pw = PanedWindow()    
pw.configure(opaqueresize=False)