PDA

View Full Version : unfreezing GUI



singhai.nish
23rd January 2015, 16:21
Is it true that to unfreeze a GUI one should use qthread whlie working in a pyqt framework ?

If that is true then when a user clicks "OK" control should go to another thread passing a def from GUI. ( in the script attached )

and ok button change to "processing"

Googling for qthreads has helped build code to this level. However, my code does not work as required.

I have tried two ways. One is while the GUI freezes to display "processing " on ok button so that user knows something is going on.

Best would be to pass the task that takes time to another thread. None work .

Kindly suggest what I am missing.





#!/usr/bin/env python
import sys
import os, time
from PyQt4 import QtCore, QtGui
from backUpUi import Ui_MainWindow

class setText01Thread(QtCore.QThread):

def __init__(self, mw):
super(setText01Thread, self).__init__(mw)

def run(self):
self.emit(QtCore.SIGNAL('setStatus'))

class backUpMain(QtGui.QMainWindow):

"""UI functionality to pack stuff for client"""
def __init__(self,parent=None):
super(backUpMain, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)

self.connect(self.ui.okButton, QtCore.SIGNAL("clicked()"), self._handlebackUp)
self.ui.cancelButton.released.connect(self.close)
self.statusTh = setText01Thread(self)
self.connect(self.statusTh, QtCore.SIGNAL('setStatus'), self.updateText_01,QtCore.Qt.QueuedConnection)

def updateText_01(self):
self.ui.okButton.setText("Processing Started")

def _handlebackUp(self):

self.statusTh.start()
self.ui.logEdit.clear()
.
.
outPutFileName = self.ui.outEdit.text()
inputData = self.getInputContent(self.ui.filePathEdit.text())

# indPath = self.getIndPathFromShotgun(projectId,"sg_ind_path_to_frames")
# the above line takes a long time to execute. Can this def be executed in another
# thread
.
.
.

def main():
app = QtGui.QApplication(sys.argv)
dialog = backUpMain()
dialog.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()

jefftee
23rd January 2015, 20:25
I am not a python person, so take this feedback with a grain of salt... :)

You should review the QThread best practices and use the "moveToThread" approach as documented/explained here: http://qt-project.org/doc/note_revisions/5/8/view.

Also, the default QThread::run method creates an event loop and the function you defined does not do that, so unless there's some python magic under the covers, you need to either call the base class run or execute QThread::exec() on your own to create/enter an event loop.

If you adopt the "moveToThread" approach, you don't need to subclass QThread. Just create an instance of QThread, move your worker object to the thread, and QThread::run() will enter the event loop, etc.

Good luck.

anda_skoa
24th January 2015, 10:04
You need to put the method that takes long to execute into the thread, i.e. you call getInputContent() in the thread's run() method.

So your thread class needs to get the value that getInputContent() needs as its argument, e.g. as a constructor argument.

Then, in _handlebackUp(), you get the value from the UI, create the thread and pass it the value.
Then you connect the thread (e.g. its finished() signal) and start it.

Once the thread has indicated it is finished, you retrieve the data from the thread object.

Cheers,
_

singhai.nish
27th January 2015, 14:38
I have tried, to put in code, what you have suggested.
The window still freezes.

It is getIndPathFromShotgun that takes long not getInputContent

What steps should one take in code to reach the goal ?

Can you describe what you said codically.



#!/usr/bin/env python

import re
import sys
import os, time

from PyQt4 import QtCore, QtGui
from backUpUi import Ui_MainWindow
from shotgun_api3 import Shotgun


def getShotgunObject():
sgServer = "https://abcdsdf.dsfasdfsadfsdf.com"
sgScriptName = "dfsadfsadfsadfdsafd"
sgScriptKey = "dfsad43lkj453-0-if4334lkjlkjx"
sg = Shotgun(sgServer, sgScriptName, sgScriptKey)
return sg


class callerThread(QtCore.QThread):

def __init__(self, func, parent=None, *args, **kwargs):
super(callerThread, self).__init__(parent)
self._func = func
self._args = args
self._kwargs = kwargs

def run(self):
self._func(*self._args, **self._kwargs)


class Monitor(QtCore.QObject):

updateText = QtCore.pyqtSignal()

def __init__(self, parent=None):
super(Monitor, self).__init__(parent)
self.shotgun = getShotgunObject()


def update_list(self):
t_monitor = callerThread(self.monitor_vector, parent=self)
t_monitor.daemon = True
t_monitor.start()



def monitor_vector(self):
print "... emitting "
self.updateText.emit()



class backUpMain(QtGui.QMainWindow):

setText = QtCore.pyqtSignal()

