Python Forum
Tkinter function to clear old canvas and start a fresh "operation"
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tkinter function to clear old canvas and start a fresh "operation"
#1
Hello. I am using python tkinter to create interface for my application. I have made a very bad spaghetti code initially and managed to get it to work without using the mainloop. I would like to improve my previous code and get my program to work properly with tkinter mainloop.



I have created an application class :



class Application(tk.Frame):

    flag_ota_button = 0

    def __init__(self,master=None):

        super().__init__(master)

        self.master = master

    def Create_canvas(self,canvas_width,canvas_height):

        global canvas

        canvas = tk.Canvas(self.master,bg='ivory2',width=canvas_width,height=canvas_height)

  

      

    def Application_Intro(self):

        print("starting new app")

        global label_RED

        global entry_input# define this as global so it can be read in the callback function

        #canvas = tk.Canvas(self.master,bg='ivory2',width=canvas_width,height=canvas_height)

        label1 = tk.Label(canvas,text = "SKENUOKITE BARKODA(GUID) ARBA DAIKTO RIVILINI KODA:",font='Helvetica 16 bold',bg='ivory2',foreground="red")

        entry_input = tk.Entry(canvas) # entry = guid

        entry_input.focus_set()

        end_program_button = tk.Button(canvas, text="Baigti",font='Helvetica 12 bold', width=20, height=2, command=self.end_program)

        restart_program_button = tk.Button(canvas, text="Restartuoti",font='Helvetica 12 bold', width=20, height=2, command =self.Restart)

        ota_button = tk.Button(canvas, text="Atnaujinti",font='Helvetica 12 bold', width=20, height=2, command=self.OTA_gui)

        remove_item_button = tk.Button(canvas, text="Isvalyti dezute",font='Helvetica 12 bold', width=20, height=2, command=self.Remove_item_gui)

        new_operation_button = tk.Button(canvas, text="Nauja operacija",font='Helvetica 12 bold', width=20, height=2, command = self.New_operation)

        canvas.create_text(960,20,text="PICK TO LIGHT",font='Helvetica 16 bold')

        label_RED = canvas.create_window(960,75,window=label1)

        self.user_input_entry=canvas.create_window(960,125,window=entry_input)

        canvas.create_window(1210,200,window=ota_button)

        canvas.create_window(960,200,window=remove_item_button)

        canvas.create_window(710,200,window = new_operation_button)

        canvas.create_window(1210,400,window = end_program_button)

        canvas.create_window(710,400,window = restart_program_button)

        entry_input.bind('<Return>',read_user_input)

        canvas.pack()

    

    def picking_gui(self):



        self.devices = {}

        device_list,serial_list = count_unique_devices(myConnection)

        print("the list of devices=",device_list)

      

        img_red_original = Image.open("camera_red.png")

        img_red_original = img_red_original.resize((50,50),Image.ANTIALIAS)

        self.img_red = ImageTk.PhotoImage(img_red_original)

      

        img_green_original = Image.open("camera_green.png")

        img_green_original = img_green_original.resize((50,50),Image.ANTIALIAS)

        self.img_green = ImageTk.PhotoImage(img_green_original)

      

        red_image = canvas.create_image(100,50,image=self.img_red,anchor='nw')

        self.update_RED_label(label_RED,960,75,"Imkite daiktus is nurodytu dezuciu")

        gaminio_kodas_text = "Gaminio kodas="+user_input

        gaminio_serial_text = "Serial="+komplektacijos_Serial

        gaminio_IMEI_Text = "IMEI="+komplektacijos_Imei

        canvas.create_text(960,400,text=gaminio_kodas_text,font='Helvetica 12 bold')

        canvas.create_text(960,425,text=gaminio_serial_text,font='Helvetica 12 bold')

        canvas.create_text(960,450,text=gaminio_IMEI_Text,font='Helvetica 12 bold')

        for x in range(0,len(device_list)):

            self.devices[device_list[x]] = canvas.create_rectangle(20+(x*110),550,110+(x*110),640,fill='red')

            canvas.create_text(65+(x*110),585,text=device_list[x],font='Helvetica 12 bold',fill="yellow")

            canvas.create_text(65+(x*110),605,text=serial_list[x],font='Helvetica 10 bold',fill="yellow")

        if(x>15):

            self.devices[device_list[x]] = canvas.create_rectangle(20+(x*110),650,110+(x*110),740,fill='red')

            canvas.create_text(55+(x*90),585,text=device_list[x],font='Helvetica 12 bold',fill="yellow")

            canvas.create_text(65+(x*110),605,text=serial_list[x],font='Helvetica 10 bold',fill="yellow")

            canvas.pack()



  

  

    def update_rectangle_color_initial(self,device_name):

        canvas.itemconfig(self.devices[device_name],fill="green")

        self.last_device = device_name



    def update_rectangle_color_last(self): 

        canvas.itemconfig(self.devices[self.last_device],fill="red")



    def update_rectangle_color(self,device_name):

        canvas.itemconfig(self.devices[self.last_device],fill="red")

        canvas.itemconfig(self.devices[device_name],fill="green")

        self.last_device = device_name

      

      

    def refresh_app(self,delay):

        #refresh_variables()

        canvas.delete("all")

        app.after(delay,lambda:self.Application_Intro())

          

    def update_MAIN_label(self,label,x,y,text1):

        global label_MAIN

        canvas.delete(label_MAIN)

        label_MAIN = canvas.create_text(x,y,text=text1,font="Helvetica 14 bold")

  

    def update_RED_label(self,label,x,y,text1):

        global label_RED

        canvas.delete(label_RED)

        label_RED = canvas.create_text(x,y,text=text1,fill="red",font='Helvetica 16 bold')





    def make_new_textbox(self,text_input):

        global label_RED

        global new_textbox_entry

      

        canvas.delete(label_RED)

        canvas.delete(self.user_input_entry)

        label=tk.Label(canvas,text = text_input,bg='ivory2',foreground="red",font='Helvetica 16 bold')

        new_textbox_entry = tk.Entry(canvas)

        new_textbox_entry.focus_set()



        label_RED = canvas.create_window(960,75,window=label)

        self.user_input_entry = canvas.create_window(960,125,window=new_textbox_entry) 

        new_textbox_entry.bind('<Return>',read_user_new_textbox)





    def end_program(self):

        print("end program")



    def Restart(self):

        print("restart")

        restart_devices(myConnection,"RESTART")

        self.refresh_app(1000)



      

    def OTA_gui(self):

        print("OTA gui")

  

    def Remove_item_gui(self):

        print("remove item gui")

      

    def New_operation(self):

        print("new_operation")

        restart_devices(myConnection,"reset")

        canvas.delete("all")

        app.after(1000,self.Application_Intro)

        print("new operation end")



      

    def OTA_gui(self):

        self.devices = []

        if(self.flag_ota_button % 2 != 0):

            canvas.delete(ota_label)

            canvas.delete(ota_choices)

            canvas.delete(ota_button)

            self.flag_ota_button+=1

            return

        else:

            self.devices = ["device1","device2","device3","device4","device5","device6","device7","device8","device9"]         

            device_list,serial_list = count_unique_devices(myConnection)#get list of unique device names from MYSQL DB

            print("device list OTA=",device_list)

            print("sertial list OTA=",serial_list)

            label = tk.Label(canvas,text="Pasirinkti kuria dezute atnaujinti",font='Helvetica 12 bold')

            choices = ttk.Combobox(canvas,values=self.devices)

            choices.current(0)

            button = tk.Button(canvas,text="Patvirtinti",command =lambda: self.device_confirmed(choices,"ota"))

            self.ota_label = canvas.create_window(960,250,window=label)

            self.ota_choices = canvas.create_window(960,300,window=choices)

            self.ota_button = canvas.create_window(960,350,window=button)

            self.flag_ota_button+=1





    def device_confirmed(self,choices,status):

        device = choices.get()

        if(status == "ota"):

            print("device=",device)

            mqttc.publish(device+"/status","OTA")

            canvas.delete(self.ota_label)

            canvas.delete(self.ota_choices)

            canvas.delete(self.ota_button)

        else:

            publish_mqtt_json_item_inside("",device,"","")

            canvas.delete(remove_item_label)

            canvas.delete(remove_item_choices)

            canvas.delete(remove_item_button)

            remove_item_from_device(myConnection,device)
