开发者

python threading and tkinter.tix.NoteBook tabs not accessible beyond first open tab

In the following example I'm unable to write to the other tabs aside from the open-by-default Log tab:

#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count

import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk

TCL_DONT_WAIT           = 1<<1
TCL_WINDOW_EVENTS       = 1<<2
TCL_FILE_EVENTS         = 1<<3
TCL_TIMER_EVENTS        = 1<<4
TCL_IDLE_EVENTS         = 1<<5
TCL_ALL_EVENTS          = 0

class GUI(threading.Thread):

    def __init__(self):
        """ thread 开发者_StackOverflowinit
        defines some vars and starts stuff when the class is called (gui=GUI())
        """
        self.root=tkinter.tix.Tk()
        z = self.root.winfo_toplevel()
        z.wm_title('minimal example')
        if z.winfo_screenwidth() <= 800:
            z.geometry('790x590+10+10')
        else:
            z.geometry('890x640+10+10')
        frame1 = self.MkMainNotebook()
        frame2 = self.MkMainStatus()
        frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
        frame2.pack(side=BOTTOM, fill=X)
        z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
        threading.Thread.__init__(self)

    def run(self):
        """ thread start
        kick starts the main loop when the thread start()
        """
        self.root.mainloop()

    def stop(self):
        """ escape plan
        Exits gui thread
        """
        self._stop()
        raise SystemExit

    def MkMainStatus(self):
        """ status bar
        """
        top = self.root
        w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
        self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
        self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
        return w

    def MkMainNotebook(self):
        """ the tabs frame
        defines the tabs
        """
        top = self.root
        w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
        tagPadX 6
        tagPadY 4
        borderWidth 2
        """)
        top['bg'] = w['bg']

        w.add('log', label='Log', underline=0,
              createcmd=lambda w=w, name='log': self.MkLog(w, name))
        w.add('pro', label='Progress', underline=0,
              createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
        w.add('set', label='Settings', underline=0,
              createcmd=lambda w=w, name='set': self.MkSettings(w, name))
        return w

    def MkSettings(self, nb, name):
        """ TODO: settings tab
        """
        w = nb.page(name)
        options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
        settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.SettingsFrame = settings_scr_win.window

    def MkProgress(self, nb, name):
        """ TODO: progress tab
        """
        w = nb.page(name)
        options = "label.padX 4"
        progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.ProgressFrame = progress_scr_win.window

    def MkLog(self, nb, name):
        """ log
        """
        w = nb.page(name)
        options = "label.padX 4"
        log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
        self.LogFrame = log_scr_win.window

def main(argv):
    """ main function
    Keyword arguments:
    args[0] -- 
    args[1] -- 
    Returns: 
    None
    """

    #GUI
    gui=GUI()
    gui.start()
    time.sleep(1) # give it a sec to draw the gui... 

    tix.Label(gui.LogFrame, text=("log")).pack()
    tix.Label(gui.SettingsFrame, text=("settings")).pack()
    tix.Label(gui.ProgressFrame, text=("progress")).pack()

    return None    

if __name__ == "__main__":
    sys.exit(main(sys.argv))

The console traceback:

Traceback (most recent call last):
  File "C:\minimal example.py", line 132, in <module>
    sys.exit(main(sys.argv))
  File "C:\minimal example.py", line 126, in main
    tix.Label(gui.SettingsFrame, text=("settings")).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'

This happens whenever I try to create widgets in gui.SettingsFrame or gui.ProgressFrame. If I increase the time.sleep(1) in main() and click through the tabs before the tix.Label part starts, the code works since now the tabs command were invoked.

So, how do I declare the gui.SettingsFrame and gui.ProgressFrame in advance ? can I pass through the tabs in the code before I reach the tix.Label in main() ?

Note: I need it threaded, it's part of a larger program that does a dozen different things and is both multi threaded and has several processes.

Thanks

Edit 1: I could add methods to the class instead of referring to the frames:

    def print_log(self, text):
        """ log printer
        """
        tix.Label(self.LogFrame, text=(text)).pack()

    def print_progress(self, text):
        """ log printer
        """
        tix.Label(self.ProgressFrame, text=(text)).pack()

    def print_settings(self, text):
        """ log printer
        """
        tix.Label(self.SettingsFrame, text=(text)).pack()

and call them in main:

    #tix.Label(gui.LogFrame, text=("log")).pack()
    gui.print_log("log")
    #tix.Label(gui.SettingsFrame, text=("settings")).pack()
    gui.print_settings("settings")
    #tix.Label(gui.ProgressFrame, text=("progress")).pack()
    gui.print_progress("progress")    

but the results are the same:

Traceback (most recent call last):
  File "C:\minimal example.py", line 150, in <module>
    sys.exit(main(sys.argv))
  File "C:\minimal example.py", line 143, in main
    gui.print_settings("settings")
  File "C:\minimal example.py", line 124, in print_settings
    tix.Label(self.SettingsFrame, text=(text)).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'

Edit 2: a very nice quick and easy fix by Bryan Oakley :

#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count

import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk

TCL_DONT_WAIT           = 1<<1
TCL_WINDOW_EVENTS       = 1<<2
TCL_FILE_EVENTS         = 1<<3
TCL_TIMER_EVENTS        = 1<<4
TCL_IDLE_EVENTS         = 1<<5
TCL_ALL_EVENTS          = 0

class GUI(threading.Thread):

    def __init__(self):
        """ thread init
        defines some vars and starts stuff when the class is called (gui=GUI())
        """
        self.root=tkinter.tix.Tk()
        z = self.root.winfo_toplevel()
        z.wm_title('minimal example')
        if z.winfo_screenwidth() <= 800:
            z.geometry('790x590+10+10')
        else:
            z.geometry('890x640+10+10')
        frame1 = self.MkMainNotebook()
        frame2 = self.MkMainStatus()
        frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
        frame2.pack(side=BOTTOM, fill=X)
        z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
        threading.Thread.__init__(self)

    def run(self):
        """ thread start
        kick starts the main loop when the thread start()
        """
        self.root.mainloop()

    def stop(self):
        """ escape plan
        Exits gui thread
        """
        self._stop()
        raise SystemExit

    def MkMainStatus(self):
        """ status bar
        """
        top = self.root
        w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
        self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
        self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
        return w

    def MkMainNotebook(self):
        """ the tabs frame
        defines the tabs
        """
        top = self.root
        w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
        tagPadX 6
        tagPadY 4
        borderWidth 2
        """)
        top['bg'] = w['bg']

        w.add('log', label='Log', underline=0)
              #createcmd=lambda w=w, name='log': self.MkLog(w, name))
        w.add('pro', label='Progress', underline=0)
              #createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
        w.add('set', label='Settings', underline=0)
              #createcmd=lambda w=w, name='set': self.MkSettings(w, name))

        #log_it = w.subwidget('log')
        #pro_it = w.subwidget('pro')
        #set_it = w.subwidget('set')

        self.MkLog(w, 'log')
        self.MkProgress(w, 'pro')
        self.MkSettings(w, 'set')

        return w

    def MkSettings(self, nb, name):
        """ TODO: settings tab
        """
        w = nb.page(name)
        options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
        settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.SettingsFrame = settings_scr_win.window

    def MkProgress(self, nb, name):
        """ TODO: progress tab
        """
        w = nb.page(name)
        options = "label.padX 4"
        progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.ProgressFrame = progress_scr_win.window

    def MkLog(self, nb, name):
        """ log
        """
        w = nb.page(name)
        options = "label.padX 4"
        log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
        self.LogFrame = log_scr_win.window

    def print_log(self, text):
        """ log printer
        """
        tix.Label(self.LogFrame, text=(text)).pack()

    def print_progress(self, text):
        """ log printer
        """
        tix.Label(self.ProgressFrame, text=(text)).pack()

    def print_settings(self, text):
        """ log printer
        """
        tix.Label(self.SettingsFrame, text=(text)).pack()

