How to make wx not block as focused window [Python] - multithreading

I made an overlay in wxpython which I use to display a bitmap on my computer. However, when I integrate it into an app that takes focus on a screen and moves my mouse, it blocks the app and never goes in the "apploop". If I comment the Overlay in the app constructor, the app works just fine but I wouldn't see the Overlay.
I tried threading the wx_app.Mainloop (I would comment self.wx_app.MainLoop()) and the apploop works fine but it now freezes the windows that's focused by the app so it's useless. Other processes like Chrome work fine.
How would I make the focused windows not blocked while having the overlay? I would need both of them at the same time.
Here's a simplified example of my code:
import win32gui, win32process, win32api
import wx
import threading
class App():
def __init__(self) -> None:
# Window focus
hwnd = win32gui.FindWindow(None, 'Exact window name')
win32gui.SetActiveWindow(hwnd)
remote_thread, _ = win32process.GetWindowThreadProcessId(hwnd)
win32process.AttachThreadInput(win32api.GetCurrentThreadId(), remote_thread, True)
prev_handle = win32gui.SetFocus(hwnd)
# Overlay
self.wx_app = wx.App()
self.overlay = Overlay()
self.wx_app.MainLoop()
# t = threading.Thread(target=self.wx_app.MainLoop)
# t.start()
self.apploop()
def apploop(self):
import time
while True:
print('Apploop running')
time.sleep(5)
class Overlay(wx.Frame):
def __init__(self):
style = ( wx.CLIP_CHILDREN | wx.STAY_ON_TOP | wx.NO_BORDER | wx.FRAME_SHAPED )
wx.Frame.__init__(self, None, title='Fancy', style = style)
self.bitmap = wx.StaticBitmap(parent=self)
self.SetSize( (320, 180) )
self.SetPosition( (10,10) )
self.SetBackgroundColour( [0,0,0,255] )
self.SetTransparent( 200 )
self.Show(True)

You were almost there. The wx.App has to be started in the main Python thread and cannot run in threading.Thread. So your long-running-code will work when running wx_app.MainLoop() in the main thread and your payload in a Thread:
self.wx_app = wx.App()
self.overlay = Overlay()
t = threading.Thread(target=self.apploop)
t.start()
self.wx_app.MainLoop()
See at the end of wx Wiki - LongRunningTasks for a very simple example, mind the wx.CallAfter or other thread-safe Python objects to communicate between worker and main thread.

Related

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

"RuntimeError: Calling Tcl from different appartment" tkinter and threading

I want to implement GUI using threading and tkinter (python 3.6).
When I run GUIExecution.py, the following error occurs.
"RuntimeError: Calling Tcl from different appartment" on self.root.mainloop() in base_gui_class.py
I am implementing it on a class basis, and the three code files are as follows.
The executable file is GUIExecution.py.
I spent a lot of time trying to fix the error, but I have not been able to fix it yet.
Please give a lot of advice.
Additionally, if I run the following code in a python2 environment, it works fine without error.
GUIExecution.py
from base_gui_class import *
from base_class import *
speed = 1000
height = 500
width = 700
base_model = base_class()
gui = base_gui_class(base_model, speed, height, width)
base_model.visualize()
base_class.py
class base_class():
genes = []
dicLocations = {}
gui = ''
best = ''
time = 0
def __init__(self):
pass
def visualize(self):
if self.gui != '':
self.gui.start()
def registerGUI(self, gui):
self.gui = gui
base_gui_class.py
import threading
import tkinter as tk
import math
import threading
import time
class base_gui_class(threading.Thread):
root = ''
canvas = ''
speed = 0
base_model = ''
def __init__(self, base_model, speed, h, w):
threading.Thread.__init__(self)
self.base_model = base_model
base_model.registerGUI(self)
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, height=h, width=w)
self.canvas.pack()
self.root.title("Test")
self.speed = 1 / speed
def run(self):
self.root.mainloop()
def update(self):
time.sleep(self.speed)
width = int(self.canvas.cget("width"))
height = int(self.canvas.cget("height"))
self.canvas.create_rectangle(0, 0, width, height, fill='white')
def stop(self):
self.root.quit()
To a very good first and second approximation, the core of Tk is single threaded. It can be used from multiple threads, but only by initialising it separately in each of those threads. Internally, it uses thread-specific variables extensively to avoid the need for major locking (that is, it has nothing like a big Global Interpreter Lock) but that means you must not cheat. Whatever thread initialises a Tk context must be the only thread that interacts with that Tk context. This includes loading the Tkinter module so you are effectively restricted to using Tkinter from your main thread only; working around this is serious expert's-only stuff.
I recommend that you make your worker threads make changes to your GUI by posting events to it using a queue (or otherwise interlock with critical sections and condition variables, though I find queues easier in practice).
pip install tkthread
#call the function which shows error like this:
tkthread.call_nosync(yourfunction)
this tkthread library handles all the threading internally by itself.
I recommend you reading the documentation of this library:https://pypi.org/project/tkthread/ .

