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
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
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)
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 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
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
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 : 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"))
Partager