i use some global variables inside my class because I have other functions outside the class that access to those variables.





my main code starts like that:

master = tk.Tk()

app = Application(master=master)

app.Create_canvas(1920,1080)

app.Application_Intro()

app.mainloop()
As you can see from the function above, i am calling Application_intro function which is handles my main GUI. One of the GUI widgets is the "new operation" button:

After pressing that button, the code executed is:

    def New_operation(self):

        print("new_operation")

        restart_devices(myConnection,"reset")

        canvas.delete("all")

        app.after(1000,self.Application_Intro)

        print("new operation end")
As you can see from the code above, the function deletes all previous canvas elements and calls new Application_intro. I just want to delete whatever happened during the previous task and start a fresh task. A fresg task is what I consider an idle state, The application intro creates a text entry and sits there untill I write something in the text box and press enter key



The issue is:

I can see that canvas.delete("all") properly deletes all canvas widgets and Application_Intro code is being called to generate a new widgets HOWEVER, it does not seem to start a "fresh" task. It continues to execute where its left



For example, if I am in the middle of a task:

The code below is my task:

def handle_next_pick():

    print("handling next pick")

    global item_number

    global flag_next_device

    print("pick counter =",pick_counter)

    if(pick_counter < Counter_global):

        if(flag_next_device>0):

            print("flag_next_device is higher than 1")

            if(item_number > 0):#only check if it divides by counter if its higher then 0 to avoid glitch

                if(item_number % Counter_global_temp==0):

                    item_number=item_number-(Counter_global_temp)

            app.update_rectangle_color(Device_global_temp[item_number])

            string = Device_global_temp[item_number]+"/"+"status"

            mqttc.publish(string,"ACTIVE",1)

            flag_next_device=0

              

            if(update_DB==1):

                update_DB_Quantity(myConnection)

            else:

                print("Dont need to update DB")

            app.after(100,handle_next_pick)

        else:

            app.after(100,handle_next_pick)

            print("wait for next command")

    else:

        app.after(1000,handle_end_of_picking)