"""UI functionality to pack stuff for client"""
def __init__(self,parent=None):
super(backUpMain, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)

self.jobName = os.getenv('JOB')
self.shotgun = getShotgunObject()
self.dept = {}


self.connect(self.ui.okButton, QtCore.SIGNAL("clicked()"), self.startThread)
self.ui.cancelButton.released.connect(sys.exit)

self.ui.selectInFile.clicked.connect(self.selectIn putFile)
self.ui.selectOutFile.clicked.connect(self.selectO utputFile)
self.ui.confirmButton.clicked.connect(self.validat eAll)
self.ui.clearCheckBox.clicked.connect(self.clearBo x)
self.ui.clearAllButton.clicked.connect(self.clearA ll)


def getIndPathFromShotgun(self):

projectId = self.getProjectId(self.jobName)
filters = [['project','is',{'type':'Project','id':projectId}]]
field1 = "sg_ind_path_to_frames"
fields = []
fields.append(field1)
indPath = self.shotgun.find("Version", filters, fields)
print " >>>> inside get ind path from shotgun "
return indPath


def _handlebackUp(self):

self.ui.logEdit.clear()

outPutFileName = self.ui.outEdit.text()
inputData = self.getInputContent(self.ui.filePathEdit.text())

print " #### projectId in shotgun ",projectId

self.startThread()

print " #### post start thread "



def getProjectId(self, job):
result = self.shotgun.find_one("Project", [['name', 'is', job]])
prJid = int(result['id'])
return prJid


def startThread(self):

self.monitor = Monitor()
print "in start thread"

self.monitor.updateText.connect(self.getIndPathFro mShotgun)
self.monitor.update_list()

print "post start thread"


def getInputContent(self, data):
'''
check if data is file or group of shotnames or blank
'''
# print " data ",data

reg = r'[^a-zA-Z0-9]'
shotList = []
if os.path.isfile(data):
with open(data) as inf:
for line in inf:
shotList.append(line.rstrip("\n"))
# print "shotList ", shotList
elif data == "":
# make backup of all shots
shotList.append(None)
else:
sss = re.findall(reg,data)
if sss == []:
message = "No seperator found. Try again. | or , or any non alpha numeric"
self.ui.logEdit.append(message)
return -1
else:
tmp = data.split(sss[0])
for each in tmp:
shotList.append(each)
return shotList

.
.
.
.
.




def main():
app = QtGui.QApplication(sys.argv)
dialog = backUpMain()
dialog.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()

anda_skoa
27th January 2015, 15:38
It is getIndPathFromShotgun that takes long not getInputContent

The principle is obviously the same.

- Put what ever data you need into the thread object
- Run whatever operation on that data in run()
- Retrieve the results when the thread's finished() signal has indicated that it is done.

Cheers,
_

singhai.nish
28th January 2015, 16:54
Thank you. With help from this link ( http://www.matteomattei.com/pyside-signals-and-slots-with-qthread-example/ ) and your suggestion, I can now see the window unfreeze while getIndPathFromShotgun was running ( important achievment ). Next step is to bigger (do more computation) .

singhai.nish
29th January 2015, 11:57
So far, I've been able to pass data from sub thread to the main thread using signals. How do I pass a value from main UI to a thread.
Or should I put this as a new question.

anda_skoa
29th January 2015, 13:23
If you can pass the data before you call start(), you can pass it through the thread object's constructor.

If you need to pass it later, you can do that through a setter method, from which you write members of the thread object.
Make sure to properly protect access using QMutex and friends.

Cheers,
_

singhai.nish
30th January 2015, 14:11
1. Tried first suggestion (refer : http://stackoverflow.com/questions/25413743/how-can-i-pass-arguments-to-qthread-worker-class , kitsune meyoko)

2. In current context, I'll have to choose latter suggestion, as it is user input that i need to pass to thread and thread is being instanced in main thread __init__ func


Question is how to use setter method to pass variables to thread ?

Tried googling, found this but not sure if this will help. Kindly suggest.

https://wiki.python.org/moin/PyQt/Binding%20widget%20properties%20to%20Python%20vari ables

anda_skoa
30th January 2015, 14:51
2. In current context, I'll have to choose latter suggestion, as it is user input that i need to pass to thread and thread is being instanced in main thread __init__ func

For which reason do you instantiate the thread there and not when you need it?
Is the thread running at all times and occasionally getting data?

Cheers,
_

singhai.nish
2nd February 2015, 11:09
Thank you anda_skoa. Your counter questions have helped solve qthread as a puzzle for me. Thank you very much for your support.

I've put thread instantiation where required not in __init__. It worked.