Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
303 views
in Technique[技术] by (71.8m points)

python - Disable Exit (or [ X ]) in tkinter Window

I am posting this because I myself have struggled with finding a clear answer on this problem . . .

In search of trying to create a progress bar for my program, I find that it is difficult to do using tkinter. To accomplish creating a progress bar without running into the dreaded "mainloop", I opted to make a class out of the progress bar using threads. Through lots of trial an error, I found that there is not much that can be customized due to the use of multithreading (tkinter likes being in the main thread). Here are two options I have tried, followed by a third that best fits my needs:

Option 1: Using a callback function

Given the following code:

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

Where

self.root.protocol("WM_DELETE_WINDOW", self.__callback)

Prevents the window from being closed. However, if the Exit, or [ X ], button were to be held down, the progress bar would freeze until the user releases the button. (The __callback function is constantly being called, preventing other tasks from being completed).

Option 2: Using root.overriderdirect(True)

Given the following code:

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

Where

self.root.overrideredirect(True)

Clears all of tkinters window options. However, the progress bar is not only in an odd location, but it also obscures the users window. The progress bar should user friendly.

Option 3: Using root.attributes('-disabled', True)

Given the following code:

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

Where

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

Prevents any user interaction with the window. This has best suited my needs for this program as it prevents the window from closing and still has a nice appearance to it. (My only minor issue with it is that the user can no longer minimize the progress bar or move it around).

If there are any better solutions, I would love to see them. Hopefully, this has helped someone.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can create a function that just uses pass to do nothing.

Take a look at the below:

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

You could also remove the toolbar all together with root.overrideredirect(True) that will prevent the user from using any of the toolbar. leaving root.protocol("WM_DELETE_WINDOW", disable_event) will also prevent the use of 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()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...