2017-08-02 6 views
0

Je poste ceci parce que j'ai moi-même eu du mal à trouver une réponse claire à ce problème. . .Désactiver la sortie (ou [X]) dans la fenêtre tkinter

En cherchant à essayer de créer une barre de progression pour mon programme, je trouve qu'il est difficile de faire avec tkinter. Pour accomplir la création d'une barre de progression sans courir dans le "mainloop" redouté, I opted to make a class out of the progress bar using threads. A travers beaucoup d'essais une erreur, j'ai trouvé qu'il n'y a pas grand chose qui peut être personnalisé en raison de l'utilisation du multithreading (tkinter aime être dans le thread principal). Voici deux options que j'ai essayé, suivi d'un troisième qui correspond le mieux à mes besoins:

Option 1: En utilisant une fonction de rappel

Vu 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 . . . 

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 d'autres tâches d'être complétées).

Option 2: Utilisation root.overriderdirect (Vrai)

Vu 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 . . . 

self.root.overrideredirect(True) 

efface toutes tkinters options de la fenêtre. Cependant, la barre de progression n'est pas seulement dans un emplacement impair, mais elle masque également la fenêtre des utilisateurs. La barre de progression devrait être conviviale.

Option 3: Utilisation root.attributes ('- désactivé', true)

Vu 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 . . . 

self.root.attributes('-disabled', True) 

empêche toute interaction de l'utilisateur avec la fenêtre . Cela a mieux adapté mes besoins pour ce programme car il empêche la fermeture de la fenêtre et a toujours une belle apparence. (Mon seul problème mineur est que l'utilisateur ne peut plus réduire la barre de progression ou la déplacer).

S'il y a de meilleures solutions, j'aimerais les voir. Espérons que cela a aidé quelqu'un.

+0

Cela ne ressemble pas à une question. Je ne comprends pas ce que vous demandez. –

Répondre

0

Vous pouvez créer une fonction qui utilise simplement pass pour ne rien faire.

Jetez un oeil à la ci-dessous:

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 tous ensemble avec root.overrideredirect(True) qui empêchera l'utilisateur d'utiliser toute la barre d'outils. laissant root.protocol("WM_DELETE_WINDOW", disable_event) permettra également d'empêcher 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() 
+0

Je n'aime pas utiliser root.overrideredirect (True) parce que la fenêtre est bloquée dans le coin supérieur gauche de mon écran lors de l'exécution. Cependant, je vais essayer "passer" dans root.protocol ("WM_DELETE_WINDOW", disable_event). J'utilisais le retour plus tôt. –

+0

@JoshuaVanDeren: Vous pouvez utiliser 'overrideredirect (True)' et vous pouvez ensuite créer votre propre barre d'outils personnalisée. C'est un peu de travail mais c'est très personnalisable. –

+0

EDIT: l'utilisation de pass ne résout pas le problème. La barre de progression est toujours gelée lorsque l'utilisateur appuie sur [X] @Sierra Mountain Tech Etes-vous sûr de pouvoir le faire lorsque la barre de progression est dans un fil? J'ai rencontré un problème plusieurs fois où il doit être dans le fil principal. –

0

une autre façon d'y parvenir sur les fenêtres:

#!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() 
+0

Regarde compliqué, mais semble faisable! –

+0

cela semble compliqué parce que tkinter ne joue pas bien avec l'API Windows pour obtenir un handle de la fenêtre parent, le hackery pour contourner cela est dans la fonction '_get_hwnd', et les fonctions de boutons ajouter ou supprimer obtiennent le style de la fenêtre comme il est, puis ajoute de supprime les styles nécessaires pour enlever tous les boutons dans la barre de titre, car il modifie le style existant cela devrait fonctionner avec la plupart des styles de fenêtres –