I am not gonna go into details about what this code does, but it waits for me to "initiate" the next pick.That is being done when I toggle a sensor on ESP32 device. When this task is started, it prints me the message ("wait for next command"). When I execute new_operation() which supposed to clear canvas and start a fresh task, it continues to print "wait for next command" but that should not be the case. Becuase I want to cancel the current task and just go to the idle state where I the program waits for my textbox input



In short, I want to be able to press a button which would clear whole GUI, and and basically start a "new" tkinter GUI. But my current function does not stop the current mainloop even though it clears canvas and creates a new instance of Application_intro
Reply
#2
canvasname.delete("all") replace canvasname with your canvas name
Reply
#3
(Sep-30-2020, 12:24 PM)Larz60+ Wrote: canvasname.delete("all") replace canvasname with your canvas name

Thanks for the response

I am indeed using this function to delete the canvas:
def New_operation(self):
 
    print("new_operation")
 
    restart_devices(myConnection,"reset")
 
    canvas.delete("all")
 
    app.after(1000,self.Application_Intro)
 
    print("new operation end")
And that works as expected, the canvas gets deleted and the new GUI is being created through Application_Intro call. However, it does not stop the code where its left. Even though I initiate New_operation, I am still getting serial prints from the previous application
Reply
#4
Quote:And that works as expected, the canvas gets deleted and the new GUI is being created through Application_Intro call.