pyQt and threading application crash

I wrote simple program which has pyQt interface with 2 buttons (start and cancel). Start button runs some calculations in the background (by starting update function) and thanks to threading I can still use UI.
But the application crashes after 10sec - 2 minutes. UI just dissapears, program shutdown.
when I use pythonw to run app without console thread crashes after ~25 sec but gui still works.
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.show()
def update(self,stop_event):
while True and not stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
self.guiUpdate()
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update, args=(self.stop_event,))
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
if __name__ == '__main__':
main()
I dont know if I'm doing threading right. I found example like this on stack. I'm quite new to python and I'm using threading for the first time.
It is most likely due to calling a GUI function in your separate thread. PyQt GUI calls like setText() on a QLineEdit are not allowed from a thread. Anything that has PyQt painting outside of the main thread will not work. One way to get around this is to have your thread emit a signal to update the GUI when data is ready. The other way is to have a timer periodically checking for new data and updating the paintEvent after a certain time.
========== EDIT ==========
To Fix this issue I created a library named qt_thread_updater. https://github.com/justengel/qt_thread_updater This works by continuously running a QTimer. When you call call_latest the QTimer will run the function in the main thread.
from qt_thread_updater import get_updater
lbl = QtWidgets.QLabel('Value: 1')
counter = {'a': 1}
def run_thread():
while True:
text = 'Value: {}'.format(counter['a'])
get_updater().call_latest(lbl.setText, text)
counter['a'] += 1
time.sleep(0.1)
th = threading.Thread(target=run_thread)
th.start()
========== END EDIT ==========
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
display_update = QtCore.pyqtSignal() # ADDED
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.display_update.connect(self.guiUpdate) # ADDED
self.show()
def update(self):
while True and not self.stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
# self.guiUpdate()
self.display_update.emit() # ADDED
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update)
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
# ex.update() # - this does nothing
if __name__ == '__main__':
main()
The other thing that could be happening is deadlock from two threads trying to access the same variable. I've read that this shouldn't be possible in python, but I have experienced it from the combination of PySide and other Python C extension libraries.
May also want to join the thread on close or use the QtGui.QApplication.aboutToQuit signal to join the thread before the program closes.
The Qt documentation for QThreads provides two popular patterns for using threading. You can either subclass QThread (the old way), or you can use the Worker Model, where you create a custom QObject with your worker functions and run them in a separate QThread.
In either case, you can't directly update the GUI from the background thread, so in your update function, the guiUpdate call will most likely crash Qt if it tries to change any of the GUI elements.
The proper way to run background processes is to use one of the two QThread patterns and communicate with the main GUI thread via Signals and Slots.
Also, in the following bit of code,
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
app.exec_ starts the event loop and will block until Qt exits. Python won't run the ex.update() command until Qt has exited and the ex window has already been deleted, so you should just delete that command.

PyQt: wait until window is visible

I do run some time consuming calculations and want to show a splash screen while the process is running. I've tried:
form = QSpashScreen()
form.showMessage('Please wait ...')
form.show()
time_consuming_function()
form.close()
But the splash screen doesn't actually appear. How can I make this work?
You need to specify one parent for QSplashScreen, otherwise it will map the constructor of this in native Qt:
QSplashScreen::QSplashScreen ( const QPixmap & pixmap = QPixmap(), Qt::WindowFlags f = 0 )
Construct a splash screen that will display the pixmap.
There should be no need to set the widget flags, f, except perhaps Qt::WindowStaysOnTopHint.
If you don't have a parent widget for this, you can assign the QDesktopWidget as the parent
from PyQt4.QtGui import *
import time
def time_consuming_function():
time.sleep(10)
def main():
app = QApplication(sys.argv)
form = QSplashScreen(QDesktopWidget().screen())
form.showMessage('Please wait ...')
form.resize(1000, 1000)
form.move(0, 0)
form.show()
time_consuming_function()
form.close()
app.exec_()
if __name__ == '__main__':
import sys
main()
By the way, you should not use the "tiem_consuming_function" with the same thread of main, this will block the UI thread, make the UI thread unable to respond the user input, looks like the UI is freezing.

