Python Forum
Using Tkinter inside function not working - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: Using Tkinter inside function not working (/thread-31603.html)



Using Tkinter inside function not working - Ensaimadeta - Dec-22-2020

Hello all,

With the help of some users in different forums 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. Needless to say that I am a novice in Python...

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:

Error:
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"
As can be seen, the program goes into the paintRectangle after reading the coordinates, but it does not "understand" the canvas operation.

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? Wall

Thanks in advance.

Joan.


RE: Using Tkinter inside function not working - iMuny - Dec-22-2020

the canvas object you created is not visible to the paintRectangle[1-4] methods due to being out of scope of the subject methods, you can either adopt an object oriented approach to the above problem or you can pass an instance of the canvas object when you call the paintRectangle[1-4] methods,

def paintRectangle1(canvas_ref):
    canvas_ref.create_rectangle(200, 120, 400, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle2(canvas_ref):
    canvas_ref.create_rectangle(600, 120, 800, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle3(canvas_ref):
    canvas_ref.create_rectangle(200, 260, 400, 360, fill='red')
    time.sleep(0.3)
    
def paintRectangle4(canvas_ref):
    canvas_ref.create_rectangle(600, 260, 800, 360, fill='red')
    time.sleep(0.3)
and you can call the methods like

canvas = Canvas( root, width = 1280, height = 800) 

        if (19 < x < 60) and (20 < y < 40):
            print("OK button1")
            paintRectangle1(canvas)
        if (68 < x < 110) and (20< y < 40):
            print("OK button2")
            paintRectangle2(canvas)
        if (19 < x < 60) and (45 < y < 80):
            print("OK button3")
            paintRectangle3(canvas)
        if (68 < x <110) and (45 < y < 80):
            print("OK button4")
            paintRectangle4(canvas)



RE: Using Tkinter inside function not working - Ensaimadeta - Dec-22-2020

Hello iMuny, thanks for your suggestion.

I have done as you suggested and still no luck... Think

This is how it looks now:

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_ref):
    print("entered Rect 1")
    canvas_ref.create_rectangle(207, 151, 620, 374, fill='red')
    time.sleep(0.3)
    
def paintRectangle2(canvas_ref):
    print("entered Rect 2")
    canvas_ref.create_rectangle(600, 120, 800, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle3(canvas_ref):
    print("entered Rect 3")
    canvas_ref.create_rectangle(200, 260, 400, 360, fill='red')
    time.sleep(0.3)
    
def paintRectangle4(canvas_ref):
    print("entered Rect 4")
    canvas_ref.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)
    
        canvas = Canvas( root, width = 1280, height = 800)
        
        if (x>19) and (x<60) and (y>20) and (y<40):
            print("OK button1")
            paintRectangle1(canvas)
        if (x>68) and (x<110) and (y>20) and (y<40):
            print("OK button2")
            paintRectangle2(canvas)
        if (x>19) and (x<60) and (y>45) and (y<80):
            print("OK button3")
            paintRectangle3(canvas)
        if (x>68) and (x<110) and (y>45) and (y<80):
            print("OK button4")
            paintRectangle4(canvas)
        '''    
        else:
            canvas.create_image( 0, 0, image = bg, anchor = "nw")

'''
and the error...

Error:
x: 11 - y: 11 Traceback (most recent call last): File "/home/pi/Documents/Touch-GUI-TKInter_4.py", line 55, in <module> canvas = Canvas( root, width = 1280, height = 800) File "/usr/lib/python3.7/tkinter/__init__.py", line 2405, in __init__ Widget.__init__(self, master, 'canvas', cnf, kw) File "/usr/lib/python3.7/tkinter/__init__.py", line 2299, in __init__ (widgetName, self._w) + extra + self._options(cnf)) _tkinter.TclError: can't invoke "canvas" command: application has been destroyed



RE: Using Tkinter inside function not working - deanhystad - Dec-23-2020

Code located after "root.mainloop()" is not going to execute until you close the root window. But you can't just move your while loop ahead of "root.mainloop()" because that prevents tkinter from running. What you need to do is simultaneously let mainloop() run while processing data from your serial port. There is probably a framework for doing this kind of thing built into your Raspberry Pi libraries. Look at the serial port library for some way to call a function when data is available on the serial port. Failing that, look for some kind of scheduler that will execute a function periodically.

You could always use the ".after" function from tkinter. In the example below I used root.after() in the random_rectangle() function to execute the function 10 times a second while letting mainloop() run at the same time.
import random
from tkinter import *
import time

rectangle = None
root = Tk() 
root.geometry("200x200")
 
canvas = Canvas( root, width = 1280, height = 800) 
canvas.pack(fill = "both", expand = True) 
canvas.create_image(0, 0, anchor = "nw")
     
def random_rectangle():
    global rectangle
    if rectangle is not None:
        canvas.delete(rectangle)
    x = random.randint(0, 200) // 50 * 50
    y = random.randint(0, 200) // 50 * 50
    rectangle = canvas.create_rectangle(x, y, x+50, y+50, fill='red')
    root.after(100, random_rectangle)
    
random_rectangle()

root.mainloop() 

print('This is not executed until you close the root window')



RE: Using Tkinter inside function not working - darkv - Dec-03-2023

(Dec-22-2020, 07:11 PM)Ensaimadeta Wrote: Hello iMuny, thanks for your suggestion.

I have done as you suggested and still no luck... Think

This is how it looks now:

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_ref):
    print("entered Rect 1")
    canvas_ref.create_rectangle(207, 151, 620, 374, fill='red')
    time.sleep(0.3)
    
def paintRectangle2(canvas_ref):
    print("entered Rect 2")
    canvas_ref.create_rectangle(600, 120, 800, 220, fill='red')
    time.sleep(0.3)
    
def paintRectangle3(canvas_ref):
    print("entered Rect 3")
    canvas_ref.create_rectangle(200, 260, 400, 360, fill='red')
    time.sleep(0.3)
    
def paintRectangle4(canvas_ref):
    print("entered Rect 4")
    canvas_ref.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)
    
        canvas = Canvas( root, width = 1280, height = 800)
        
        if (x>19) and (x<60) and (y>20) and (y<40):
            print("OK button1")
            paintRectangle1(canvas)
        if (x>68) and (x<110) and (y>20) and (y<40):
            print("OK button2")
            paintRectangle2(canvas)
        if (x>19) and (x<60) and (y>45) and (y<80):
            print("OK button3")
            paintRectangle3(canvas)
        if (x>68) and (x<110) and (y>45) and (y<80):
            print("OK button4")
            paintRectangle4(canvas)
        '''    
        else:
            canvas.create_image( 0, 0, image = bg, anchor = "nw")

'''
and the error...

Error:
x: 11 - y: 11 Traceback (most recent call last): File "/home/pi/Documents/Touch-GUI-TKInter_4.py", line 55, in <module> canvas = Canvas( root, width = 1280, height = 800) File "/usr/lib/python3.7/tkinter/__init__.py", line 2405, in __init__ Widget.__init__(self, master, 'canvas', cnf, kw) File "/usr/lib/python3.7/tkinter/__init__.py", line 2299, in __init__ (widgetName, self._w) + extra + self._options(cnf)) _tkinter.TclError: can't invoke "canvas" command: application has been destroyed

same here. did you found the solution


RE: Using Tkinter inside function not working - deanhystad - Dec-03-2023

You could use tkinter.after() to periodically run a function that checks for input on the serial port, or you can run the serial port loop in a separate thread.