Posts: 15
Threads: 4
Joined: Mar 2019
Hello there,
I am working on the small project in which I need to process data based on input numbers. Here is the code and questions below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import sys
import convert
import db_analysis
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtCore import QThread
from PyQt5 import uic
Ui_MainWindow, QtBaseClass = uic.loadUiType( 'interface.ui' )
class Window(QtBaseClass, Ui_MainWindow):
def __init__( self , parent = None ):
super (QtBaseClass, self ).__init__(parent)
self .setupUi( self )
self .threading = ThreadClass()
self .push_import.clicked.connect( self .import_data)
self .push_cancel.clicked.connect( self .close_app)
self .push_ok.clicked.connect( self .process_input)
def update_progress_bar( self , progress_val):
self .input_progress_bar.setValue(progress_val)
def import_data( self ):
db_analysis.insert_data()
def process_input( self ):
self .threading.start()
def closeEvent( self , event):
odp = QMessageBox.question(
self , 'Exit' ,
"Are you sure you want exit?" ,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if odp = = QMessageBox.Yes:
event.accept()
else :
event.ignore()
def close_app( self ):
odp = QMessageBox.question(
self , 'Exit' ,
"Are you sure you want exit?" ,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if odp = = QMessageBox.Yes:
sys.exit()
class ThreadClass(QThread):
def __init__( self , parent = None ):
super (ThreadClass, self ).__init__(parent)
def run( self ):
convert.convert_main()
def main():
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ = = "__main__" :
main()
|
As you see I am using threading in PyQt to be able to update progress bar widget value. Few things I am struggling with:
1. How can I improve my existing code so I can have quick access to the list of widgets from interface.ui (auto-complete after self in __init__). Atm I need to write it down or copy from interface.py
2. After input value in input_line (QLineEdit) I would like to pass self.input_line.text() into convert.convert_main() inside run() function of ThreadClass.
3. After push_import click Widow Explorer dialog window pop up, when I choose correct file code is finish properly, but when I want to close the dialog window before choosing file whole program close instead to going back to main GUI. How I should handle with this event?
Posts: 164
Threads: 22
Joined: Feb 2017
(Mar-02-2019, 06:02 PM)mrdominikku Wrote: 1. How can I improve my existing code so I can have quick access to the list of widgets from interface.ui (auto-complete after self in __init__). Atm I need to write it down or copy from interface.py
You can import the ui file with uic, and place it inside an object (self.ui) to access it later:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets , uic
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__)) + "/"
class Main(QtWidgets.QMainWindow):
def __init__( self ):
super ().__init__()
self .ui = uic.loadUi(LOCAL_DIR + "gui_main.ui" , self )
self .show()
if __name__ = = '__main__' :
app = QtWidgets.QApplication([])
gui = Main()
sys.exit(app.exec_())
|
(Mar-02-2019, 06:02 PM)mrdominikku Wrote: 2. After input value in input_line (QLineEdit) I would like to pass self.input_line.text() into convert.convert_main() inside run() function of ThreadClass.
See this example on how to do threading in pyqt5. The correct way is to use the moveToThread method and to communicate with the thread by using the signals and slots mechanism.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import sys
import time
from PyQt5 import QtWidgets, QtCore
class WorkerThread(QtCore.QObject):
signalExample = QtCore.pyqtSignal( str , int )
def __init__( self ):
super ().__init__()
@QtCore .pyqtSlot()
def run( self ):
while True :
self .signalExample.emit( "leet" , 1337 )
time.sleep( 5 )
class Main(QtWidgets.QMainWindow):
def __init__( self ):
super ().__init__()
self .worker = WorkerThread()
self .workerThread = QtCore.QThread()
self .workerThread.started.connect( self .worker.run)
self .worker.signalExample.connect( self .signalExample)
self .worker.moveToThread( self .workerThread)
self .workerThread.start()
def signalExample( self , text, number):
print (text)
print (number)
if __name__ = = '__main__' :
app = QtWidgets.QApplication([])
gui = Main()
sys.exit(app.exec_())
|
(Mar-02-2019, 06:02 PM)mrdominikku Wrote: 3. After push_import click Widow Explorer dialog window pop up, when I choose correct file code is finish properly, but when I want to close the dialog window before choosing file whole program close instead to going back to main GUI. How I should handle with this event?
To avoid this you must add
1 |
app.setQuitOnLastWindowClosed( False )
|
Posts: 15
Threads: 4
Joined: Mar 2019
@ Alfalfa, thanks for response, 1st and 3rd applied, but still can't make 2nd work. It doesn't crash program or anything, but either emit signal or call class procedure don't update progress bar :( I added part of convert module below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
class Window(QtBaseClass, Ui_MainWindow):
def __init__( self , parent = None ):
super (QtBaseClass, self ).__init__(parent)
self .setupUi( self )
self .threading = ThreadClass( self )
self .threading.signal.connect( self .update_progress_bar)
self .push_import.clicked.connect( self .import_data)
self .push_cancel.clicked.connect( self .close_app)
self .push_ok.clicked.connect( self .process_input)
def update_progress_bar( self , progress_val):
self .input_progress_bar.setValue(progress_val)
def import_data( self ):
db_analysis.insert_data()
def process_input( self ):
self .threading.start()
def closeEvent( self , event):
odp = QMessageBox.question(
self , 'Exit' ,
"Are you sure you want exit?" ,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if odp = = QMessageBox.Yes:
event.accept()
else :
event.ignore()
def close_app( self ):
odp = QMessageBox.question(
self , 'Exit' ,
"Are you sure you want exit?" ,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if odp = = QMessageBox.Yes:
sys.exit()
class ThreadClass(QThread):
signal = QtCore.pyqtSignal( int )
def __init__( self , window, parent = None ):
super (ThreadClass, self ).__init__(parent)
self .window = window
def run( self ):
convert.convert_main( self , self .window.input_line.text())
def convert_main(worker, input_array):
worker = worker.signal
window = ui_main.Window()
my_list = list ( map ( int , input_array.split( " " )))
for i to my_list:
worker.emit(my_list.index(a) / len (my_list))
window.update_progress_bar(my_list.index(a) / len (my_list))
|
Posts: 164
Threads: 22
Joined: Feb 2017
(Mar-04-2019, 05:50 AM)mrdominikku Wrote: @Alfalfa, thanks for response, 1st and 3rd applied, but still can't make 2nd work. It doesn't crash program or anything, but either emit signal or call class procedure don't update progress bar :( I added part of convert module below.
Your usage of threading, slot and signals is incorrect. Look again at the example I gave you above. If you have trouble understanding how it work, I propose you start with this very short threading example and grow your program around it.
Posts: 15
Threads: 4
Joined: Mar 2019
(Mar-04-2019, 11:28 AM)Alfalfa Wrote: (Mar-04-2019, 05:50 AM)mrdominikku Wrote: @Alfalfa, thanks for response, 1st and 3rd applied, but still can't make 2nd work. It doesn't crash program or anything, but either emit signal or call class procedure don't update progress bar :( I added part of convert module below.
Your usage of threading, slot and signals is incorrect. Look again at the example I gave you above. If you have trouble understanding how it work, I propose you start with this very short threading example and grow your program around it.
Been searching for few solutions and still don't get it. In PyQt5 documentation is described how to use signals and slot, but even after using them I can't call emit signal from another module, but I can emit from same module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Window(QtBaseClass, Ui_MainWindow):
def __init__( self , parent = None ):
super (QtBaseClass, self ).__init__(parent)
self .setupUi( self )
self .worker = ThreadClass( self )
self .worker.signal.connect( self .update_progress_bar)
def update_progress_bar( self , progress_val):
self .input_progress_bar.setValue(progress_val)
def process_input( self ):
self .worker.start()
class ThreadClass(QThread):
signal = QtCore.pyqtSignal( int )
def __init__( self , window, parent = None ):
super (ThreadClass, self ).__init__(parent)
self .window = window
def run( self ):
self .signal.emit( 20 )
convert.convert_main( self .signal, self .window.input_line.text())
def convert_main(s_thread, input_array):
s_thread.emit( 20 )
|
Posts: 164
Threads: 22
Joined: Feb 2017
Do not subclass QThread. Instead you must subclass a QObject and place it into a QThread using the moveToThread method.
But I don't get what you are trying to do; signals are meant to be emitted from within the thread, so why would you want to call it from outside? Instead you can simply connect your second thread to the same slot and emit from there.
Posts: 15
Threads: 4
Joined: Mar 2019
Mar-10-2019, 03:56 PM
(This post was last modified: Mar-10-2019, 03:56 PM by mrdominikku.)
(Mar-10-2019, 03:12 PM)Alfalfa Wrote: But I don't get what you are trying to do; signals are meant to be emitted from within the thread, so why would you want to call it from outside? Instead you can simply connect your second thread to the same slot and emit from there.
I prefer functional programming in python so I don't like to store all functions in one module. So all GUI related functions should be kept in ui_main.py and all other in convert.py. Convert_main function based on given input (list of 10-20 numbers - self.window.input_line.text()) perform loop for each number from the list. At the end of each loop I want to update progress bar so user will know the progress of the main code. ThreadClass.start() starts thread, so as far def run() is running convert.convert_main() is within thread (at least I understand it like that) so signal.emit() in convert_main should be emitted to main GUI thread.
If I don't find better solution which meets my needs I will need to make less slick coding and put whole convert_main() code withing run() :(
Posts: 164
Threads: 22
Joined: Feb 2017
Mar-10-2019, 06:35 PM
(This post was last modified: Mar-10-2019, 06:35 PM by Alfalfa.)
(Mar-10-2019, 03:56 PM)mrdominikku Wrote: I prefer functional programming in python so I don't like to store all functions in one module. So all GUI related functions should be kept in ui_main.py and all other in convert.py.
From your main class, you can init instances of other class and connect their signals and slots from there. This way your main class is kept to a minimum and handle all the connections between the widgets and the logic. For instance;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import sys
import time
import random
from PyQt5 import QtWidgets, QtCore
class WorkerThread(QtCore.QObject):
signal = QtCore.pyqtSignal( int )
def __init__( self ):
super ().__init__()
@QtCore .pyqtSlot()
def run( self ):
while True :
number = random.randint( 1 , 1000 )
self .signal.emit(number)
time.sleep( 1 )
class WorkerLabel(QtWidgets.QLabel):
def __init__( self , parent):
super ().__init__()
@QtCore .pyqtSlot( int )
def slot( self , i):
self .setText( str (i))
class UserInterface(QtWidgets.QWidget):
def __init__( self , parent):
super ().__init__()
self .label = WorkerLabel( self )
self .layout = QtWidgets.QVBoxLayout()
self .layout.addWidget( self .label)
self .setLayout( self .layout)
class Main(QtWidgets.QMainWindow):
def __init__( self ):
super ().__init__()
self .ui = UserInterface( self )
self .setCentralWidget( self .ui)
self .worker = WorkerThread()
self .workerThread = QtCore.QThread()
self .workerThread.started.connect( self .worker.run)
self .worker.moveToThread( self .workerThread)
self .worker.signal.connect( self .ui.label.slot)
self .workerThread.start()
self .show()
if __name__ = = '__main__' :
app = QtWidgets.QApplication([])
gui = Main()
sys.exit(app.exec_())
|
Here I made a subclass for the label, but if all it does is having one slot, you might put the method into UserInterface and use self.label.setText() instead.
Posts: 15
Threads: 4
Joined: Mar 2019
Mar-10-2019, 09:14 PM
(This post was last modified: Mar-10-2019, 09:14 PM by mrdominikku.)
Found solution..
The signal was emitted but the value that is emitted is always almost zero as:
0 < worker.emit(my_list.index(a)/len(my_list)) < 1, where len(my_list) = 11, so 1/11 = 0.09
Added worker.emit(my_list.index(a)/len(my_list)*100)
SORRY if I didn't mentioned that! Thread can be closed :)
|