2017-08-02 47 views
0

我发布这个,因为我自己一直在寻找这个问题的明确答案挣扎。 。 。禁用退出(或[X])在tkinter窗口

为了尝试为我的程序创建进度条,我发现使用tkinter很困难。要完成创建进度条而不会遇到可怕的“主循环”,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) 

防止关闭窗口。但是,如果按住Exit或[X]按钮,则进度条将冻结,直到用户释放按钮。 (__callback函数不断被调用,阻止其他任务完成)。

选项2:使用root.overriderdirect(真)

考虑下面的代码:

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( ' - 禁用',真)

鉴于以下代码:

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) 

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

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

+0

这看起来并不像一个问题。我不明白你在问什么。 –

回答

0

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

您也可以删除工具栏上所有root.overrideredirect(True)在一起,这将阻止用户使用任何工具栏。留下root.protocol("WM_DELETE_WINDOW", disable_event)也会阻止使用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

我不喜欢使用root.overrideredirect(True),因为执行时窗口卡在屏幕的左上角。 但是,我会尝试在root.protocol(“WM_DELETE_WINDOW”,disable_event)中“传递”。我之前正在使用退货。 –

+0

@JoshuaVanDeren:你可以使用'overrideredirect(True)',然后你可以创建自己的自定义工具栏。它的一些工作,但非常可定制。 –

+0

编辑:使用pass仍然不能解决问题。当用户按下时,进度条仍然冻结[X] @Sierra Mountain Tech您确定您可以在进度条处于线程中吗?我已经多次在主线程中遇到问题。 –

0

另一种方式来实现这一目标的窗口:

#!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

看起来很复杂,但似乎可行! –

+0

它看起来很复杂,因为tkinter不能很好的与窗口api一起获取父窗口的句柄,为了解决这个问题,hackery在'_get_hwnd'函数中,并且添加或删除按钮函数获得窗口,然后添加删除标题栏中所有按钮所需的样式,因为它修改了现有样式,这应该适用于大多数窗口样式 –