web-dev-qa-db-fra.com

Désactiver la sortie (ou [X]) dans la fenêtre tkinter

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:

Option 1: utilisation d'une fonction de rappel

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

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

Option 2: utilisation de root.overriderdirect (True)

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

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.

Option 3: utilisation de root.attributes ('- désactivé', True)

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

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.

7
Josh

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()
3
Mike - SMT

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()
2
James Kent