Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter Root Window mouse drag motion becomes slower

Tags:

python

tkinter

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.

like image 712
jul Avatar asked Nov 15 '25 04:11

jul


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.

like image 73
nespe Avatar answered Nov 17 '25 18:11

nespe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!