PyQt doesn't switch the screen

I've written a GUI program which do a lot of calculation in the back, at the same time, display a progress bar. After it was done, a new screen will show the result.
Then I want to make another interface before the calculation to let the user select if they want to use the last calculation result, thus skipping the calculation.
I made a screen with a button which connected to the calculation, and a combo box to select last calculation result file.
However, when I clicked the button, it did nothing. And after around 10 secs( the duration for the calculation ), the result screen pop open. Thus, it skipped the progress bar screen. Why?
This it part of the original program:
import sys
import configparser
import getpass
import telnetlib
import time
import subprocess
from datetime import *
from log_tracker import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
class Task_Checker(QMainWindow):
def __init__(self):
super(Task_Checker, self).__init__()
config = configparser.RawConfigParser()
config.read('profile.cfg')
self.log_path = config.get('Config', 'log_path')
self.log_prefix = config.get('Config', 'log_prefix')
self.log_suffix = config.get('Config', 'log_suffix')
self.initUI()
def check_production(self):
self.log_tracker = Log_Tracker(self)
self.log_tracker.tick.connect(self.pbar.setValue)
self.log_tracker.parseConfig()
self.log_tracker.connectDb()
self.log_tracker.trackLog()
def initUI(self):
self.resize(1400, 768)
self.center()
self.statusBar().showMessage('Checking Production Programs')
self.wait_message = QLabel('Checking Production Programs')
self.wait_message.setAlignment(Qt.Alignment(Qt.AlignHCenter))
self.pbar = QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
vbox = QVBoxLayout()
vbox.addWidget(self.wait_message)
vbox.addWidget(self.pbar)
tmpWidget2 = QWidget()
tmpWidget2.setLayout(vbox)
self.setCentralWidget(tmpWidget2)
self.show()
self.check_production()
self.pbar.hide()
self.statusBar().showMessage('Processing Information')
self.tabs = QTabWidget()
mua_table = self.processInfo('MUA')
bps_table = self.processInfo('BPS')
obdua_table = self.processInfo('OBDUA')
sua_table = self.processInfo('SUA')
ngr_ftp_table = self.processInfo('NGR_FTP')
bpspdfbill_table = self.processInfo('BpsPdfBill')
disk_space_table = self.processInfo('Disk_Space')
self.tabs.addTab(mua_table, 'MUA')
self.tabs.addTab(bps_table, 'BPS')
self.tabs.addTab(obdua_table, 'ODBUA')
self.tabs.addTab(sua_table, 'SUA')
self.tabs.addTab(ngr_ftp_table, 'NGR_FTP')
self.tabs.addTab(bpspdfbill_table, 'BpsPdfBill')
self.tabs.addTab(disk_space_table, 'Disk_Space')
self.setCentralWidget(self.tabs)
self.statusBar().showMessage('Ready')
self.setWindowTitle('Task Checker')
self.show()
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def processInfo(self, project_name):
...the processing...
In order to add a new screen before the progress bar ( originally it loads the calculation right away ), I made a fews changes to initUI() and move the calculation part to a new sub routine checkProd(), then connect it with a button:
import sys
import configparser
import getpass
import telnetlib
import time
import subprocess
from datetime import *
from log_tracker import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
class Task_Checker(QMainWindow):
def __init__(self):
super(Task_Checker, self).__init__()
config = configparser.RawConfigParser()
config.read('profile.cfg')
self.log_path = config.get('Config', 'log_path')
self.log_prefix = config.get('Config', 'log_prefix')
self.log_suffix = config.get('Config', 'log_suffix')
self.initUI()
def check_production(self):
self.log_tracker = Log_Tracker(self)
self.log_tracker.tick.connect(self.pbar.setValue)
self.log_tracker.parseConfig()
self.log_tracker.connectDb()
self.log_tracker.trackLog()
def initUI(self):
self.resize(1400, 768)
self.center()
btn_check = QPushButton('Check Lastest Status', self)
btn_check.setToolTip('Click this if you want to check the lastest status in production')
combo = QComboBox()
dirlist = os.listdir(self.log_path)
for f in dirlist:
combo.addItem(f)
QtCore.QObject.connect(btn_check, QtCore.SIGNAL('clicked()'), self.checkProd)
hbox = QHBoxLayout()
hbox.addWidget(btn_check)
hbox.addWidget(combo)
tmpWidget = QWidget()
tmpWidget.setLayout(hbox)
self.setCentralWidget(tmpWidget)
self.show()
def checkProd(self):
self.statusBar().showMessage('Checking Production Programs')
self.wait_message = QLabel('Checking Production Programs')
self.wait_message.setAlignment(Qt.Alignment(Qt.AlignHCenter))
self.pbar = QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
vbox = QVBoxLayout()
vbox.addWidget(self.wait_message)
vbox.addWidget(self.pbar)
tmpWidget2 = QWidget()
tmpWidget2.setLayout(vbox)
self.setCentralWidget(tmpWidget2)
self.show()
self.check_production()
self.pbar.hide()
self.statusBar().showMessage('Processing Information')
self.tabs = QTabWidget()
mua_table = self.processInfo('MUA')
bps_table = self.processInfo('BPS')
obdua_table = self.processInfo('OBDUA')
sua_table = self.processInfo('SUA')
ngr_ftp_table = self.processInfo('NGR_FTP')
bpspdfbill_table = self.processInfo('BpsPdfBill')
disk_space_table = self.processInfo('Disk_Space')
self.tabs.addTab(mua_table, 'MUA')
self.tabs.addTab(bps_table, 'BPS')
self.tabs.addTab(obdua_table, 'ODBUA')
self.tabs.addTab(sua_table, 'SUA')
self.tabs.addTab(ngr_ftp_table, 'NGR_FTP')
self.tabs.addTab(bpspdfbill_table, 'BpsPdfBill')
self.tabs.addTab(disk_space_table, 'Disk_Space')
self.setCentralWidget(self.tabs)
self.statusBar().showMessage('Ready')
self.setWindowTitle('Task Checker')
self.show()
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def processInfo(self, project_name):
...the processing...
This is a lot of unnecessary code to read, but what I have managed to parse out of it, is that you are doing heavy calculations in the main thread, while expecting another widget in the main thread to also maintain responsiveness.
This is a common pitfall when working with PyQt4 (and probably most GUI frameworks that use a main thread event loop). The issue is that when you start your app, the main event loop constantly polls for new events to process from your GUI. Its expecting that every operation should either take a very short time to complete, or, periodically relinquish control back to the eventloop. Right now, your calculation is hogging up all the availability of the main thread, and anything that your progress window is trying to do is just backing up into a queue waiting for a chance for the event loop to pick it up.
The simple answer to this is: Do any heavy calculations in a separate QThread, and communicate back to your main thread via signals. This will require a little bit of retooling on you part. You should not put anything heavy into an init of any class. But you have managed to address this in your second example by attaching the computation to a button. Good start. The button should actually start the calc in another thread, thus not blocking up the main thread.
Qt4.7 Threading Basics
GUI Thread and Worker Thread
As mentioned, each program has one thread
when it is started. This thread is called the "main thread" (also
known as the "GUI thread" in Qt applications). The Qt GUI must run in
this thread. All widgets and several related classes, for example
QPixmap, don't work in secondary threads. A secondary thread is
commonly referred to as a "worker thread" because it is used to
offload processing work from the main thread.
...
Using Threads
There are basically two use cases for threads:
1. Make
processing faster by making use of multicore processors.
2. Keep the GUI
thread or other time critical threads responsive by offloading long
lasting processing or blocking calls to other threads.
As a quick fix if you just want to see some results, you can periodically call QtGui.QApplication.processEvents() from your calculation method. This will every so often allow the event loop to flush out pending operations. Doing so will let something like your progress widget actually function. Essentially what you are doing is manually "pumping" the event loop. This isn't the best approach though. Usually its reserved for lighter weight one off stuff. If you want the best performance, move it to a thread.

Resources