Why while loop freeze api gui [duplicate] - python

This question already has answers here:
Equivalent to time.sleep for a PyQt application
(5 answers)
Closed 1 year ago.
I trying create GUI Api. First i build python script with only print information in console.
So I wanted to rebuild applications into applications with an interface. I decided to use PyQt5
Like this:
To(first look):
I ran into a problem with the loop While. Aplication just freeze when while is runing
I prepared a short script simulating the problem. The main program looks different
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
from termcolor import colored
import time
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'API NORD'
self.left = 0
self.top = 0
self.width = 300
self.height = 200
self.setWindowTitle(self.title)
self.resize(800, 600)
self.center()
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
def center(self):
# geometry of the main window
qr = self.frameGeometry()
# center point of screen
cp = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qr.moveCenter(cp)
# top left of rectangle becomes top left of window centering it
self.move(qr.topLeft())
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.pushButton1 = QPushButton("Run")
self.layout.addWidget(self.pushButton1)
self.pushButton1.clicked.connect(self.button2_clicked)
self.textedit = QtWidgets.QTextEdit(readOnly=True)
self.layout.addWidget(self.textedit)
self.textedit.setText("STATUS")
def onClicked(self):
radioButton = self.sender()
if radioButton.isChecked():
x=0
# print("Shop is %s" % (radioButton.shop))
self.Sklep=radioButton.shop
self.l1.setText(self.Sklep)
return
def checkBulkStatus(self):
Status = "Start"
x=0
self.textedit.setText("Start")
while x < 5:
print("Aktualny Status:", colored(Status,"yellow"))
Status="Running"
self.textedit.append(Status)
if Status=="FAILED":
print("Error")
break
time.sleep(2.5)
x+=1
print("Aktualny Status: ", colored("COMPLETED", "green"))
self.textedit.setText("COMPLETED")
def button2_clicked(self):
self.checkBulkStatus()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
In main program I ussing while to check status of BULK request in GraphQL:
def checkBulkStatus(self):
self.url = self.auth(self.Sklep)["url_auth"]
print(self.url)
Status = "Start"
self.textedit.setText("Start")
while Status != "COMPLETED":
print("Aktualny Status:", colored(Status,"yellow"))
checking = self.Core.callShopifyGraphQL(self.Core.CheckQuery,self.url)
result = checking.json()
Status=result["data"]["currentBulkOperation"]["status"]
self.textedit.append(Status)
if Status=="FAILED":
print(result["data"]["currentBulkOperation"])
break
time.sleep(2.5)
print("Aktualny Status: ", colored("COMPLETED", "green"))
URL_bulk=result["data"]["currentBulkOperation"]["url"]

The problem is that the gui runs in the same thread as the script, so when you run the script it freezes the interface. To prevent this from happening, you need to run the script in a thread, as this way you can share variables with the main thread.
I hope it helps you, greetings.

Related

PyQt5 chang Label text using Loop

code it's clean from bugs but when is running program will freezing
the program its countdown using button to start the countdown
the picture below discripe the layout
enter image description here
the probelm is im using loop to changing label text and that make program freezed
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = lambda: setting_label())
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
The way the code is written in the OP, it doesn't really get stuck or frozen. But rather, the display fails to update. You can get around this by trying to force Qt to update the GUI with app.processEvents(). Put it in your setting_label function after the setText():
self.my_label.setText(timer)
app.processEvents()
The Preferred Way
Using app.processEvents() a lot is usually discouraged. Another way to make a countdown timer is to use a QTimer. There is a little more overhead in setting up all of the signals and slots. But it can be very powerful. Here is an example
from time import sleep
import PyQt5.QtWidgets as Qtw
from PyQt5.QtCore import QTimer
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = self.start_countdown)
self.layout().addWidget(pushButton)
self.my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(self.my_label)
self.timer = QTimer() # create a new QTimer instance
self.timer.setInterval(1000) # make it fire every 1000 msec
self.t = 1200
self.timer.timeout.connect(self.setting_label) # connect the timeout signal to self.setting_label
self.show()
def start_countdown(self):
self.t = 1200
self.timer.start()
def setting_label(self):
if self.t == 0:
self.timer.stop()
print('timer stopped')
h = self.t // 3600
m = self.t // 60
s = self.t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
self.my_label.setText(timer)
self.t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
Edit: This solution works for the OP, but others have pointed out that doing this using threading can cause unexpected behavior, crashes, etc. so it's better to do this using other methods (for example: this answer)
The interface freezes because the while loop you use to change the label blocks the event loop of the app (i.e. receiving inputs). You can fix this by moving the function to a thread like this:
import threading
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
pushButton = Qtw.QPushButton(
"start",
clicked=lambda: thread.Thread(target=setting_label).start()
)
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()

