PYTHON - how to use a loop without stopping Tkinter

Asked

Viewed 854 times

0

I have this program which is a kind of 'Force Brute' (I automated a process of clicking on a place, typing a number, and clicking elsewhere with @Pyautogui), I need to use a loop but when the loop starts Tkinter to, so I couldn’t think of how to do something to pause the loop! the code:

from tkinter import *
import pyautogui
import time

class ForceBrute:
    def __init__(self,master=None):
#faz com que a janela não possa ser redimensionada pelo usuario
        master.resizable(width=False,height=False)

#Containers para os itens da interface
        self.con1 = Frame(master)
        self.con1['pady'] = 8
        self.con1.pack()

        self.con2 = Frame(master)
        self.con2['pady'] = 15
        self.con2.pack()


        self.con3 = Frame(master)
        self.con3['pady'] = 30
        self.con3.pack()


        self.con4 = Frame(master)
        self.con4['pady'] = 10
        self.con4.pack()


#itens da interface
        self.tTitle = Label(self.con1)
        self.tTitle['font'] = ('Roboto','30')
        self.tTitle['text'] = 'SiriusForce'
        self.tTitle.pack()

#recebe o valor de aparti de quando deve iniciar
        self.eDe = Entry(self.con2,justify = 'center',relief=RIDGE,width=3)
        self.eDe['font'] = ('Roboto', '20')
        self.eDe.pack(side=LEFT)
#coloca 100 no campo como padrão
        self.eDe.insert(0,'100')

        self.tAte = Label(self.con2)
        self.tAte['font'] = ('Roboto', '20')
        self.tAte['text'] = 'Ate:'
        self.tAte.pack(side=LEFT)

#recebe ate que valor deve ir
        self.eAte = Entry(self.con2,justify = 'center',relief=RIDGE,width=3)
        self.eAte['font'] = ('Roboto', '20')
        self.eAte.pack(side=LEFT)
        self.eAte.insert(0,'999')

#botão para iniciar 
        self.bIniciar = Button(self.con3,relief=RIDGE,fg='blue')
        self.bIniciar['font'] =('Roboto', '25')
        self.bIniciar['text'] = 'Iniciar'
        self.bIniciar['command'] = self.iniciar
        self.bIniciar.pack(side=LEFT)


        self.bPausar = Button(self.con3,relief=RIDGE,fg='red')
        self.bPausar['font'] =('Roboto', '25')
        self.bPausar['text'] = 'Pausar'
#      self.bPausar['command'] =           seria para pausar o 'laço' 
        self.bPausar.pack(side=LEFT)


#exibe em que numero parou
        self.eCont = Entry(self.con4,justify= 'center',relief=RIDGE,width=3,bg='black',fg='green')
        self.eCont['font'] = ('Roboto', '30')
        self.eCont.pack()




        self.cod = 0


    def iniciar(self):
        self.de = int(self.eDe.get()) -1 
        self.ate = int(self.eAte.get()) 

        print('Iniciado De:',self.de,'Ate:',self.ate) # só para visualizar 

        self.cod = self.de

        while(self.cod < self.ate):

            self.cod +=1

            pyautogui.doubleClick(697,363)
            pyautogui.typewrite(str(self.cod))

            print(self.cod)

            self.eCont.delete(0,END)
            self.eCont.insert(0,self.cod)

            pyautogui.click(880,516)

            time.sleep(1.5)


root = Tk()
root.geometry('220x350+400+100')
root.title('Generico')
root.attributes('-topmost',True)
ForceBrute(root)
root.mainloop()

I’ve seen in some places I can use 'root.after()' but I couldn’t implement.

1 answer

2


The program runs on a single thread - then either it runs the Tkinter loop, responsible for receiving and executing mouse events, keyboard, etc... or it stays in your while...time.sleep(1.5).

This type of program (graph) always has to be based on responding to events: what keeps running all the time is the event loop - in the case of Tkinter, which is activated by mainloop. The code snippets we write have to perform a task, and return the processing to the main loop (also applies to Javascript code embedded in web pages, for example).

What has to be done then is, along with the processing of your code, schedule an event to call the function again. This scheduling event is what is done with the method call .after(intervalo, objeto chamável)

This calling event will run your code that has to be repeated once, and schedule your next call - not create a while. In the case of your code, the most suitable is to place the body of your while as a separate method, which will be called directly by Tkinter, with the use of .after:

class ForceBrute:
    def __init__(self,master=None):
        # Guarda uma referência ao root:
        self.master = master
        #faz com que a janela não possa ser redimensionada pelo usuario
        master.resizable(width=False,height=False)
        ...

    ...

    def iniciar(self):
        self.de = int(self.eDe.get()) -1 
        self.ate = int(self.eAte.get()) 

        print('Iniciado De:',self.de,'Ate:',self.ate) # só para visualizar 

        self.cod = self.de
        self.verifica()

    def verifica(self):

        self.cod +=1

        pyautogui.doubleClick(697,363)
        pyautogui.typewrite(str(self.cod))

        print(self.cod)

        self.eCont.delete(0,END)
        self.eCont.insert(0,self.cod)

        pyautogui.click(880,516)

        if (self.cod < self.ate):
            self.master.after(1500, self.verifica)

So - the only other thing that was needed there was to keep a reference to the program window within the class - because that’s where the method is .after.

And the other important detail: when calling this type of method in which a function is passed to be called back (callback), the parentheses are never placed after the name of the function or method. This would cause the function to be executed there, and its return result to be sent as a parameter to the after. Without parentheses, functions and methods are treated like any other variable by Python.

(1500 is just your 1.5 second rewritten in milliseconds)

  • I had an initial oddity for using directly self.after. So I went to see that in my play projects I always inherit from tk.Frame, then that inheritance would be part of the field self.master of your solution. Very good the answer

  • 1

    That - in particular, I consider nay inherit from Tk. Frame a major breakthrough - I don’t know why the tutorials keep recommending this: it’s a well-known anti-pattern (its class gets hundreds of attributes and methods that have no relation to the logic of its application. Keeping a reference to root in any attribute (in the self.master case) solves this.

  • Once again Jsbueno saving the day kkkk! solved my problem, thank you! : 3

Browser other questions tagged

You are not signed in. Login or sign up in order to post.