Reputation: 103
I have a program with two QTreeView. When I press a button, I need to allow the user to select several elements, and when user press Escape, to transfer the selected elements to a waiting function, which will then pass to the handler function.
I've tried to use threads, gevent, and asyncio.
this function in main class, i run this function when i need get some files.
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import *
import ui
import exampleQTV
import asyncio
class PyMap(QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.select.clicked.connect(self.selectAction)
def selectAction(self):
self.getSomeFiles("Example", "Select some files, and press Escape", self.leftView)
def getSomeFiles(self, title, path, view):
# return QFileDialog.getOpenFileNames(self, title, path) ### older, and ugly variant
buttonReply = QMessageBox.information(self, "Information", "Select needed files",
QMessageBox.Ok)
loop = asyncio.get_event_loop()
tasks = [loop.create_task(view.getFiles())]
wait_tasks = asyncio.wait(tasks)
result = loop.run_until_complete(asyncio.gather(*tasks))
print (result)
# result = view.getFiles()
return result
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.rightView = exampleQTV.exampleQTV()
self.rightView.setObjectName("rightView")
self.gridLayout.addWidget(self.rightView, 1, 1, 1, 1)
self.leftView = exampleQTV.exampleQTV()
self.leftView.setObjectName("leftView")
self.gridLayout.addWidget(self.leftView, 1, 0, 1, 1)
self.select = QtWidgets.QPushButton(self.centralwidget)
self.select.setObjectName("select")
self.gridLayout.addWidget(self.select, 0, 0, 1, 2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.select.setText(_translate("MainWindow", "Select"))
def main():
app = QtWidgets.QApplication(sys.argv)
window = PyMap()
window.show()
app.exec_()
if __name__ == '__main__':
main()
this functions in QTreeView's class
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from multiprocessing.pool import ThreadPool
import asyncio
class exampleQTV(QTreeView):
def __init__(self):
QTreeView.__init__(self)
self.model = QFileSystemModel()
self.model.setRootPath("/") # i'm on linux if you not change from / to for example D:\\
self.setModel(self.model)
self.eventCalled = False
self.requestForEscape = False
self.setColumnHidden(1, True)
self.setColumnHidden(2, True)
self.setColumnHidden(3, True)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.eventCalled = false
def getFiles_thread(self):
while True:
if self.requestForEscape == True:
if self.eventCalled == False:
return self.selectedIndexes()
async def getFiles(self):
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
self.eventCalled = True
self.requestForEscape = True
pool = ThreadPool(processes=1)
self.printMessage(1)
async_result = pool.apply_async(self.getFiles_thread)
self.printMessage(2)
result = await async_result.get()
self.printMessage(3)
# ### Sorry if it not corrent, i'm just copied from doc
# tasks = [self.getFiles_thread()]
# loop = asyncio.get_event_loop()
# result = loop.run_until_complete(asyncio.gather(*tasks))
# task = [gevent.spawn(self.getFiles_thread(), 2)]
# result = gevent.joinall(task)
# result = await self.getFiles_thread()
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
return result
def printMessage(self, message):
print(message)
output: 1 2
Upvotes: 1
Views: 247
Reputation: 243897
The tasks of the GUI such as the selection of items, listening to keyboard events, etc. do not need to be executed in another thread, nor in another process.
Your way of programming is procedural but in the GUI the paradigm of Event-driven Programming is used, in the case of Qt it is implemented through the signals, slot and events. Only tasks that synchronously consume a lot of time must be executed in another thread, for example I emulated the task of copying with QtCore.QThread.sleep(...)
.
Considering the above I have implemented the logic of enabling the selection, listen to the keyPressEvent, emit a signal with the selected indexes, and call the heavy function with the data.
main.py
import sys
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
import exampleQTV
class PyMap(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.select.clicked.connect(self.selectAction)
self.leftView.selectedIndexesSignal.connect(
self.on_selectedIndexesSignal
)
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.moveToThread(thread)
@QtCore.pyqtSlot()
def selectAction(self):
buttonReply = QtWidgets.QMessageBox.information(
self,
"Information",
"Select needed files",
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
if buttonReply == QtWidgets.QMessageBox.Ok:
self.leftView.setEnableMultiSelection(True)
@QtCore.pyqtSlot(list)
def on_selectedIndexesSignal(self, indexes):
paths = []
for ix in indexes:
path = ix.data(QtWidgets.QFileSystemModel.FilePathRole)
paths.append(path)
print(paths)
wrapper = partial(self.m_worker.heavyTask, paths)
QtCore.QTimer.singleShot(0, wrapper)
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.rightView = exampleQTV.ExampleQTV()
self.rightView.setObjectName("rightView")
self.gridLayout.addWidget(self.rightView, 1, 1, 1, 1)
self.leftView = exampleQTV.ExampleQTV()
self.leftView.setObjectName("leftView")
self.gridLayout.addWidget(self.leftView, 1, 0, 1, 1)
self.select = QtWidgets.QPushButton(self.centralwidget)
self.select.setObjectName("select")
self.gridLayout.addWidget(self.select, 0, 0, 1, 2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.select.setText(_translate("MainWindow", "Select"))
class Worker(QtCore.QObject):
@QtCore.pyqtSlot(list)
def heavyTask(self, paths):
print("started")
# emulate heavy task
QtCore.QThread.sleep(5)
print(paths)
print("finished")
def main():
app = QtWidgets.QApplication(sys.argv)
window = PyMap()
window.show()
app.exec_()
if __name__ == "__main__":
main()
exampleQTV.py
from PyQt5 import QtCore, QtGui, QtWidgets
class ExampleQTV(QtWidgets.QTreeView):
selectedIndexesSignal = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super(ExampleQTV, self).__init__(parent)
self.model = QtWidgets.QFileSystemModel(self)
self.model.setRootPath(QtCore.QDir.rootPath())
self.setModel(self.model)
for i in (1, 2, 3):
self.setColumnHidden(i, True)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.expandAll()
def setEnableMultiSelection(self, enable):
self.setSelectionMode(
QtWidgets.QAbstractItemView.MultiSelection
if enable
else QtWidgets.QAbstractItemView.NoSelection
)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.selectedIndexesSignal.emit(self.selectedIndexes())
self.selectionModel().clearSelection()
self.setEnableMultiSelection(False)
super(ExampleQTV, self).keyPressEvent(event)
Upvotes: 1