Je poste ceci parce que j'ai moi-même du mal à trouver une réponse claire à ce problème. . .
À la recherche d'essayer de créer une barre de progression pour mon programme, je trouve qu'il est difficile de faire en utilisant tkinter. Pour accomplir la création d'une barre de progression sans courir dans la redoutable "mainloop", j'ai choisi de faire une classe de la barre de progression en utilisant des threads . Grâce à beaucoup d'essais et d'erreurs, j'ai trouvé qu'il n'y avait pas grand-chose à personnaliser en raison de l'utilisation du multithreading (tkinter aime être dans le fil principal). Voici deux options que j'ai essayées, suivies d'une troisième qui correspond le mieux à mes besoins:
Étant donné le code suivant:
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 . . .
Où
self.root.protocol("WM_DELETE_WINDOW", self.__callback)
Empêche la fermeture de la fenêtre. Cependant, si le bouton Quitter ou [X] devait être maintenu enfoncé, la barre de progression se bloquerait jusqu'à ce que l'utilisateur relâche le bouton. (La fonction __callback est constamment appelée, empêchant ainsi l'exécution d'autres tâches).
Étant donné le code suivant:
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 . . .
Où
self.root.overrideredirect(True)
Efface toutes les options de la fenêtre tkinters. Cependant, la barre de progression se trouve non seulement dans un endroit étrange, mais elle masque également la fenêtre des utilisateurs. La barre de progression doit être conviviale.
Étant donné le code suivant:
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 . . .
Où
self.root.attributes('-disabled', True)
Empêche toute interaction de l'utilisateur avec la fenêtre. Cela a le mieux adapté à mes besoins pour ce programme car il empêche la fenêtre de se fermer et a toujours une belle apparence. (Mon seul problème mineur est que l'utilisateur ne peut plus minimiser la barre de progression ou la déplacer).
S'il existe de meilleures solutions, j'aimerais les voir. J'espère que cela a aidé quelqu'un.
Vous pouvez créer une fonction qui utilise simplement pass
pour ne rien faire.
Jetez un œil à ce qui suit:
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()
Vous pouvez également supprimer la barre d'outils avec root.overrideredirect(True)
, ce qui empêchera l'utilisateur d'utiliser l'une des barres d'outils. laisser root.protocol("WM_DELETE_WINDOW", disable_event)
empêchera également l'utilisation de 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()
une autre façon d'y parvenir sur 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()