Tkinter Root Window mouse drag motion becomes slower

Viewed 187

I noticed a lagging behavior when I dragged my Tkinter Root window across the screen (it does not have same motion speed as my mouse cursor). I have created a simple Tkinter app where one of the features is that user can select via button widget(s) to show/hide different Frame widgets which contains other child widgets. I have created all the required widget(s) during init() in a class.

In this example, lets say I have 4 Frame widgets (Frame A, B, C, D) and 4 buttons (Button A, B, C, D), respectively. Each Frame widget(s) contain several child widget(s). So, when I click Button A, the app will show Frame A using .grid(), and hide other Frame(s) using .grid_remove(). However, when I continue my clicking actions on the rest of the Button(s) to display & hide other Frame(s), I noticed it affected my Root window dragging motion when I want to move the app across my screen.

Demonstration GIF

Code:

import tkinter as tk
from functools import partial
from tkinter import ttk

class AppGui(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self, master, *args, **kwargs)

        self.grid_columnconfigure(index = 0, weight = 1)
        self.grid_rowconfigure(index = 1, weight = 1)
        self.main_interface()

    def main_interface(self):
        prnt = tk.Frame(self, highlightthickness = 0, bd = 0)
        self.btn_a = tk.Button(prnt, relief = tk.GROOVE, text = 'Frame A', width = 12, font='Helvetica 11 bold')
        self.btn_b = tk.Button(prnt, relief = tk.GROOVE, text = 'Frame B', width = 12, font='Helvetica 11 bold')
        self.btn_c = tk.Button(prnt, relief = tk.GROOVE, text = 'Frame C', width = 12, font='Helvetica 11 bold')
        self.btn_d = tk.Button(prnt, relief = tk.GROOVE, text = 'Frame D', width = 12, font='Helvetica 11 bold')
        self.btn_a['command'] = partial(self.fr_sel_func, 'A')
        self.btn_b['command'] = partial(self.fr_sel_func, 'B')
        self.btn_c['command'] = partial(self.fr_sel_func, 'C')
        self.btn_d['command'] = partial(self.fr_sel_func, 'D')

        self.btn_a.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (1,5), pady = (1,1), sticky = 'nwse')
        self.btn_b.grid(column = 1, row = 0, columnspan = 1, rowspan = 1, padx = (1,5), pady = (1,1), sticky = 'nwse')
        self.btn_c.grid(column = 2, row = 0, columnspan = 1, rowspan = 1, padx = (1,5), pady = (1,1), sticky = 'nwse')
        self.btn_d.grid(column = 3, row = 0, columnspan = 1, rowspan = 1, padx = (1,1), pady = (1,1), sticky = 'nwse')

        self.hashmap_fr_gui = {}
        # self.create_fr_gui(): Function to create a Frame widget with all the child widgets.
        # Store the Frame widget(s) in a hashmap
        self.hashmap_fr_gui['A'] = self.create_fr_gui('A')
        self.hashmap_fr_gui['B'] = self.create_fr_gui('B') 
        self.hashmap_fr_gui['C'] = self.create_fr_gui('C') 
        self.hashmap_fr_gui['D'] = self.create_fr_gui('D')

        prnt.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (2,1), pady = (1,1), sticky = 'nwse')

        self.btn_a.invoke()

    def fr_sel_func(self, hashmap_id):
       if hashmap_id in self.hashmap_fr_gui:
           for kw, tk_gui in self.hashmap_fr_gui.items():
               if kw == hashmap_id:
                   tk_gui.grid(column = 0, row = 1, columnspan = 1, rowspan = 1, padx = (2,2), pady = (3,1), sticky = 'nwse')
               else:
                   tk_gui.grid_remove()

    def create_fr_gui(self, hashmap_id):
        # Create Parent Frame GUI
        prnt_gui = tk.Frame(self)
        prnt_gui['bg'] = 'white'
        prnt_gui.grid_columnconfigure(index = 0, weight = 1)
        prnt_gui.grid_columnconfigure(index = 1, weight = 1)
        prnt_gui.grid_rowconfigure(index = 0, weight = 1)
        prnt_gui.grid_rowconfigure(index = 1, weight = 1)

        child_gui_a = self.create_fr_child(prnt_gui, 'Subframe 1{}'.format(hashmap_id))
        child_gui_b = self.create_fr_child(prnt_gui, 'Subframe 2{}'.format(hashmap_id))
        child_gui_c = self.create_fr_child(prnt_gui, 'Subframe 3{}'.format(hashmap_id))
        child_gui_d = self.create_fr_child(prnt_gui, 'Subframe 4{}'.format(hashmap_id))

        child_gui_a.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (1,1), pady = (1,1), sticky = 'nwse')
        child_gui_b.grid(column = 1, row = 0, columnspan = 1, rowspan = 1, padx = (3,1), pady = (1,1), sticky = 'nwse')
        child_gui_c.grid(column = 0, row = 1, columnspan = 1, rowspan = 1, padx = (1,1), pady = (3,1), sticky = 'nwse')
        child_gui_d.grid(column = 1, row = 1, columnspan = 1, rowspan = 1, padx = (3,1), pady = (3,1), sticky = 'nwse')

        return prnt_gui

    def create_fr_child(self, prnt, panel_name):
        # Create Child Frame GUI
        child_gui = tk.Frame(prnt, highlightthickness = 1, highlightbackground = 'black')

        child_gui.grid_columnconfigure(index = 0, weight = 1)
        child_gui.grid_columnconfigure(index = 1, weight = 10)

        child_gui.grid_rowconfigure(index = 0, weight = 0)
        child_gui.grid_rowconfigure(index = 1, weight = 1)

        name_tk_lb = tk.Label(child_gui, text = panel_name, font = 'Helvetica 14 bold'
            , justify = tk.LEFT, anchor = 'nw')
        name_tk_lb.grid(column = 0, row = 0, columnspan = 2, rowspan = 1, padx = (5,1), pady = (5,5), sticky = 'nwse')
        #####################################################################################################################
        # Left Side Grid Widget(s)
        left_grid = tk.Frame(child_gui)
        left_grid.grid_columnconfigure(index = 0, weight = 1)
        left_grid.grid(column = 0, row = 1, columnspan = 1, rowspan = 1, padx = (20,1), pady = (1,1), sticky = 'nwse')

        tk_lb = tk.Label(left_grid, text = 'Combobox: ', font = 'Helvetica 12 italic', width = 12
            , justify = 'left', anchor = 'w')
        tk_lb.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (1,5), pady = (1,1), sticky = 'nwse')

        ttk_cbox = ttk.Combobox(left_grid, values = [], width=13, state='readonly', font = 'Helvetica 11')
        ttk_cbox.grid(column = 0, row = 1, columnspan = 1, rowspan = 1, padx = (1,5), pady = (5,1), sticky = 'nwse')

        ttk_cbox.unbind_class("TCombobox", "<MouseWheel>")

        tk_btn = tk.Button(left_grid, relief = tk.GROOVE, width = 10, height = 1, font = 'Helvetica 12')
        tk_btn['text'] = 'Button'

        tk_btn.grid(column = 0, row = 2, columnspan = 1, rowspan = 1, padx = (1,5), pady = (100,1), sticky = 'nwse')

        #####################################################################################################################
        # Right Side Grid Widget(s)
        right_grid = tk.Frame(child_gui)
        right_grid.grid_columnconfigure(index = 0, weight = 5)
        right_grid.grid_columnconfigure(index = 1, weight = 10)
        right_grid.grid_columnconfigure(index = 2, weight = 1)

        right_grid.grid(column = 1, row = 1, columnspan = 1, rowspan = 1, padx = (10,20), pady = (1,1), sticky = 'nwse')

        for i in range(0, 6):
            right_grid.grid_rowconfigure(index = i, weight = 1, min = 50)
            tk_lb  = tk.Label(right_grid, text = 'Scalebar {}'.format(i + 1), font = 'Helvetica 12', justify = 'right', anchor = 'ne')

            scl_var   = tk.StringVar()
            sbox_var  = tk.StringVar()

            tk_scl = tk.Scale(right_grid, from_=1, to=10, variable=scl_var, orient='horizontal', showvalue=0)

            tk_sbox  = tk.Spinbox(right_grid, width = 4, textvariable = sbox_var, from_=1, to=10
                                 , highlightbackground="black", highlightthickness=1, font = 'Helvetica 12')

            scl_var.set(1)
            tk_lb.grid(column = 0, row = i, columnspan = 1, rowspan = 1, padx = (3,1), pady = (3,1), sticky = 'nwes')
            tk_scl.grid(column = 1, row = i, columnspan = 1, rowspan = 1, padx = (3,1), pady = (4,1), sticky = 'nwe')
            tk_sbox.grid(column = 2, row = i, columnspan = 1, rowspan = 1, padx = (3,3), pady = (3,1), sticky = 'nwe')

        return child_gui

