Using Tkinter inside function not working

Hello all,

With the help of some users in this forum I have get this far, but facing a new challenge in my way to making my first real world GUI, I hope some expert in tkinter can help.

I have made a device with the following structure:

  • Presence sensors and controllers for Arduino
  • Arduino Mega
  • Raspberry Pi4
  • Video card
  • 1280x800 OEM screen

What the system should do is: the Arduino is running a firmware to read the presence sensors and determine a position with a set of coordinates (x;y) and prints them through the Serial port (USB) in a line. The Raspberry running my Python code gets the information from the Serial port and breaks the line into 2 coordinates again. Then the program is supposed to paint a red rectangle if the coordinates are within the area defined, corresponding to 4 areas in the background image of the GUI.

I am using Tkinter for this as it looked pretty easy for me to make the main window, put a background image and paint rectangles, but the reality is that I am finding that when I run the functions of painting the rectangles it looks like Tkinter “is not present” somehow (sorry for my lack of knowledge in explaining the issue).

The code is this (and the error below):

from serial import *
from tkinter import *
import time

root = Tk() 
root.geometry("1280x800")
root.attributes('-fullscreen',True)
bg = PhotoImage(file = "/home/pi/Pictures/SCREEN2.png") 

canvas = Canvas( root, width = 1280, height = 800) 
canvas.pack(fill = "both", expand = True) 
canvas.create_image( 0, 0, image = bg, anchor = "nw") 
    
serialPort = "/dev/ttyUSB0"
ser = Serial(serialPort, 9600, timeout=0)
ser.flush()

def split_coords(astring):
    a, b = astring.split(';')
    a = int(a)
    b = int(b)
    
    return (a, b)

def paintRectangle1():
    canvas.create_rectangle(200, 120, 400, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle2():
    canvas.create_rectangle(600, 120, 800, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle3():
    canvas.create_rectangle(200, 260, 400, 360, fill='red')
    time.sleep(0.3)
    
def paintRectangle4():
    canvas.create_rectangle(600, 260, 800, 360, fill='red')
    time.sleep(0.3)

root.mainloop() 

while True:
        
    if ser.in_waiting> 0:
        line = ser.readline().decode('utf-8').rstrip()
        x, y = split_coords(line)
        print("x:", x, "- y:", y)
        
        if (19 < x < 60) and (20 < y < 40):
            print("OK button1")
            paintRectangle1()
        if (68 < x < 110) and (20< y < 40):
            print("OK button2")
            paintRectangle2()
        if (19 < x < 60) and (45 < y < 80):
            print("OK button3")
            paintRectangle3()
        if (68 < x <110) and (45 < y < 80):
            print("OK button4")
            paintRectangle4()
            
        #else:
            #paint the backgrand image again after the 0.3 seconds defined into the paintRectangle function

The error that this code throws:

x: 32 - y: 28
OK button1
Traceback (most recent call last):
  File "/home/pi/Documents/Touch-GUI-TKInter_4.py", line 52, in <module>
    paintRectangle1()
  File "/home/pi/Documents/Touch-GUI-TKInter_4.py", line 26, in paintRectangle1
    canvas.create_rectangle(200, 120, 400, 220, fill='red')
  File "/usr/lib/python3.7/tkinter/__init__.py", line 2501, in create_rectangle
    return self._create('rectangle', args, kw)
  File "/usr/lib/python3.7/tkinter/__init__.py", line 2480, in _create
    *(args + self._options(cnf, kw))))
_tkinter.TclError: invalid command name ".!canvas"

I really don’t know what I should do to “get the mainframe inside the def statements” to make this work.

Does anyone have an idea of what I am doing wrong?

Thanks in advance.

Joan.

Hi Joan,

it has been quite a while since I used Tkinter, but maybe the following hints can be of some help:

  1. As far as I remember “root.mainloop()” is blocking.

This means, that any code below that, namely your “while True: …” loop, is not executed (at all) as long as the application is running (the mainloop is something quite similar to a “while True” loop, which keeps the Tkinter widgets alive).

Your while loop only is executed after the application has been closed - but at this point the Tkinter mainloop obviously is not running anymore… Which also means, you cannot draw rectangles anymore…

So, what you tried to do in a while loop should either be done in a function cyclically triggered by a timer alike:

root.after(5000, function_name)

(The first call must naturally be done before you enter the mainloop!)

or you can use different threads resp. tasks for the mainloop and the while loop.

  1. I would propose to subclass tk.Frame as it is done here:

tkinter — Python interface to Tcl/Tk — Python 3.9.4 documentation

Your canvas can be an attribute of the Application class then…

Have Fun, Dominik