第一个打开的选项卡之外无法访问python线程和tkinter.tix.NoteBook选项卡

时间:2011-07-09 14:10:56

标签: python multithreading tkinter python-multithreading tix

在以下示例中,除了默认打开的Log选项卡之外,我无法写入其他选项卡:

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

控制台追溯:

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'

每当我尝试在gui.SettingsFrame或gui.ProgressFrame中创建小部件时,就会发生这种情况。 如果我在main()中增加time.sleep(1)并在tix.Label部分启动之前单击选项卡,则代码将起作用,因为现在调用了tabs命令。

那么,如何提前声明gui.SettingsFrame和gui.ProgressFrame?在到达main()中的tix.Label之前,我可以通过代码中的选项卡吗?

注意:我需要它的线程,它是一个更大的程序的一部分,它执行十几个不同的事情,并且都是多线程的,并且有多个进程。

由于

编辑1: 我可以在类中添加方法而不是引用框架:

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

并在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")    

但结果是一样的:

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'

编辑2 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))

谢谢!

编辑3 :以下是线程安全实现。我加了一个奖金计时器:

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

这可能是向控制台应用程序实现线程tkinter gui的最简单方法。 gui.print_queue.put()通过调整rate变量来替换print()以及可以在print_label(self)中修改gui更新的速率。

享受!

1 个答案:

答案 0 :(得分:0)

首先,Tkinter不是线程安全的。您无法从多个线程访问tk小部件。这样做会产生不可预测的行为(尽管通常可以预测它会导致崩溃)。

至于另一个问题 - 如何提前声明gui.SettingsFrame - 我不知道如何在不说明问题的情况下回答它。要“声明”它,你必须创建它,并创建它,你必须调用创建它的方法。在使用gui.SettingsFrame作为其他方法的参数之前,您必须执行此操作。

为什么要在创建框架的方法外部的框架中创建标签?我认为该问题的解决方案是将“设置”标签的创建从Main移至MkSettings(同样用于“进度”和“日志”标签)。