Output:
tkinter reference manual (get a copy at: http://reu.cct.lsu.edu/documents/Python_Course/tkinter.pdf ) states: delete(self, *args) | Delete items identified by all tag or ids contained in ARGS.
delete does not delete the Canvas widget, it only clears it.
if you wish to remove the widget, use destroy

Quote:However, it does not stop the code where its left.

This is controlled by your code.
You should not be recreating the widget, unless you first 'destroy' the original.
Reply
#5
Delete may cause problems if there something that is still trying to draw on the old canvas. You need to stop drawing, clear the canvas, and then start the new drawing.
Reply
#6
Thanks for all responses ! It is not fully clear to me yet. I will try to explain best to my ability my recent findings:
def handle_next_pick():
 
    print("handling next pick")
 
    global item_number
 
    global flag_next_device
 
    print("pick counter =",pick_counter)
 
    if(pick_counter < Counter_global): ## THIS IS ALWAYS TRUE WHEN THE OPERATION STARTS
 
        if(flag_next_device>0): //FLAG_NEXT_DEVICE IS SET BY TRIGGERING THE REMOTE SENSOR. 
 
            print("flag_next_device is higher than 1")
 
            if(item_number > 0):#only check if it divides by counter if its higher then 0 to avoid glitch
 
                if(item_number % Counter_global_temp==0):
 
                    item_number=item_number-(Counter_global_temp)
 
            app.update_rectangle_color(Device_global_temp[item_number])
 
            string = Device_global_temp[item_number]+"/"+"status"
 
            mqttc.publish(string,"ACTIVE",1)
 
            flag_next_device=0
 
               
 
            if(update_DB==1):
 
                update_DB_Quantity(myConnection)
 
            else:
 
                print("Dont need to update DB")
 
            app.after(100,handle_next_pick)
 
        else:
 
            app.after(1000,handle_next_pick)#IF THE SENSOR IS NOT SET, CHECK AGAIN AFTER 1 SECOND
            print("wait for next command")
 
    else: 
 
        app.after(1000,handle_end_of_picking)
So the code below is my main "task" code. I have addded some additional comments to explain what is going on. At the bottom of the function, I recursively call :
            app.after(1000,handle_next_pick)#IF THE SENSOR IS NOT SET, CHECK AGAIN AFTER 1 SECOND
            print("wait for next command")
This just checks whether the flag has been set every 1 second. And it continues this loop untill I complete the task.

The issue is that I want to be able to stop current task and start a new one even when I am in the middle of a current task.

Assume that I have currently started a task and this function "handle_next_pick" is being called every 1 second.

When I press the button on my GUI, it executed this function:
def New_operation(self):
  
    print("new_operation")
  
    restart_devices(myConnection,"reset")
  
    canvas.delete("all")
  
    app.after(1000,self.Application_Intro)
  
    print("new operation end")
So I obviously expect this function to clear all canvas, interrupt the current task and start a new one, however, thats not the case! After clearing canvas and displaying a new GUI , it continues to call handle_next_pick function every 1 second which is what I am trying to solve.


My recent findings are:

Instead of doing everyting when the button is pressed, I just tried to set the global flag:

def New_operation(self):
global reset
reset = 1 # this is defined as global variable therefore will be recognised outside
And then In my task code, I am constantly checking whether this flag is set:

            app.after(1000,handle_next_pick)#IF THE SENSOR IS NOT SET, CHECK AGAIN AFTER 1 SECOND
            print("wait for next command")
            # check if the flag is set to restart the program
            if (reset==1):
                reset = 0
                canvas.delete("all")
                app.after(1000,self.Application_Intro)
                return # IF RETURN USED HERE, PROGRAM RESETS AND WORKS AS EXPECT
Notice that return I have added after Calling Application_Intro. This return makes the program reset properly and start from the beggining. If I do not use return, It continues to call handle_next_pick every 1 second.
Could someone help me understand this behaviour?
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] Clickable Rectangles Tkinter Canvas MrTim 4 1,238 May-11-2021, 10:01 PM
Last Post: MrTim
  [Tkinter] Draw a grid of Tkinter Canvas Rectangles MrTim 5 1,003 May-09-2021, 01:48 PM
Last Post: joe_momma
Thumbs Up tkinter canvas; different page sizes on different platforms? philipbergwerf 4 860 Mar-27-2021, 05:04 AM
Last Post: deanhystad
  tkinter get function finndude 2 699 Mar-02-2021, 03:53 PM
Last Post: finndude
  tkinter -- after() method and return from function -- (python 3) Nick_tkinter 12 1,285 Feb-20-2021, 10:26 PM
Last Post: Nick_tkinter
  how to resize image in canvas tkinter samuelmv30 2 4,342 Feb-06-2021, 03:35 PM
Last Post: joe_momma
  Using Tkinter inside function not working Ensaimadeta 3 902 Dec-23-2020, 02:57 PM
Last Post: deanhystad
  function in new window (tkinter) Dale22 7 1,039 Nov-24-2020, 11:28 PM
Last Post: Dale22
Star [Tkinter] How to perform math function in different page of Tkinter GUI ravaru 2 1,242 Oct-23-2020, 05:46 PM
Last Post: deanhystad
  Call local variable of previous function from another function with Python3 & tkinter Hannibal 5 1,275 Oct-12-2020, 09:16 PM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020