GENKY
GENKY

Reputation: 103

selection of several elements on qtreeview and their processing

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

Answers (1)

eyllanesc
eyllanesc

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

Related Questions