1

I took my working tkinter code (which only drew window/buttons and so on) and tried to add some code from the approved answer here: python code for serial data to print on window.

The approved answer works by itself with very small modifications, but added to my code I get the error "'Gui' object has no attribute 'after'"

What I don't understand is why the attribute "after" is looked for in class Gui instead of in method process_serial.

from tkinter import *
from tkinter import ttk

import serial
import threading
import queue

class SerialThread(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
    def run(self):
        s = serial.Serial('COM11',115200)
        while True:
            if s.inWaiting():
                text = s.readline(s.inWaiting())
                self.queue.put(text)

class Gui():
    def __init__(self, master):
        ###MAIN FRAME###
        mainFrame = Frame(master, width=50000, height=40000)
        mainFrame.pack(fill = BOTH, expand = 1)

        ###LIST FRAME###
        listFrame = Frame(mainFrame)
        listFrame.pack(side = TOP, fill = BOTH, expand = 1)

        self.sensorList = ttk.Treeview(listFrame)

        self.sensorList["columns"]=("MAC","Type","Value","Voltage","Firmware","Rate","RSSI")
        self.sensorList.column("MAC", width=200, minwidth=200)
        self.sensorList.column("Type", width=100, minwidth=100)
        self.sensorList.column("Value", width=100, minwidth=100)
        self.sensorList.column("Voltage", width=100, minwidth=100)
        self.sensorList.column("Firmware", width=100, minwidth=100)
        self.sensorList.column("Rate", width=100, minwidth=100)
        self.sensorList.column("RSSI", width=100, minwidth=100)
        self.sensorList.heading("MAC", text="MAC")
        self.sensorList.heading("Type", text="Type")
        self.sensorList.heading("Value", text="Value")
        self.sensorList.heading("Voltage", text="Voltage")
        self.sensorList.heading("Firmware", text="Firmware")
        self.sensorList.heading("Rate", text="Rate")
        self.sensorList.heading("RSSI", text="RSSI")

        self.sensorList.pack(fill = BOTH, expand = 1,  pady=5, padx=5)

        ###TEXT AREA FRAME###
        textAreaFrame = Frame(mainFrame)
        textAreaFrame.pack(side = TOP, fill = BOTH, expand = 1)

        self.textArea = Text(textAreaFrame)
        self.textArea.pack(fill = BOTH, expand = 1,  pady=5, padx=5)

        ###INPUT FRAME###
        inputFrame = Frame(mainFrame)
        inputFrame.pack(side = BOTTOM, fill = X, expand = 0)

        self.input = Entry(inputFrame)
        self.input.pack(side=LEFT, fill = X, expand = 1,  pady=5, padx=5)

        self.comboAction = ttk.Combobox(inputFrame)
        self.comboAction.pack(side = LEFT, pady=5, padx=5)

        self.comboDevice = ttk.Combobox(inputFrame)
        self.comboDevice.pack(side = LEFT, pady=5, padx=5)

        self.sendButton = Button(
            inputFrame, text="SEND", command=mainFrame.quit
        )

        self.sendButton.pack(side=LEFT,pady=5, padx=5)

        #self.button = Button(
        #   mainFrame, text="QUIT", fg="red", command=mainFrame.quit
        #)
        #self.button.pack(side=LEFT)

        #self.hi_there = Button(mainFrame, text="Hello", command=self.say_hi)
        #self.hi_there.pack(side=LEFT)

        ###AFFIX MINIMUM SIZE OF MAIN WINDOW TO PREVENT POOR SIZING###
        master.update()
        master.minsize(root.winfo_width(), root.winfo_height())
        master.minsize(master.winfo_width(), master.winfo_height())

        ###SERIAL PORT###
        self.queue = queue.Queue()
        thread = SerialThread(self.queue)
        thread.start()
        self.process_serial()

    def process_serial(self):
        while self.queue.qsize():
            try:
                self.textArea.delete(1.0, 'end')
                self.textArea.insert('end', self.queue.get())
            except Queue.Empty:
                pass
        self.after(100, self.process_serial)



    def say_hi(self):
        s = self.input.get()
        print ("hi there, everyone!" + s)

root = Tk()

gui = Gui(root)

root.mainloop()
root.destroy() # optional; see description below
Community
  • 1
  • 1
HSPalm
  • 165
  • 1
  • 2
  • 8

2 Answers2

5

The culprit is in this line in the process_serial function:

self.after(100, self.process_serial)

The self variable that is in here refers to the Gui object, not to a tkinter object that has the 'after' function.

There is a mismatch between your code and the code from the linked question. Your class does not extend a tkinter object. The class in the answer extended the tkinter Tk object like so:

class App(tk.Tk):

Thereby inheriting functions from the Tk class.

To solve this for your code, replace self in the process_serial function with a tkinter object, like self.textArea.

self.textArea.after(100, self.process_serial)

Alternatively, you could subclass tk.Tk just like in the linked answer. But I do not see the added benefit here.

Munchhausen
  • 442
  • 4
  • 16
  • Thank you. This worked, but I must spend some time in really understanding what happened. After this was solved my program is not closing properly. I mean I'm not taken back to cmd line input, and nothing happens when I CTRL+C. I removed that last root.destroy() because it threw an error saying it's already destroyed. Is it because I'm running that process_serial loop? – HSPalm Jul 29 '16 at 10:52
  • 1
    The close button of your application defaults to destroying it. It then comes out of the root.mainloop() function and tries to call root.destroy(). This fails, because it has already been destroyed. root.destroy() is therefore unnecessary. You are not supposed to be taken back to the cmd line, because the mainloop() is running. To make your application exit when pressing ctrl-C, set thread.daemon to True for the thread. – Munchhausen Jul 29 '16 at 11:21
  • See: https://docs.python.org/3/library/threading.html#threading.Thread.daemon and see: http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.mainloop-method – Munchhausen Jul 29 '16 at 11:32
1

The the method after was a inherited from Tkinter.Tk. Check mentioned question

You probably should subclass Tkinter.Tk

...
import Tkinter
class Gui(Tkinter.Tk)
    ...
Community
  • 1
  • 1
Mathias
  • 6,777
  • 2
  • 20
  • 32