if __name__ == '__main__':
    tk_root = tk.Tk()
    tk_root_width = 890
    tk_root_height = 600
    screen_width = tk_root.winfo_screenwidth()
    screen_height = tk_root.winfo_screenheight()
    x_coordinate = int((screen_width/2) - (tk_root_width/2))
    y_coordinate = int((screen_height/2) - (tk_root_height/2))
    tk_root.geometry("{}x{}+{}+{}".format(tk_root_width, tk_root_height, x_coordinate, y_coordinate))
    app_gui = AppGui(tk_root)
    tk_root.grid_columnconfigure(index = 0, weight = 1)
    tk_root.grid_rowconfigure(index = 0, weight = 1)
    app_gui.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (1,1), pady = (1,1), sticky = 'nwse')
    tk_root.mainloop()


The issue seem to be mitigated when i experimented with .grid_destroy() on the Frame widget(s). However, I prefer not to use .grid_destroy(), otherwise I have to re-create my widget(s) again. So to solve this issue, do I have to use .grid_remove() on every child widgets within my Frame widget(s)? Or is there any other advise/solution to this.

EDIT 1: To replicate the lagging behavior when dragging the app window, user need to click each button(s) to display each Frame widgets at least once.

1 Answers

I experienced the same behavior with my Tkinter app and I found out that it's caused by my mouse (SteelSeries Sensei Ten).