Resuming a thread which was created using opencv , in pyqt5

I am trying to design a gui which is related to my computer vision project. In that, the video I want to stop the web camera feed and I want to resume it by pressing a button. I managed to stop the feed, but I cannot resume it. The camera gets turned on but it is not working. This is the code for the program.
from PyQt5 import uic
from PyQt5 import QtCore, QtWidgets, QtGui
import cv2
import sys
class opencv_feed(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = uic.loadUi('../designs/design5_flexible_opencv_window2.ui', self) #change this whenever u want... keep the ui file with you
self.resize(900,600)
self.worker1 = worker1() #creating an instance
self.worker1.start()
self.worker1.ImgUpdate.connect(self.ImageUpdateSlot)
self.but_stop.clicked.connect(self.cancel_feed)
self.but_resume.clicked.connect(self.resume_feed)
def ImageUpdateSlot(self, Image):
self.label.setPixmap(QtGui.QPixmap.fromImage(Image))
def cancel_feed(self):
self.worker1.stop()
def resume_feed(self):
self.__init__()
#self.worker1.ImgUpdate.connect(self.ImageUpdateSlot)
class worker1(QtCore.QThread):
ImgUpdate = QtCore.pyqtSignal(QtGui.QImage)
#QtCore.pyqtSlot()
def run(self): #put self in every variable to stop crashing the gui, when we interact with gui
self.ThreadActive = True
self.feed = cv2.VideoCapture(0)
while self.ThreadActive:
self.ret, self.frm = self.feed.read()
if self.ret:
self.img = cv2.cvtColor(self.frm, cv2.COLOR_BGR2RGB)
#print(img1.shape)
self.img = cv2.flip(self.img,1)
self.qtformat_conv_img = QtGui.QImage(self.img.data, self.img.shape[1], self.img.shape[0], QtGui.QImage.Format_RGB888)
#print(self.img.shape)
self.pic = self.qtformat_conv_img.scaled(self.img.shape[1],self.img.shape[0],QtCore.Qt.KeepAspectRatio) #keep this as an attribute, else when resizing the app stops
self.ImgUpdate.emit(self.pic)
def stop(self):
self.ThreadActive = False
self.feed.release()
self.quit()
#os._exit(0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
wind = opencv_feed()
wind.show()
sys.exit(app.exec_())
Can someone explain me what am I doing wrong.
Link to the UI file..
https://drive.google.com/file/d/1UP8RjQML1GzFA75eGURgWt4Y0o_Ip3sU/view?usp=sharing
You can only start a thread once. Once it finishes you need to create another thread object to actually run. I would add another flag after self.ThreadActive called something like "pause" to keep the thread alive, just without doing anything.
#QtCore.pyqtSlot()
def run(self): #put self in every variable to stop crashing the gui, when we interact with gui
self.ThreadActive = True
self.paused = False
self.feed = cv2.VideoCapture(0)
while self.ThreadActive:
if not self.paused:
self.ret, self.frm = self.feed.read()
if self.ret:
self.img = cv2.cvtColor(self.frm, cv2.COLOR_BGR2RGB)
#print(img1.shape)
self.img = cv2.flip(self.img,1)
self.qtformat_conv_img = QtGui.QImage(self.img.data,
self.img.shape[1],
self.img.shape[0],
QtGui.QImage.Format_RGB888)
#print(self.img.shape)
self.pic = self.qtformat_conv_img.scaled(self.img.shape[1],self.img.shape[0],QtCore.Qt.KeepAspectRatio) #keep this as an attribute, else when resizing the app stops
self.ImgUpdate.emit(self.pic)
this way when you want to pause the thread you can pause and unpause using that flag
Either that or you need to always create another instance of the worker. Does it work if you create the instance outside of the init ? I'm unsure what happens to the GUI if the init is called twice.
EDIT:
You'll also have to change the way you pause and start again
def cancel_feed(self):
self.worker1.paused = True
def resume_feed(self):
self.worker1.paused = False

How can I detect when one window occludes another in PyQt5?

I'm using PyQt5 to create an app with multiple main windows. I want to be able to allow the user to save and load window sizes and window positions. That's easy with, e.g., QMainWindow.saveGeometry() and QMainWindow.loadGeometry() or the corresponding .saveState() and .loadState() variants. These work great for position and size, but if the user moves or resizes one window so that it occludes another, I want to also restore this positioning. I don't mind writing my own code to save the info for each window, but I can't see any way to detect the relative Z order of windows. Am I missing it in the docs, or is this not possible?
To see what I mean, try this:
from PyQt5.QtWidgets import QApplication, QMainWindow, QPlainTextEdit
from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QCloseEvent
'''
context: Linux Mint 19.3 Tricia x86_64
Python 3.9
PyQt5 5.15.1
'''
class RememberWin(QMainWindow):
def __init__(self, win_name: str):
super(RememberWin, self).__init__()
self.win_name = win_name
self.setWindowTitle(win_name)
self.can_close = False
def restore_window(self) -> bool:
try:
settings = QSettings("PyQtExamples", "RememberWinTest")
self.restoreGeometry(settings.value(f'{self.win_name} Geometry'))
self.restoreState(settings.value(f'{self.win_name} State'))
return True
except:
return False
def closeEvent(self, event: QCloseEvent):
if not self.can_close:
event.ignore()
else:
settings = QSettings("PyQtExamples", "RememberWinTest")
settings.setValue(f'{self.win_name} Geometry', self.saveGeometry())
settings.setValue(f'{self.win_name} State', self.saveState())
QMainWindow.closeEvent(self, event)
class ControlWindow(RememberWin):
def __init__(self, win_name: str = "ControlWindow"):
super(ControlWindow, self).__init__(win_name=win_name)
self.can_close = True
self.window1 = RememberWin(win_name='WindowOne')
self.window2 = RememberWin(win_name='WindowTwo')
self.text = QPlainTextEdit(self)
s = "Try making Window1 wide enough to cover Window2.\n" \
"Then close this window (auto closes others).\n" \
"Re-run the app and you'll notice that Window2\n" \
"is not on top of Window1 which means that this\n" \
"info isn't getting saved."
self.text.setPlainText(s)
self.setCentralWidget(self.text)
if not self.restore_window():
self.setGeometry(100, 390, 512, 100)
if not self.window1.restore_window():
self.window1.setGeometry(100, 100, 512, 384)
if not self.window2.restore_window():
self.window2.setGeometry(622, 100, 512, 384)
self.window1.show()
self.window2.show()
def closeEvent(self, event: QCloseEvent):
for win in (self.window1, self.window2):
win.can_close = True
win.close()
super(ControlWindow, self).closeEvent(event)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = ControlWindow(win_name='ControlWindow (You can only close this one)')
window.show()
sys.exit(app.exec_())
The simplest way to do what you want to achieve is to keep track of the current focused widget, or, to be precise, the top level window of the last focused widget.
You can store the focused windows in the settings as a list, using a unique objectName for each window (you are already doing this, so you just need to use setObjectName()), then restore the window by showing them in the correct order as long as the object name matches.
class RememberWin(QMainWindow):
def __init__(self, win_name: str):
super(RememberWin, self).__init__()
self.win_name = win_name
self.setObjectName(win_name)
self.setWindowTitle(win_name)
self.can_close = False
# ...
class ControlWindow(RememberWin):
def __init__(self, win_name: str = "ControlWindow"):
# ...
self.settings = QSettings("PyQtExamples", "RememberWinTest")
self.zOrder = []
QApplication.instance().focusObjectChanged.connect(self.focusChanged)
windowOrder = self.settings.value('windowOrder', type='QStringList')
topLevelWindows = QApplication.topLevelWidgets()
if windowOrder:
for objName in windowOrder:
for win in topLevelWindows:
if win.objectName() == objName:
win.show()
else:
self.window1.show()
self.window2.show()
def focusChanged(self, obj):
if not obj or obj.window() == self.window():
return
if obj.window() in self.zOrder[:-1]:
self.zOrder.remove(obj.window())
self.zOrder.append(obj.window())
def closeEvent(self, event: QCloseEvent):
for win in (self.window1, self.window2):
win.can_close = True
win.close()
self.settings.setValue('windowOrder',
[w.window().objectName() for w in self.zOrder])
super(ControlWindow, self).closeEvent(event)

Can't invoke button click function() outside pyQT based GUI

I am trying to enhance the existing pyQT based GUI framework. The GUI is a multithread GUI based out of pyQT. I have taken some portion of it here to explain the problem
GUI.py - main python script that invokes function to create Tab on GUI
from TabWidgetClass import ConstructGUITabs
class mainWindow(QtGui.QMainWindow):
def __init__(self,**kwargs):
super().__init__()
self.dockList = []
#Lets build&run GUI
self.BuildRunGUI() ### initialize GUI
def BuildRunGUI(self):
### frame, status definition
### tab definition
self.mainWidget = QtGui.QTabWidget(self)
self.setCentralWidget(self.mainWidget)
self.GUIConnect = ConstructGUITabs(docklist=self.dockList, parent_widget=self.mainWidget,mutex=self.mutex)
self.show()
if __name__ == "__main__":
try:
mW = mainWindow()
mW.setGeometry(20, 30, 1920*0.9,1080*0.9)
mW.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtGui.QTabWidget.North)
#mW.show()
TabWidgetClass.py - Script to create tabs & widgets
from SerialInterface import SerialInterface
from BackQThread import BackQThread
class ConstructGUITabs():
def __init__():
super().__init__() #init!!
#Thread Variables
self.main_thread = None
self.background_thread = None
# Let's define each tab here
self.ADD_CALIBRATE_TAB()
############## Start of ADD_CONNECT_TAB##################################################
def ADD_CALIBRATE_TAB(self, tec_layout):
#Create threads here. Main to handle update in GUI & Background thread for few specific
#time consuming operation
self.main_thread = SerialInterface()
self.background_thread = BackQThread()
#Get button connect to get_value through main thread
self.get_button = QtGui.QLabel(self)
self.set_button = QtGui.QPushButton('Set')
self.set_button.clicked.connect(self.set_value)
#Set button connect to Set_value through background_thread
self.get_button = QtGui.QLabel(self)
self.get_button = QtGui.QPushButton('Get')
self.get_button.clicked.connect(self.get_new_value)
self.pwr_disp_val = QtGui.QLineEdit(self)
self.pwr_disp_val.setText('')
#add the layout
#connect signal for backgrund thread
self.background_thread.read_info_flash.connect(self.update_info_from_flash)
def set_value(self):
self.main_thread.setvalue()
def get_new_value(self):
self.background_thread.call_update_info_bg_thread(flag = 'ReadSignalInfo',
list=[var1, var2])
def update_info_from_flash(self,flashData):
self.pwr_disp_val.setText(flashData)
The 2 library modules SerialInterface & BackQThread are as below:
SerialInterface.py
import pyftdi.serialext
class LaserSerialInterface(object):
def __init__():
self.port = pyftdi.serialext.serial_for_url('COM1',
baudrate=9600,
timeout=120,
bytesize=8,
stopbits=1,
parity='N',
xonxoff=False,
rtscts=False)
def setValue(self, value):
self.port.write(value)
def readvalue(self,value):#nITLA
return (value * 100)
BackQThread.py
from SerialInterface import SerialInterface
class BackQThread(QtCore.QThread):
signal = QtCore.pyqtSignal(object)
read_info_flash = QtCore.pyqtSignal(object)
def __init__(self):
self.serial = SerialInterface()
def call_update_info_bg_thread(self,flag,**kwargs): #nITLA
self.flag = flag
self.kwargs = kwargs
def update_info_bg_thread(self, Signallist): #nITLA
flashData = []
for index in range(0, len(Singnallist)):
flashData.append(self.serial.readvalue(Signallist[index]))
self.read_info_flash.emit(flashData)
def run(self):
while True:
time.sleep(0.1)
if self.flag == None:
pass
elif self.flag == 'ReadSignalInfo':
self.update_info_bg_thread(Signallist = self.kwargs['Signallist'])
self.flag = False
All above interface works without issues.
The new requirement is to do automation on GUI. In such way that, execute GUI options without manually doing clicks. Script should get an access to ConstructGUITabs() class, to invoke the click events.
Here is sample test script which works fine, if I invoke click events.
GUI.py - script is modified to declare GUIConnect as global variables using 'import globfile'
from TabWidgetClass import ConstructGUITabs
import globfile
class mainWindow(QtGui.QMainWindow):
def __init__(self,**kwargs):
super().__init__()
self.dockList = []
#Lets build&run GUI
self.BuildRunGUI() ### initialize GUI
def BuildRunGUI(self):
### frame, status definition
### tab definition
self.mainWidget = QtGui.QTabWidget(self)
self.setCentralWidget(self.mainWidget)
globfile.GUIConnect = ConstructGUITabs(docklist=self.dockList, parent_widget=self.mainWidget,mutex=self.mutex)
........
Test_Script.py
import globfile
globfile.GUIConnect.get_button.click() #this invokes get_new_value
globfile.GUIConnect.set_button.click()# this set_value
As you see here, through *click() function we are again doing mouse click events. I dont want to do this.
So i tried
globfile.GUIConnect.set_value()# this set_value
# This set_Value() would work as we are using only single & main thread
globfile.GUIConnect.get_new_value() #this invokes get_new_value
# This function call wont' get completed. It would stuck at 'self.read_info_flash.emit(flashData)' in BackQThread.py script. Hence, background thread fails to run update function 'update_info_from_flash'.
Can anyone tell me, what is the wrong in invoking directly functions rather than click() events. Why multithread alone fails to complete.