def main(argv):
    """ main function
    Keyword arguments:
    args[0] -- 
    args[1] -- 
    Returns: 
    None
    """

    #GUI
    gui=GUI()
    gui.start()
    time.sleep(1) # give it a sec to draw the gui... 

    tix.Label(gui.LogFrame, text=("log")).pack()
    #gui.print_log("log")
    tix.Label(gui.SettingsFrame, text=("settings")).pack()
    #gui.print_settings("settings")
    tix.Label(gui.ProgressFrame, text=("progress")).pack()
    #gui.print_progress("progress")    

    return None    

if __name__ == "__main__":
    sys.exit(main(sys.argv))

Thanks !

Edit 3: The following is a thread safe implementation. I added a bonus timer:

#!/usr/bin/python
import os, sys
import threading
import queue
from queue import Empty
import time
from itertools import count

import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk

TCL_DONT_WAIT           = 1<<1
TCL_WINDOW_EVENTS       = 1<<2
TCL_FILE_EVENTS         = 1<<3
TCL_TIMER_EVENTS        = 1<<4
TCL_IDLE_EVENTS         = 1<<5
TCL_ALL_EVENTS          = 0

class GUI(threading.Thread):

    def __init__(self):
        """ thread init
        defines some vars and starts stuff when the class is called (gui=GUI())
        """
        self.root=tkinter.tix.Tk()
        z = self.root.winfo_toplevel()
        z.wm_title('minimal example')
        if z.winfo_screenwidth() <= 800:
            z.geometry('790x590+10+10')
        else:
            z.geometry('890x640+10+10')
        frame1 = self.MkMainNotebook()
        frame2 = self.MkMainStatus()
        frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
        frame2.pack(side=BOTTOM, fill=X)
        z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())

        threading.Thread.__init__(self)

    def run(self):
        """ thread start
        kick starts the main loop when the thread start()
        """
        self.root.mainloop()

    def stop(self):
        """ escape plan
        Exits gui thread
        """
        self._stop()
        raise SystemExit

    def MkMainStatus(self):
        """ status bar
        """
        top = self.root
        w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)

        self.status = tkinter.tix.Label(w, anchor=E, bd=1)
        self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())

        self.print_queue=queue.Queue()
        self.print_label()

        self.status.grid(row=0, column=0, sticky=W, padx=3, pady=3)
        self.exitbutton.grid(row=0, column=1, sticky=E, padx=3, pady=3)
        return w

    def print_label(self):
        """ listner
        listner
        """
        rate=0.5 # seconds to re-read queue; 0.5=half a second, 1=a full second
        counter = count(0, rate)
        def update_func():
            secs= str(counter.__next__())
            try:
                self.status.config(text=str("%s(secs): Processing queue..." % (secs.split('.'))[0]), fg=str("red"))
                a = tix.Label(self.LogFrame, text=(self.print_queue.get(False)))
            except Empty:
                self.status.config(text=str("%s(secs): Waiting for queue..." % (secs.split('.'))[0]), fg=str("black"))
                self.status.after(int(rate*1000), update_func)
            else:
                a.pack()
                a.after(int(rate*1000), update_func)
        update_func()

    def MkMainNotebook(self):
        """ the tabs frame
        defines the tabs
        """
        top = self.root
        w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
        tagPadX 6
        tagPadY 4
        borderWidth 2
        """)
        top['bg'] = w['bg']

        w.add('log', label='Log', underline=0)
        self.MkLog(w, 'log')
        w.add('pro', label='Progress', underline=0)
        self.MkProgress(w, 'pro')
        w.add('set', label='Settings', underline=0)
        self.MkSettings(w, 'set')
        return w

    def MkSettings(self, nb, name):
        """ TODO: settings tab
        """
        w = nb.page(name)
        options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
        settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.SettingsFrame = settings_scr_win.window

    def MkProgress(self, nb, name):
        """ TODO: progress tab
        """
        w = nb.page(name)
        options = "label.padX 4"
        progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)        
        self.ProgressFrame = progress_scr_win.window

    def MkLog(self, nb, name):
        """ log
        """
        w = nb.page(name)
        options = "label.padX 4"
        log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
        log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
        self.LogFrame = log_scr_win.window

def main(argv):
    """ main function
    Keyword arguments:
    args[0] -- 
    args[1] -- 
    Returns: 
    None
    """

    #GUI
    gui=GUI()
    gui.start()

    gui.print_queue.put("log")

    time.sleep(10) # timed release test
    gui.print_queue.put("timed release test")

    return None    

if __name__ == "__main__":
    sys.exit(main(sys.argv))

It's probably the simplest method for implementing a threaded tkinter gui to a console application. The gui.print_queue.put() replaces the print() and the rate the gui updates can be modified in print_label(self) by adjusting the rate variable.

Enjoy !


For one, Tkinter is not thread safe. You can't access the tk widgets from more than one thread. Doing so yields unpredictable behavior (though one can usually predict that it will cause crashes).

As for the other question -- how to declare gui.SettingsFrame in advance -- I'm not sure how to answer it without stating the obvious. To "declare" it you must create it, and to create it you must call the method that creates it. And you must do this before using gui.SettingsFrame as an argument to some other method.

Why are you creating a label in a frame from outside of the method where the frame is created? I think the solution to that problem is to move the creation of the "settings" label from Main to MkSettings (and likewise for the "progress" and "log" labels).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