When I tried moving the window with a different generic mouse, the "slugish" movement was gone. Finally, I tried messing with my mouse settings and it turned out that lowering my polling rate from 1000 to 250 Hz fixes it as well.

I also found out that this behavior is dependent alongside the polling rate on the number of used widgets and the size of the window.

I guess that a better solution would be to limit the "refresh rate" of your application locally. However, I'm not sure if that's possible.

EDIT: It is possible actually.

from time import sleep


def on_configure(e):
    if e.widget == tk_root:
        sleep(0.015)


if __name__ == '__main__':
    tk_root = tk.Tk()
    tk_root.bind("<Configure>", on_configure)
    tk_root_width = 890
    tk_root_height = 600
    screen_width = tk_root.winfo_screenwidth()
    screen_height = tk_root.winfo_screenheight()
    x_coordinate = int((screen_width/2) - (tk_root_width/2))
    y_coordinate = int((screen_height/2) - (tk_root_height/2))
    tk_root.geometry("{}x{}+{}+{}".format(tk_root_width, tk_root_height, x_coordinate, y_coordinate))
    app_gui = AppGui(tk_root)
    tk_root.grid_columnconfigure(index = 0, weight = 1)
    tk_root.grid_rowconfigure(index = 0, weight = 1)
    app_gui.grid(column = 0, row = 0, columnspan = 1, rowspan = 1, padx = (1,1), pady = (1,1), sticky = 'nwse')
    tk_root.mainloop()

This fixed it for me.

Related