TomSchimansky / CustomTkinter

A modern and customizable python UI-library based on Tkinter
MIT License
11.58k stars 1.08k forks source link

Screen jitter when changing the state of the SegmentedButton widget #2121

Open DimaTepliakov opened 11 months ago

DimaTepliakov commented 11 months ago

Description: I have encountered an issue with the segmented button widget that causes screen jitter when changing its state. Here's a detailed description of the problem:

I created a view that includes two radio buttons belonging to the same group. Each button controls the state of a different widget—one for the segmented button and the other for the slider. The upper button enables the segmented button and disables the slider, while the lower button does the opposite.

Issue: Upon changing the state of these radio buttons, I observed a noticeable jitter or refresh effect on the segmented button widget. The jitter is disruptive to the user experience and seems unexpected.

Expected Behavior: I would expect a smooth transition between the two states without any noticeable jitter or refresh effect.

Video: https://github.com/TomSchimansky/CustomTkinter/assets/69217590/9b3dfaac-760e-46a5-9d71-467801a62dc6

Example Code:

# Imports
import tkinter as tk
import customtkinter as ctk

class ShiftFrame(ctk.CTkFrame):

    def __init__(self, parent):
        super().__init__(parent)
        self.grid(sticky="nsew")

        self.max_adaptive_shift = 256
        self.max_adaptive_shift_values = [256, 512, 1024, 2048]
        self.constant_shift     = 0
        self.is_max_adaptive    = True

        self.create_widgets()

    def create_widgets(self):

        # Shift Title
        self.shift_label = ctk.CTkLabel(self, text="Shift", font=ctk.CTkFont(size=16, weight='bold'))
        self.shift_label.grid(row=0, column=0, padx=10, pady=10, sticky="w")

        self.shift_radio_btn_int_var = tk.IntVar(value=int(self.is_max_adaptive))
        # Max Adaptive Shift Radio Button
        self.max_adaptive_shift_radio_button = ctk.CTkRadioButton(master=self, 
                                                                  text='Max Adaptive:', 
                                                                  font=ctk.CTkFont(size=15), 
                                                                  variable=self.shift_radio_btn_int_var, 
                                                                  value=0, 
                                                                  command=self.max_adaptive_shift_radio_button_event)
        self.max_adaptive_shift_radio_button.grid(row=1, column=0, padx=20, sticky="wn")

        # Max Adaptive Shift Segmented Button
        self.max_adaptive_shift_seg_button = ctk.CTkSegmentedButton(master=self, 
                                                                    values=self.max_adaptive_shift_values, 
                                                                    font=ctk.CTkFont(size=15))
        self.max_adaptive_shift_seg_button.grid(row=1, column=1, padx=(0, 10), sticky="nw")
        self.max_adaptive_shift_seg_button.set(self.max_adaptive_shift)

        # Constant Shift Radio Button
        self.constant_shift_radio_button = ctk.CTkRadioButton(master=self, 
                                                              text='Constant:', 
                                                              font=ctk.CTkFont(size=15), 
                                                              variable=self.shift_radio_btn_int_var, 
                                                              value=1, 
                                                              command=self.constant_shift_radio_button_event)
        self.constant_shift_radio_button.grid(row=2, column=0, padx=20, pady=10, sticky="wn")

        # Constant Shift Slider
        self.constant_shift_value = ctk.IntVar(value=self.constant_shift)
        self.constant_shift_slider = ctk.CTkSlider(self, 
                                                   width=120, 
                                                   variable=self.constant_shift_value,
                                                   from_=self.max_adaptive_shift_values[0], 
                                                   to=self.max_adaptive_shift_values[-1], 
                                                   number_of_steps=(len(self.max_adaptive_shift_values)-1))
        self.constant_shift_slider.grid(row=2, column=1, padx=(0,10), pady=10, sticky="we")

        if self.is_max_adaptive:
            self.max_adaptive_shift_radio_button_event()
        else:
            self.constant_shift_radio_button_event()

    def max_adaptive_shift_radio_button_event(self):

        print(f'Max Adaptive Shift')
        self.max_adaptive_shift_seg_button.configure(state=tk.NORMAL)
        self.constant_shift_slider.configure(state=tk.DISABLED)

    def constant_shift_radio_button_event(self):
        print('Constant Shift')
        self.max_adaptive_shift_seg_button.configure(state=tk.DISABLED)
        self.constant_shift_slider.configure(state=tk.NORMAL)

# Usage example:
if __name__ == "__main__":
    root = ctk.CTk()
    root.title("Shift Frame")

    shift_frame = ShiftFrame(root)
    root.mainloop()

Is it solvable?: I am seeking guidance on whether this issue is solvable and, if so, any potential solutions or workarounds.

Thank you for your attention to this matter.

Akascape commented 11 months ago

Try to make it non resizable using root.resizable(False, False).

DimaTepliakov commented 11 months ago

Try to make it non resizable using root.resizable(False, False).

