0

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

#!/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() 替换了 print() 并且可以通过调整 rate 变量在 print_label(self) 中修改 gui 更新的速率。

享受 !

4

1 回答 1

0

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

至于另一个问题——如何gui.SettingsFrame提前申报——我不知道如何回答,不说清楚。要“声明”它,您必须创建它,并且要创建它,您必须调用创建它的方法。您必须在将gui.SettingsFrame其用作其他方法的参数之前执行此操作。

为什么要从创建框架的方法之外的框架中创建标签?我认为该问题的解决方案是将“设置”标签的创建从“移动”MainMkSettings“”,“进度”和“日志”标签也是如此)。

于 2011-07-09T15:25:28.297 回答