首页 > 解决方案 > tkinter Class Object Not Defined

问题描述

I am fairly new to Python and this is my first project using tkinter. I have my entire project working fine with one exception. I have built the tkinter code into a class and it all works but I cannot figure out how to call the methods from outside of the class.

When I create the object on the following line in main, I get NameError: name 'robotGUI' is not defined

class botGUI:
    def __init__(self):
        #Init Code

    def updateStatus(self):
        #Code Here

robotGUI = botGUI()

If I initialize the variable "robotGUI" to None, the code runs but when I later try to access one of its methods I get AttributeError: 'NoneType' object has no attribute 'doSomething'. It appears that the robotGUI object is not being created but I do not understand why.

I have searched everywhere and have found some close answers but nothing that exactly pertains to this issue. I have a handfull of other classes that are working perfect in this program so I am sure it has to do with tkinter and its internal mainloop just havent been able to pin point it.

Here is my greatly reduced and simplified code showing the problem:

#!/usr/bin/env python3

#Imports
import socket, select, errno, sys, queue, time, threading, cv2
from tkinter import *
from tkinter import font
from PIL import Image, ImageTk

#GUI
class botGUI:

    def __init__(self):

        #Create the Window Object and Setup the Window
        self.window = Tk()
        self.window.geometry("800x480+0+0")
        self.window.overrideredirect(True)
        self.window.fullScreenState = False

        #Code to Generate Gaphics Here .....        

        #Call Repeating Status Update Script and Start the Main Loop
        self.updateStatus()
        self.window.mainloop()    

    def updateStatus(self):

        #Code to Handle Updating Screen Objects Here ....    
        print("Update Status Running")

        #Set this function to be called again
        self.window.after(1000, lambda: self.updateStatus())
        

    def doSomething(self, myStr):

        #Code to change something on the screen ...
        print(f"Command: {str(myStr)}")

    def doSomethingElse(self, myStr):

        #Code to change something on the screen ...
        print(f"Command: {str(myStr)}")
        


#Main Task - Since tKinter is running in the main loop, all of the main loop code is moved to here
def main_loop():

    global robotGUI
    robotDataReceived = True #This is only for this posting

    #Main Loop
    while True:

        #If Incoming Data from Robot, Get and Process It!
        if robotDataReceived:
            robotCmdHandler()
            
        #Anti Blocking Delay (Much shorter, set higher for this post)
        time.sleep(2)


#Robot Command Handler
def robotCmdHandler():

    global robotGUI

    #Code to get a command string and process it goes here .....
    cmd = "dosomething"  #Temporary for this post

    #Handle command
    if (cmd == "dosomething"):
        print("Processing Command")
        robotGUI.doSomething("Do This")


if __name__ == '__main__':

    global robotGUI
    robotGUI = None

    #Create and Start Threads
    t1 = threading.Thread(target=main_loop, name='t1')
    t1.start()            

    #Create GUI Object
    robotGUI = botGUI()

    #Wait until threads are finished
    t1.join() 

   

                         

标签: pythonpython-3.xtkinter

解决方案


Remove the call self.window.mainloop() from botGUI.__init__(), then you can:

  • create the instance of botGUI: robotGUI = botGUI()
  • create the thread and start it
  • call roboGUI.window.mainloop()

Below is the modified code:

#!/usr/bin/env python3

#Imports
import socket, select, errno, sys, queue, time, threading, cv2
from tkinter import *
from tkinter import font
from PIL import Image, ImageTk

#GUI
class botGUI:

    def __init__(self):

        #Create the Window Object and Setup the Window
        self.window = Tk()
        self.window.geometry("800x480+0+0")
        self.window.overrideredirect(True)
        self.window.fullScreenState = False

        #Code to Generate Gaphics Here .....        

        #Call Repeating Status Update Script and Start the Main Loop
        self.updateStatus()
        #self.window.mainloop()    

    def updateStatus(self):

        #Code to Handle Updating Screen Objects Here ....    
        print("Update Status Running")

        #Set this function to be called again
        self.window.after(1000, lambda: self.updateStatus())
        

    def doSomething(self, myStr):

        #Code to change something on the screen ...
        print(f"Command: {str(myStr)}")

    def doSomethingElse(self, myStr):

        #Code to change something on the screen ...
        print(f"Command: {str(myStr)}")
        


#Main Task - Since tKinter is running in the main loop, all of the main loop code is moved to here
def main_loop():

    #global robotGUI
    robotDataReceived = True #This is only for this posting

    #Main Loop
    while True:

        #If Incoming Data from Robot, Get and Process It!
        if robotDataReceived:
            robotCmdHandler()
            
        #Anti Blocking Delay (Much shorter, set higher for this post)
        time.sleep(2)


#Robot Command Handler
def robotCmdHandler():

    #global robotGUI

    #Code to get a command string and process it goes here .....
    cmd = "dosomething"  #Temporary for this post

    #Handle command
    if (cmd == "dosomething"):
        print("Processing Command")
        robotGUI.doSomething("Do This")


if __name__ == '__main__':

    #Create GUI Object
    robotGUI = botGUI()

    #Create and Start Threads
    t1 = threading.Thread(target=main_loop, name='t1')
    t1.start()            

    # start the GUI main loop
    robotGUI.window.mainloop()

    #Wait until threads are finished
    t1.join()

推荐阅读