我发布这个是因为我自己一直在努力寻找这个问题的明确答案。 。 。
为了寻找为我的程序创建进度条,我发现使用tkinter很难做到。为了完成创建进度条而不进入可怕的"mainloop",我选择使用线程从进度条中创建一个类。通过大量的试验错误,我发现由于使用多线程(tkinter喜欢在主线程中),没有太多可以定制的。以下是我尝试过的两个选项,其次是最符合我需求的第三个选项:
选项1:使用回调函数
给出以下代码:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.__callback)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
@staticmethod
def __callback():
return
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
哪里
self.root.protocol("WM_DELETE_WINDOW", self.__callback)
防止窗口关闭。但是,如果要按住"退出"或[X]按钮,进度条将冻结,直到用户释放按钮。 (不断调用__callback函数,防止其他任务完成)。
选项2:使用root.overriderdirect(True)
给出以下代码:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.overrideredirect(True)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
哪里
self.root.overrideredirect(True)
清除所有tkinters窗口选项。但是,进度条不仅位于奇数位置,而且还会遮挡用户窗口。进度条应该是用户友好的。
选项3:使用root.attributes(' - disabled',True)
给出以下代码:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.attributes('-disabled', True)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
哪里
self.root.attributes('-disabled', True)
阻止任何用户与窗口交互。这最适合我对这个程序的需求,因为它可以防止窗口关闭并且仍然具有漂亮的外观。 (我唯一的小问题是用户不能再最小化进度条或移动它)。
如果有更好的解决方案,我很乐意看到它们。希望这对某人有所帮助。
这看起来不是一个问题。 我不明白你在问什么。
创建一个没有X(关闭)按钮的python tkinter窗口的可能重复
另一种在Windows上实现此目的的方法:
#!python3
import tkinter as tk
from tkinter import ttk
import threading, time
import tkinter as tk
from ctypes import windll, wintypes
GWL_STYLE = -16
WS_CHILD = 0x40000000
WS_SYSMENU = 0x00080000
SWP_FRAMECHANGED = 0x0020
SWP_NOACTIVATE = 0x0010
SWP_NOMOVE = 0x0002
SWP_NOSIZE = 0x0001
# write short names for functions and specify argument and return types
GetWindowLong = windll.user32.GetWindowLongW
GetWindowLong.restype = wintypes.ULONG
GetWindowLong.argtpes = (wintypes.HWND, wintypes.INT)
SetWindowLong = windll.user32.SetWindowLongW
SetWindowLong.restype = wintypes.ULONG
SetWindowLong.argtpes = (wintypes.HWND, wintypes.INT, wintypes.ULONG)
SetWindowPos = windll.user32.SetWindowPos
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.pb = ttk.Progressbar(self, orient="horizontal", length=400, mode="determinate", maximum=100)
self.pb.pack()
tk.Button(self, text="Remove buttons", command=self.remove_buttons).pack()
tk.Button(self, text="Add buttons", command=self.add_buttons).pack()
def start(self):
self.t = threading.Thread(target=self.loop)
self.t.start()
def loop(self):
while True:
for num in range(0, 100):
self.pb['value']=num
time.sleep(0.1)
def _get_hwnd(self):
w_id = self.winfo_id() # gets handle
style = GetWindowLong(w_id, GWL_STYLE) # get existing style
newstyle = style & ~WS_CHILD # remove child style
res = SetWindowLong(w_id, GWL_STYLE, newstyle) # set new style
res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
hwnd = int(self.wm_frame(), 16) # find handle of parent
res = SetWindowLong(w_id, GWL_STYLE, style) # set back to old style
res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
return hwnd # return parents handle
def remove_buttons(self):
hwnd = self._get_hwnd()
style = GetWindowLong(hwnd, GWL_STYLE) # get existing style
style = style & ~WS_SYSMENU
res = SetWindowLong(hwnd, GWL_STYLE, style)
res = SetWindowPos(hwnd, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
def add_buttons(self):
hwnd = self._get_hwnd()
style = GetWindowLong(hwnd, GWL_STYLE) # get existing style
style = style | WS_SYSMENU
res = SetWindowLong(hwnd, GWL_STYLE, style)
res = SetWindowPos(hwnd, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
if __name__ =="__main__":
app = App()
app.start()
app.mainloop()
看起来很复杂,但似乎可行!
它看起来很复杂,因为tkinter不能很好地使用windows api来获取父窗口的句柄,解决这个问题的方法是在_get_hwnd函数中,并且添加或删除按钮函数可以获得窗口的样式,因为它是,然后添加删除删除标题栏中所有按钮所需的样式,因为它修改了现在的样式,这应该适用于大多数窗口样式
您可以创建一个仅使用pass来执行任何操作的函数。
看看下面的内容:
import tkinter as tk
root=tk.Tk()
def close_program():
root.destroy()
def disable_event():
pass
btn = tk.Button(root, text ="Click me to close", command = close_program)
btn.pack()
root.protocol("WM_DELETE_WINDOW", disable_event)
root.mainloop()
您还可以将root.overrideredirect(True)一起删除工具栏,以防止用户使用任何工具栏。 离开root.protocol("WM_DELETE_WINDOW", disable_event)也会阻止使用ALT + F4。
import tkinter as tk
root=tk.Tk()
root.geometry("400x400")
root.overrideredirect(True)
def close_program():
root.destroy()
def disable_event():
pass
btn = tk.Button(root, text ="Click me to close", command = close_program)
btn.pack()
root.protocol("WM_DELETE_WINDOW", disable_event)
root.mainloop()
我不喜欢使用root.overrideredirect(True),因为在执行时窗口会卡在屏幕的左上角。但是,我将在root.protocol("WM_DELETE_WINDOW",disable_event)中尝试"传递"。我之前使用过返回。
@JoshuaVanDeren:您可以使用overrideredirect(True),然后您可以创建自己的自定义工具栏。它有点工作,但非常可定制。
编辑:使用传递仍然无法解决问题。当用户按下[X]时,进度条仍然被冻结@Sierra Mountain Tech您确定在进度条处于线程中时可以吗?我已经多次碰到一个问题,需要在主线程中。
@JoshuaVanDeren:如果你可以创建一个Minimal,Complete和Verifiable示例,我可以测试一下,我可以提出一个可行的解决方案。
当然可以。如果我有时间,我会举一个例子。
我更新了主要问题。