2017-08-02 45 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'函數中,並且添加或刪除按鈕函數獲得窗口,然後添加刪除標題欄中所有按鈕所需的樣式,因爲它修改了現有樣式,這應該適用於大多數窗口樣式 –