Salut à tous,

J'ai écrit un petit code, adapter de quelques exemples en c++.

Le problème initial était que pour des grosses quantités de donnée, la rechercher dans mon QTableview est atrocement lente.
Surtout que je souhaite permettre aux utilisateurs de chercher sur toute les colonnes plutôt que l'obliger à en sélectionner une sur laquelle il veux faire sa recherche.

J'ai donc fait quelques recherches sur internet et j'ai sélectionner quelques approches qui me semblent pertinentes. J'ai rien trouvé en python du coup j'ai converti quelques samples en PyQt.

Si vous avez des pistes d'améliorations, des remarques... je suis preneur, si vous avez déjà eu ce genre de problème et que vous avez utilisé une autre solution, ça m’intéresse aussi !

Merci d'avance !

AbstractProxyChainModel
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
 
from enum import Enum
from PyQt5.QtCore import QIdentityProxyModel
 
# QStack seem to not be available in pyqt
class QStack:
 
    def __init__(self):
        self.items = []
 
    def isEmpty(self):
        return self.items == []
 
    def push(self, item):
        self.items.append(item)
 
    def pop(self):
        return self.items.pop()
 
    def peek(self):
        return self.items[len(self.items)-1]
 
    def size(self):
        return len(self.items)
 
    def clear(self):
        self.items = []
 
    def front(self):
        return self.items[0]
 
    def top(self):
        return self.items[-1]
 
 
class RelationType(Enum):
 
    Identical = 1
    Narrower = 2
    Different = 3
 
 
class AbstractProxyChainModel(QIdentityProxyModel):
    """
    An abstract class representing a proxy chain. It does not know anything
    about the proxies, it just manages the chain using the "can_be_chained"
    function. It pops the stack automatically until it finds a suitable model,
    or it reaches the original source.
 
    It takes ownership over the proxies and deletes them, but not over the
    proxies, but not over the original source.
    """
 
    def __init__(self, parent=None):
        super(AbstractProxyChainModel, self).__init__(parent)
 
        # The stack of chained models.
        self.chain = QStack()  # QStack<QAbstractProxyModel>
 
        # QIdentityProxyModel's source model will point to the last filter model,
        # so we need a new variable to hold the **actual** source.
        self.real_source_model = None
 
    def clear_chain(self):
        """Removes all the proxy models and sets the original data
           as our source."""
        super().setSourceModel(self.real_source_model)
        self.chain.clear()
 
    def add_to_chain(self, model):
        """Add a new proxy model to the chain."""
         # We take ownership over the proxy model.
        model.setParent(self)
        # Pop the stack until we find a suitable proxy to use as a source for the
        # new one. If no suitable proxy, we just chain it to the original source
        # directly.
        # We initialize it in case the chain is empty.
        relation = RelationType.Different
        while not self.chain.isEmpty():
            relation = self.relationTo(model, self.chain.top())
            if RelationType.Different is not relation:
                break
            self.chain.pop()
 
        if RelationType.Identical is relation:
            # New model is redundant, discard it and just reuse the top model.
            del model
        else:
            src_model = self.real_source_model if self.chain.isEmpty() else self.chain.top()
            model.setSourceModel(src_model)
            self.chain.push(model)
        if super().sourceModel() is not self.chain.top():
            super().setSourceModel(self.chain.top())
 
    def setSourceModel(self, sourceModel):
        """ Reimplemented because we need to cleanup the stack of filters and set
            realSourceModel.
        """
        self.real_source_model = sourceModel
 
        # QSortFilterProxyModel doesn't discard its filter on source model change, instead
        # it just reapplies it. We want to mimic that behavior. Filters have a source changed
        # event/signal, so it's enough to set the bottom-most filter in tha stack to the new
        # source model and the change will propagade all the way through to the top-most
        # filter.
        if not self.chain.isEmpty():
            self.chain.front().setSourceModel(self.real_source_model)
        else:
            super().setSourceModel(self.real_source_model)
NarrowableFilterProxyModel
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
 
from abstract_proxy_chain_model import AbstractProxyChainModel, RelationType
from PyQt5.QtCore import QSortFilterProxyModel
 
 
class NarrowableFilterProxyModel(AbstractProxyChainModel):
 
    def __init__(self, parent=None):
        super(NarrowableFilterProxyModel, self).__init__(parent)
 
    def setFilterFixedString(self, s):
        if not s:
            self.clear_chain()
        else:
            model = QSortFilterProxyModel(self)
            model.setFilterFixedString(s)
            self.add_to_chain(model)
 
    def relationTo(self, model, sourceModel):
        pattern = model.filterRegExp().pattern()
        source_pattern = sourceModel.filterRegExp().pattern()
 
        if pattern == source_pattern:
            return RelationType.Identical
        elif source_pattern in pattern:
            return RelationType.Narrower
        else:
            return RelationType.Different
Test :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
 
import sys
import PyQt5
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import qInstallMessageHandler
 
from UI_mainwindow import Ui_MainWindow
from narrowable_filter_proxy_model import NarrowableFilterProxyModel
 
 
def qtMsgHandler(msg_type, msg_log_context, msg_string):
    print('file:', msg_log_context.file)
    print('function:', msg_log_context.function)
    print('line:', msg_log_context.line)
    print('  txt:', msg_string)
    msg = "File : {0}, Function : {1}, Line : {2}, Message : {3}".format(msg_log_context.file, msg_log_context.function,
                                                                         msg_log_context.line, msg_string)
    print(msg)
 
 
qInstallMessageHandler(qtMsgHandler)
 
 
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
 
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        str_list = list()
 
        for i in range(300000):
            str_list.append(str(i))
 
        self.model = QtCore.QStringListModel(self)
        self.model.setStringList(str_list)
 
        self._filter = NarrowableFilterProxyModel(self)
        self.listView.setModel(self._filter)
 
        self._filter.setSourceModel(self.model)
 
        self.lineEdit.textChanged.connect(self.on_text_changed)
 
    def on_text_changed(self, text):
        _filter = self.listView.model()
        _filter.setFilterFixedString(text)
 
 
 
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
 
    # Create class
    mw = MainWindow()
    mw.show()
    err = app.exec_()
    print("return code : ", err)
    sys.exit(0)
code UI mainwindow
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
 
 
from PyQt5 import QtCore, QtGui, QtWidgets
 
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(400, 300)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget)
        self.verticalLayout.setContentsMargins(11, 11, 11, 11)
        self.verticalLayout.setSpacing(6)
        self.verticalLayout.setObjectName("verticalLayout")
        self.lineEdit = QtWidgets.QLineEdit(self.centralWidget)
        self.lineEdit.setObjectName("lineEdit")
        self.verticalLayout.addWidget(self.lineEdit)
        self.listView = QtWidgets.QListView(self.centralWidget)
        self.listView.setObjectName("listView")
        self.verticalLayout.addWidget(self.listView)
        MainWindow.setCentralWidget(self.centralWidget)
        self.menuBar = QtWidgets.QMenuBar(MainWindow)
        self.menuBar.setGeometry(QtCore.QRect(0, 0, 400, 25))
        self.menuBar.setObjectName("menuBar")
        MainWindow.setMenuBar(self.menuBar)
        self.mainToolBar = QtWidgets.QToolBar(MainWindow)
        self.mainToolBar.setObjectName("mainToolBar")
        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
        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"))