Search code examples
pythonmultithreadingtkinterprocesspywinauto

Python script works well on its own, but crashes when running it form another script using Tkinter


I would like to execute a python script sequence.py by clicking a button on usercontrol.py, all the necessary code I want to initiate is located in sequence.py main() function sequentially.

The script sequence.py works fine by itself, and if I try to call the script from another script simply like this test.py:

import sequence as sq

sq.main()

it will also run fine.

But if I try to run it by clicking a button in usercontrol.py which contains variuos Tkinter GUI elements:

import sequence as sq
import tkinter as tk
import customtkinter

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()
             .... CODE ....
             self.start_button_1 = customtkinter.CTkButton(master=self.frame_1, text="Start", command=sq.main)
             .... CODE ....

if __name__ == "__main__":
    app = App()
    app.mainloop()

It will crash at the crucial first steps of sequence.py.

While I think this has more to do with Tkinter, I am trying to automate a legacy software using pywinauto, the huge stack of code in sequence.py works entirely fine, except the main thing where It should open the process for automation, the window is not opened, I get a back telling me that there is no process to move windows, then after that the windows appears. sequence.py code:

import time, pywinauto
from pywinauto import application
from pywinauto.keyboard import send_keys

def main():
    # Step 1: Connects to the Application, creates directories and calibrates the main window to user monitor settings
    main_processName = "EFrame2"
    try: 
    .... CODE (works fine) ....
    except Exception:
        user_name = input('Name: ')
        user_password = input('Password: ')
        send_keys('^%e') # CTRL + ALT + E shortcut to open the application 
        time.sleep(5)
        main_pid = application.process_from_module(module = main_processName)
        main_process = application.Application().connect(process = main_pid, timeout=10)
        main_dlg = main_process.window(class_name="TFormMDI")
        monitor_set_position(main_dlg) # it crashes at this function because main_dlg it not shown
        connect_sequence(main_process, main_pid, user_name, user_password)
        pass
if __name__ == "__main__":
    main()

The most frustrating thing, is that all variables are returning the values as it should on debug mode.. It still finds the process and connects to it. Then I get the Traceback, and instantly the Windows that should be shown prior - appears.

To summarize, using a tkinter button to initiate the script with pywinauto somehow doesn't work when launching a new process, maybe this could be a processing/thread problem? Any pointers to solving this issue are welcome.

EDIT 1 (adding Traceback if needed):

Traceback (most recent call last):
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\application.py", line 250, in __resolve_control
    ctrl = wait_until_passes(
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\timings.py", line 458, in wait_until_passes
    raise err
pywinauto.timings.TimeoutError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 553, in _clicked
    self._command()
  File "c:\Dokumentai\auto\module\sequence.py", line 567, in main
    monitor_set_position(main_dlg)
  File "c:\Dokumentai\auto\module\sequence.py", line 95, in monitor_set_position
    main_dlg.move_window(x=0, y=0, width=800, height=800, repaint=True)
    ^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\application.py", line 396, in __getattribute__
    ctrls = self.__resolve_control(self.criteria)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\application.py", line 261, in __resolve_control
    raise e.original_exception
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\timings.py", line 436, in wait_until_passes
    func_val = func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\application.py", line 203, in __get_ctrl
    dialog = self.backend.generic_wrapper_class(findwindows.find_element(**criteria[0]))
                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\linas\AppData\Local\Programs\Python\Python311-32\Lib\site-packages\pywinauto\findwindows.py", line 87, in find_element
    raise ElementNotFoundError(kwargs)
pywinauto.findwindows.ElementNotFoundError: {'class_name': 'TFormMDI', 'backend': 'win32', 'process': 10812}

Solution

  • Answering my own question, if someone would have a similar issue in the future with Tkinter.

    The problem was Threading, in my sequence.py, I was using timing functions like time.sleep(5), and it was causing problems, lagging ant etc. when using on a single thread together with GUI. I don't understand the details about threading, I am beginner myself, but you can probably count on this youtube video to help you out.

    The ultimate change that I have done that fixed everything for me is in usercontrol.py, I have changed the command of the button and put the start script into a function:

    import sequence as sq
    import tkinter as tk
    import customtkinter
    import threading
    
    class App(customtkinter.CTk):
        def __init__(self):
            super().__init__()
                 .... CODE ....
                 self.start_button_1 = customtkinter.CTkButton(master=self.frame_1, text="Start", command=lambda: threading.Thread(target=self.initiate_weekly_report_module).start())
                 .... CODE ....
        def initiate_weekly_report_module(self):
            sq.main()
    
    if __name__ == "__main__":
        app = App()
        app.mainloop()