Boxuan Zhong
Boxuan Zhong

Reputation: 31

time.sleep blocks pressing tkinter.Button

I am trying to implement an app to show image for 6s and then jump to another page with radio buttons to let them rate their own feeling about the images (give 15s for rating) and then show the next image. However, for counting down 15s, I used time.sleep() function, which make the program very slow for pressing radiobuttons (but my clicking, the button can still be pressed). I am wondering whether there is a way that the count down will not influence the program but just let the program jump to the next page when time up. Here is my code :

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.geometry('1800x970+15+5')
        self.title('Emotion Test')
        # the container is where we'll stack a bunch of frames
        # on top of each other, then the one we want visible
        # will be raised above the others
        container = tk.Frame(self)
        container.pack(side="top", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in (StartPage, Show_image, Rate_e, EndPage):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame

            # put all of the pages in the same location;
            # the one on the top of the stacking order
            # will be the one that is visible.
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame("StartPage")
    def show_frame(self, page_name):
        '''Show a frame for the given page name'''
        frame = self.frames[page_name]
        frame.tkraise()
        frame.execute()


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text=instructions, width=1500, height=700, font=TITLE_FONT)
        label.pack(side="top")
        label.place(x=100, y=40, width=1500, height=700)
        button1 = tk.Button(self, text="Start", height = 5, width = 10,
                            command=lambda: controller.show_frame("Show_image"))
        button1.pack(side="bottom")

    def execute(self):
        current = -1

class EndPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text=instructions, width=1500, height=700,
        font=TITLE_FONT)
        label.pack(side="top")
        label.place(x=100, y=40, width=1500, height=700)

    def execute(self):
        current = -1

class Show_image(tk.Frame):

    def __init__(self, parent, controller):
        global seconds
        tk.Frame.__init__(self, parent)
        self.controller = controller
        global lbPic
        global timer1
        lbPic = tk.Label(self, text='Image System', width=1200, height=900)
        lbPic.place(x=20, y=50, width=1200, height=900)
        lbPic.pack(side="top", fill="both", expand=True)
        timer1 = tk.Label(self, text='timer', width=50, height=10)
        timer1.place(x=1325, y=2, width=50, height=10)
        timer1.pack(side="bottom", fill="both", expand=True)
    def execute(self):
        if (self.changePic(1)):
           for k in range(seconds, 0, -1):
               timer1["text"] = "Time left : " + str(k) + " s"
               self.update()
               time.sleep(1)
           self.controller.show_frame("Rate_e")
        else:
           self.controller.show_frame("EndPage")

    def changePic(self, flag):
        global current

        new = current + flag
        if new<0:
           #tkMessageBox.showerror('', 'last picture')
           return 0
        elif new>=len(pics):
             #tkMessageBox.showerror('', 'last picture')
             return 0
        else:
             #get the next picture
             pic = pics[new]
             im = Image.open(pic)
             w, h = im.size
             if w>1200:
                h = int(h*1200/w)
                w = 1200
             if h>800:
                w = int(w*800/h)
                h = 800
             im = im.resize((w,h))
             im1 = ImageTk.PhotoImage(im)
             lbPic['image'] = im1
             lbPic.image = im1
             current = new
             return 1

class Rate_e(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        global wait
        self.controller = controller
        global lbrate1
        global lbrate2
        global lbrate3
        global timer2
        lbrate1 = tk.Label(self, text='Pleasure', width=1400, height=260)
        lbrate1.place(x=50, y=10, width=1400, height=260)
        lbrate1.pack(side="top", fill="both", expand=True)
        lbrate2 = tk.Label(self, text='Excited', width=1400, height=260)
        lbrate2.place(x=50, y=230, width=1400, height=260)
        lbrate2.pack(side="top", fill="both", expand=True)
        lbrate3 = tk.Label(self, text='Controlled', width=1400, height=260)
        lbrate3.place(x=50, y=650, width=1400, height=260)
        lbrate3.pack(side="top", fill="both", expand=True)
        timer2 = tk.Label(self, text='timer', width=50, height=10)
        timer2.place(x=800, y=850, width=50, height=10)
        timer2.pack(side="bottom", fill="both", expand=True)

        MODES = [
            ("1", 1),
            ("2", 2),
            ("3", 3),
            ("4", 4),
            ("5", 5),
            ("6", 6),
            ("7", 7),
            ("8", 8),
            ("9", 9)
        ]
        global v1
        v1 = tk.StringVar()
        v1.set("score1") # initialize
        xaxis = 0
        for text, mode in MODES:
            b1 = tk.Radiobutton(self, text=text,
                        variable=v1, value=mode)
            b1.config(indicatoron=False,width=10,height=2)
            b1.place(x=140 + xaxis*127, y=240 , width=100, height=30)
            #b.pack(side="left")
            xaxis = xaxis + 1
        global v2
        v2 = tk.StringVar()
        v2.set("score2") # initialize
        xaxis = 0
        for text, mode in MODES:
            b2 = tk.Radiobutton(self, text=text,
                        variable=v2, value=mode)
            b2.config(indicatoron=False,width=10,height=2)
            b2.place(x=140 + xaxis*128, y=510 , width=100, height=30)
            #b.pack(side="left")
            xaxis = xaxis + 1
        global v3
        v3 = tk.StringVar()
        v3.set("score3") # initialize
        xaxis = 0
        for text, mode in MODES:
            b3 = tk.Radiobutton(self, text=text,
                        variable=v3, value=mode)
            b3.config(indicatoron=False,width=10,height=2)
            b3.place(x=140 + xaxis*125, y=800 , width=100, height=30)
            #b.pack(side="left")
            xaxis = xaxis + 1

    def execute(self):
        for k in range(wait, 0, -1):
            timer2["text"] = "Time left : " + str(k) + " s"
            self.update()
            time.sleep(1)
        Pleasure = v1.get()
        Excited = v2.get()
        Control = v3.get()
        print(Pleasure)
        print(Excited)
        print(Control)
        self.controller.show_frame("Show_image")

if __name__ == "__main__":
    suffix = ('.jpg', '.bmp', '.png')
    pics = [image_path + p for p in os.listdir(image_path) if p.endswith(suffix)]
    seconds = 2
    wait = 6
    current = -1
    app = SampleApp()
    app.mainloop()

Upvotes: 1

Views: 1133

Answers (1)

martineau
martineau

Reputation: 123531

Using time.sleep() in a tkinter program will make it unresponsive since it keeps the mainloop() from executing and able to respond to events.

To void that use the universal widget method after().

So, in your case use:

self.after(1000)  # 1000 ms = 1 sec

instead of

time.sleep(1)

Here's some documentation I found about it.

Upvotes: 2

Related Questions