在tkinter窗口中禁用退出(或[X])

时间:2017-08-02 17:15:13

标签: python python-3.x tkinter progress-bar exit

我发布这个是因为我自己一直在努力寻找这个问题的明确答案。 。

为了尝试为我的程序创建一个进度条,我发现使用tkinter很难做到。要完成创建进度条而不遇到可怕的" mainloop",I opted to make a class out of the progress bar using threads。通过大量的试验错误,我发现由于使用多线程(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)

阻止任何用户与窗口进行交互。这最适合我对这个程序的需求,因为它可以防止窗口关闭并且仍然具有漂亮的外观。 (我唯一的小问题是用户不能再最小化进度条或移动它)。

如果有更好的解决方案,我很乐意看到它们。希望这对某人有帮助。

2 个答案:

答案 0 :(得分:2)

您可以创建一个仅使用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()

您还可以将{@ 1}}一起删除工具栏,以防止用户使用任何工具栏。离开root.overrideredirect(True)也会阻止使用root.protocol("WM_DELETE_WINDOW", disable_event)

ALT + F4

答案 1 :(得分:1)

在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()
相关问题