I have a program that needs to open Toplevel
windows except the main Tk()
window in tkinter. In the main window I have a Scale
widget which is updated every 100 miliseconds with the after
call. However in a state where the Toplevel window is open and the scale is updated when I press down the 'X' button in the Toplevel
window the Scale
stops moving.
This is my code:
from tkinter import Tk, Toplevel, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
num = 0
def main():
global num
slider.set(num)
num += 1
slider.after(500, main)
def toplevel():
win = Toplevel()
root.bind('<space>', lambda x: [main(), toplevel()])
root.mainloop()
When I stop pressing the 'X' button the Scale jumps to the point it should be
How can I keep the slider/scale flowing normally even when I hold down the 'X' button?
And also why does this happen?
Thanks in advance!
This issue would happen on Windows. Your code works fine on Linux.(I've tested it)
A possible reason is here:
What is happening here (simplyfied a lot) is that as soon as Windows detects a button-down event on the non-client area it stops sending update messages, gets a snapshot of the window and gets ready to start drawing all those nice effects for window-moving, -resizing, etc. The window then stays frozen until the corresponding mouse-up ends the impasse.
This post also mentioned another solution: use thread.
Due to tkinter is single-threaded and those features are packaged, it seems using thread doesn't work in tkinter.
The cause is how operate system handle those "holding down" events on the title bar.
An easy solution is just hiding your title bar, and custom these buttons by yourself.(Avoid OS handling those events.) Like:
from tkinter import Tk, Toplevel, Scale
import tkinter as tk
class CustomToplevel(Toplevel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__offset_x = 100
self.__offset_y = 100
self.window_width = 100
self.window_height = 100
self.overrideredirect(True)
self.title_bar_frame = tk.Frame(self, bg="grey")
self.title_bar_frame.pack(fill="x")
self.title_bar_frame.bind('<Button-1>', self.__click)
self.title_bar_frame.bind('<B1-Motion>',self.__drag)
self.close_button = tk.Button(self.title_bar_frame, text="X", bg="red", font=("", 15),
command=self.destroy)
self.close_button.pack(side="right", fill="y")
self.geometry(f"{self.window_width}x{self.window_height}+{self.winfo_pointerx() - self.__offset_x}+{self.winfo_pointery() - self.__offset_y}")
def __click(self, event):
self.__offset_x = event.x
self.__offset_y = event.y
def __drag(self, event):
self.geometry(f"{self.window_width}x{self.window_height}+{self.winfo_pointerx() - self.__offset_x}+{self.winfo_pointery() - self.__offset_y}")
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
num = 0
def main():
global num
slider.set(num)
num += 1
slider.after(500, main)
def toplevel():
win = CustomToplevel()
root.bind('<space>', lambda x: [main(), toplevel()])
root.mainloop()
Binding some events or using some nice color makes your UI prettier.
I have a program that needs to open Toplevel
windows except the main Tk()
window in tkinter. In the main window I have a Scale
widget which is updated every 100 miliseconds with the after
call. However in a state where the Toplevel window is open and the scale is updated when I press down the 'X' button in the Toplevel
window the Scale
stops moving.
This is my code:
from tkinter import Tk, Toplevel, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
num = 0
def main():
global num
slider.set(num)
num += 1
slider.after(500, main)
def toplevel():
win = Toplevel()
win.mainloop()
root.bind('<space>', lambda x: [main(), toplevel()])
root.mainloop()
When I stop pressing the 'X' button the Scale jumps to the point it should be
How can I keep the slider/scale flowing normally even when I hold down the 'X' button?
And also why does this happen?
Thanks in advance!
In short, that is a " feature ", at least on windows, the menu buttons are not expected to support the action of holding it. This happens because mainloop
is just asking to update its own instance from the same place, the global _default_root
, a workaround would be to create a new Tk
on a detached process. Note that this does not happen on every gui library, for example wxWidgets works fine.
As you can see on this example, regular buttons are unaffected.
import tkinter as tk
class Top_Window(tk.Toplevel):
@staticmethod
def button_release(_):
print('Button released')
def __init__(self, name, **kwargs):
tk.Toplevel.__init__(self, **kwargs)
self.protocol('WM_DELETE_WINDOW', self.quit_button)
self.geometry('300x200+300+300')
self.title = name
self.button = tk.Button(self, text='Button')
self.button.bind('<ButtonRelease>', self.button_release)
self.button.pack()
def quit_button(self):
print('Top window destroyed')
self.destroy()
class Main_Window(tk.Tk):
num = 0
def after_loop(self):
self.num += 1
self.slider.set(self.num)
self.after(500, self.after_loop)
def __init__(self):
tk.Tk.__init__(self)
self.geometry('300x200+100+100')
self.slider = tk.Scale(self, orient='horizontal')
self.slider.pack()
self.bind('<space>', self.spawn_top_level)
self.after(500, self.after_loop)
def spawn_top_level(self, _):
Top_Window('Top', master=self)
if __name__ == '__main__':
app = Main_Window()
app.mainloop()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.