Dear @Akascape, thanks for advise. But I already tried it and unfortunately it did not fixed it.

dipeshSam commented 11 months ago

@DimaTepliakov Solved! I also improved some basic functionality of the class you provided: 1. Disable multiple clicks while a click is already made. 2. Don't rely on width, use "ew" or "we", if you have the dedicated frame.

Okay. To resolve this issue, make the dynamic_resizing option False.

Video ezgif-3-1cdc75bf58

Here is the complete code:

import customtkinter as ctk

class ShiftFrame(ctk.CTkFrame):
    def __init__(self, parent):
        super().__init__(parent)

        self.max_adaptive_shift = 256
        self.max_adaptive_shift_values = [256, 512, 1024, 2048]
        self.constant_shift     = 0
        self.is_max_adaptive    = True

        self.create_widgets()

    def create_widgets(self):

        # Shift Title
        self.shift_label = ctk.CTkLabel(self, text="Shift", font=ctk.CTkFont(size=16, weight='bold'))
        self.shift_label.grid(row=0, column=0, padx=10, pady=10, sticky="w")

        self.shift_radio_btn_int_var = ctk.IntVar(value=int(self.is_max_adaptive))
        # Max Adaptive Shift Radio Button
        self.max_adaptive_shift_radio_button = ctk.CTkRadioButton(master=self, 
                                                                  text='Max Adaptive:', 
                                                                  font=ctk.CTkFont(size=15), 
                                                                  variable=self.shift_radio_btn_int_var, 
                                                                  value=0, 
                                                                  command=self.max_adaptive_shift_radio_button_event)
        self.max_adaptive_shift_radio_button.grid(row=1, column=0, padx=20, sticky="wn")

        # Max Adaptive Shift Segmented Button
        self.max_adaptive_shift_seg_button = ctk.CTkSegmentedButton(master=self, 
                                                                    values=self.max_adaptive_shift_values, 
                                                                    dynamic_resizing=False, # If True, this causes Seg Button auto resize over every update.
                                                                    font=ctk.CTkFont(size=15))
        self.max_adaptive_shift_seg_button.grid(row=1, column=1, padx=(0, 10), sticky="we")
        self.max_adaptive_shift_seg_button.set(self.max_adaptive_shift)

        # Constant Shift Radio Button
        self.constant_shift_radio_button = ctk.CTkRadioButton(master=self, 
                                                              text='Constant:', 
                                                              font=ctk.CTkFont(size=15), 
                                                              variable=self.shift_radio_btn_int_var, 
                                                              value=1, 
                                                              command=self.constant_shift_radio_button_event)
        self.constant_shift_radio_button.grid(row=2, column=0, padx=20, pady=10, sticky="wn")

        # Constant Shift Slider
        self.constant_shift_value = ctk.IntVar(value=self.constant_shift)
        self.constant_shift_slider = ctk.CTkSlider(self, 
                                                   variable=self.constant_shift_value,
                                                   from_=self.max_adaptive_shift_values[0], 
                                                   to=self.max_adaptive_shift_values[-1], 
                                                   number_of_steps=(len(self.max_adaptive_shift_values)-1))  # Removed the `width` and expanded to its column as the frame if dedicated for this.
        self.constant_shift_slider.grid(row=2, column=1, padx=(0,10), pady=10, sticky="we")

        if self.is_max_adaptive:
            self.max_adaptive_shift_radio_button_event()
        else:
            self.constant_shift_radio_button_event()

    def max_adaptive_shift_radio_button_event(self):
        print(f'Max Adaptive Shift')
        self.max_adaptive_shift_seg_button.configure(state="normal")

        # Selecting the button and preventing from rest/after clicks.
        self.max_adaptive_shift_radio_button.select()
        self.max_adaptive_shift_radio_button.configure(state="readonly")
        self.constant_shift_radio_button.configure(state="normal")

        self.constant_shift_slider.configure(state="disabled")
        self.master.update()

    def constant_shift_radio_button_event(self):
        print('Constant Shift')
        self.constant_shift_slider.configure(state="normal")

        # Selecting the button and preventing from rest/after clicks.
        self.constant_shift_radio_button.select()
        self.constant_shift_radio_button.configure(state="readonly")
        self.max_adaptive_shift_radio_button.configure(state="normal")

        self.max_adaptive_shift_seg_button.configure(state="disabled")
        self.master.update()

# Usage example:
if __name__ == "__main__":
    root = ctk.CTk()
    root.title("Shift Frame")

    shift_frame = ShiftFrame(root)
    shift_frame.grid(padx=20, pady=20)  # Use the `.grid(sticky="nsew")` outside of the class if possible.

    root.mainloop()
DimaTepliakov commented 11 months ago

@dipeshSam Thanks alot! it worked.

dipeshSam commented 11 months ago

@dipeshSam Thanks alot! it worked.

@DimaTepliakov You're most welcome! Happy customtkinter :)