PySide QWidget immediate update

In my application, I have a call to an external module which spawns some threads, does some stuff, then returns a value. I'm trying to get a QMessageBox to show before and a QLabel to update after this is complete, but I'm stumped. The code goes something like this (called from QObject.connect on a button):
def _process(self):
self._message_box.show()
for i in range(3):
rv = external_module_function_with_threads() // blocking function call
label = getattr(self, "label%d" % (i + 1))
label.setText(rv)
When I click the button and the function is called, the message box only shows after the loop completes. The labels only update after the loop completes as well. I tried calling label.repaint() in the loop, but all that seems to do is make the message box show up earlier (but with no text in it).
I know I'm not violating the "GUI operations from outside the main thread" rule (...right?), so is there a way to force an update?
For your message box use self._message_box.exec_(). From my understanding of your question, I think this will do what you want.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QVBoxLayout(self)
button = QPushButton("Press me")
self.label = QLabel("Run #")
map(layout.addWidget, [button, self.label])
button.pressed.connect(self.buttonPressed)
self.messageBox = QMessageBox()
def buttonPressed(self):
self.messageBox.exec_()
Thread().run(self.label)
class Thread(QThread):
def run(self, label):
for x in range(5):
self.updateLabel(label)
app.processEvents()
time.sleep(.5)
def updateLabel(self, label):
try:
number = int(label.text().split(" ")[-1])
number += 1
except ValueError:
number = 0
label.setText("Run %i" % number)
app = QApplication([])
main = Main()
main.show()
sys.exit(app.exec_())

Categories

Resources