QtCore.QMetaObject.connectSlotsByName() - штука, конечно, замечательная,
но несколько неоднозначная на мой взгляд. Надо поразмыслить потом на досуге.
Обычно-то, как действуем без использования QtCore.QMetaObject.connectSlotsByName()?
Все сигналы в своей прикладухе цепляем к слотам ручками, примерно как в этом примере:
Вариант A:
| Python | 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
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWidgets import QAbstractItemView
from PyQt5.QtWidgets import QTableView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt, QMetaObject
from PyQt5.QtCore import QItemSelectionModel
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(300, 200, 500, 400)
w_view = QTableView(self)
w_model = QStandardItemModel(self)
w_model.setHorizontalHeaderLabels(['Деталь', 'Длина', 'Поставщик'])
w_view.setModel(w_model)
# в списке детей w_view появляется экземпляр QItemSelectionModel
w_view.setSortingEnabled(True)
w_view.setSelectionBehavior(QAbstractItemView.SelectRows)
w_view.horizontalHeader().setStretchLastSection(True)
w_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
w_view.setSelectionMode(QAbstractItemView.SingleSelection)
w_view.activated.connect(self.on_tableview_activated)
w_view.clicked.connect(self.on_tableview_clicked)
w_view.doubleClicked.connect(self.on_tableview_doubleClicked)
w_view.entered.connect(self.on_tableview_entered)
w_view.pressed.connect(self.on_tableview_pressed)
w_model.rowsInserted.connect(self.on_itemmodel_rowsInserted)
w_select = w_view.findChild(QItemSelectionModel,
'', Qt.FindDirectChildrenOnly)
if w_select is not None:
w_select.currentChanged.connect(self.on_selectmodel_currentChanged)
w_select.selectionChanged.connect(self.on_selectmodel_selectionChanged)
rows = [['Стойка', 200, 'Фабрика'],
['Штанга', 10, 'Завод'],
['Перекладина', 5, 'Мастерская']
]
for row in rows:
w_model.appendRow([QStandardItem(row[0]),
QStandardItem(str(row[1])),
QStandardItem(row[2])])
w_view.selectRow(0)
self.setCentralWidget(w_view)
def on_tableview_activated(self, idx):
print('activated({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_clicked(self, idx):
print('clicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_doubleClicked(self, idx):
print('doubleClicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_entered(self, idx):
print('entered({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_pressed(self, idx):
print('pressed({!s},{!s})'.format(idx.row(), idx.column()))
def on_itemmodel_rowsInserted(self, p, first, last):
print('rowsInserted({!s},{!s})'.format(first, last))
def on_selectmodel_currentChanged(self, c_idx, p_idx):
print('currentChanged({!s},{!s})<-({!s},{!s})'.format(
c_idx.row(), c_idx.column(), p_idx.row(), p_idx.column()))
def on_selectmodel_selectionChanged(self, s, d):
print('selectionChanged({!s})'.format(s.indexes()[0].row()))
if __name__ == '__main__':
app = QApplication(sys.argv)
mwin = MainWindow()
mwin.show()
app.exec_()
sys.exit() |
|
Это всё, конечно, нудно и многословно, чревато ошибками и так далее.
А QtCore.QMetaObject.connectSlotsByName() предлагает снять эту нудную обязанность
с программиста, отяготив его всего лишь необходимостью присвоения объектам уникальных имён
и соблюдения правил именования слотов, принимающих сигналы на обработку, примерно так:
Вариант B:
| Python | 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
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWidgets import QAbstractItemView
from PyQt5.QtWidgets import QTableView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt, QMetaObject
from PyQt5.QtCore import QItemSelectionModel
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setObjectName('mainwindow')
self.setGeometry(300, 200, 500, 400)
w_view = QTableView(self)
w_view.setObjectName('tableview')
w_model = QStandardItemModel(self)
w_model.setObjectName('itemmodel')
w_model.setHorizontalHeaderLabels(['Деталь', 'Длина', 'Поставщик'])
w_view.setModel(w_model)
# в списке детей w_view появляется экземпляр QItemSelectionModel
w_select = w_view.findChild(QItemSelectionModel,
'', Qt.FindDirectChildrenOnly)
if w_select is not None:
w_select.setObjectName('selectmodel')
w_view.setSortingEnabled(True)
w_view.setSelectionBehavior(QAbstractItemView.SelectRows)
w_view.horizontalHeader().setStretchLastSection(True)
w_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
w_view.setSelectionMode(QAbstractItemView.SingleSelection)
QMetaObject.connectSlotsByName(self)
rows = [['Стойка', 200, 'Фабрика'],
['Штанга', 10, 'Завод'],
['Перекладина', 5, 'Мастерская']
]
for row in rows:
w_model.appendRow([QStandardItem(row[0]),
QStandardItem(str(row[1])),
QStandardItem(row[2])])
w_view.selectRow(0)
self.setCentralWidget(w_view)
def on_tableview_activated(self, idx):
print('activated({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_clicked(self, idx):
print('clicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_doubleClicked(self, idx):
print('doubleClicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_entered(self, idx):
print('entered({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_pressed(self, idx):
print('pressed({!s},{!s})'.format(idx.row(), idx.column()))
def on_itemmodel_rowsInserted(self, p, first, last):
print('rowsInserted({!s},{!s})'.format(first, last))
def on_selectmodel_currentChanged(self, c_idx, p_idx):
print('currentChanged({!s},{!s})<-({!s},{!s})'.format(
c_idx.row(), c_idx.column(), p_idx.row(), p_idx.column()))
def on_selectmodel_selectionChanged(self, s, d):
print('selectionChanged({!s})'.format(s.indexes()[0].row()))
if __name__ == '__main__':
app = QApplication(sys.argv)
mwin = MainWindow()
mwin.show()
app.exec_()
sys.exit() |
|
Объём текста, конечно, сокращается, но, на мой взгляд, несколько снижается наглядность.
Собственно говоря, на качество не влияет - дело привычки.
А для пользователей Qt Designer это должно, наверное, выглядеть примерно так:
Вариант C:
| Python | 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
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWidgets import QAbstractItemView
from PyQt5.QtWidgets import QTableView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt, QMetaObject
from PyQt5.QtCore import QItemSelectionModel
class MainWindow(QMainWindow):
def on_tableview_activated(self, idx):
print('activated({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_clicked(self, idx):
print('clicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_doubleClicked(self, idx):
print('doubleClicked({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_entered(self, idx):
print('entered({!s},{!s})'.format(idx.row(), idx.column()))
def on_tableview_pressed(self, idx):
print('pressed({!s},{!s})'.format(idx.row(), idx.column()))
def on_itemmodel_rowsInserted(self, p, first, last):
print('rowsInserted({!s},{!s})'.format(first, last))
def on_selectmodel_currentChanged(self, c_idx, p_idx):
print('currentChanged({!s},{!s})<-({!s},{!s})'.format(
c_idx.row(), c_idx.column(), p_idx.row(), p_idx.column()))
def on_selectmodel_selectionChanged(self, s, d):
print('selectionChanged({!s})'.format(s.indexes()[0].row()))
class Ui_Mwin(object):
def setupUi(self, mwin):
mwin.setObjectName('mainwindow')
mwin.setGeometry(300, 200, 500, 400)
w_view = QTableView(mwin)
w_view.setObjectName('tableview')
w_model = QStandardItemModel(mwin)
w_model.setObjectName('itemmodel')
w_model.setHorizontalHeaderLabels(['Деталь', 'Длина', 'Поставщик'])
w_view.setModel(w_model)
# в списке детей w_view появляется экземпляр QItemSelectionModel
w_select = w_view.findChild(QItemSelectionModel,
'', Qt.FindDirectChildrenOnly)
if w_select is not None:
w_select.setObjectName('selectmodel')
w_view.setSortingEnabled(True)
w_view.setSelectionBehavior(QAbstractItemView.SelectRows)
w_view.horizontalHeader().setStretchLastSection(True)
w_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
w_view.setSelectionMode(QAbstractItemView.SingleSelection)
QMetaObject.connectSlotsByName(mwin)
rows = [['Стойка', 200, 'Фабрика'],
['Штанга', 10, 'Завод'],
['Перекладина', 5, 'Мастерская']
]
for row in rows:
w_model.appendRow([QStandardItem(row[0]),
QStandardItem(str(row[1])),
QStandardItem(row[2])])
w_view.selectRow(0)
mwin.setCentralWidget(w_view)
if __name__ == '__main__':
app = QApplication(sys.argv)
mwin = MainWindow()
ui = Ui_Mwin()
ui.setupUi(mwin)
mwin.show()
app.exec_()
sys.exit() |
|
Впрочем, не настаиваю, инструментом не владею, пример сляпал ручками, хотя реалии вроде бы отражает.
Дополнение от 10.01.2022
Продолжая начатое, надо отметить, что созданные нами слоты не попадают в метаданные объекта,
хотя умный QMetaObject.connectSlotsByName() их всё-таки находит и цепляет к ним сигналы.
В этом легко убедиться, добавив сразу перед QMetaObject.connectSlotsByName() такой код:
| Python | 1
2
3
4
5
| meta = self.metaObject()
print(meta.methodCount())
for i in range(meta.methodCount()):
print(meta.method(i).name().data().decode('utf-8'))
print() |
|
Протокольчик прилагается:.
| Code | 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
| user@linux:~> python3 by_name_probe_b.py
37
destroyed
destroyed
objectNameChanged
deleteLater
_q_reregisterTimers
windowTitleChanged
windowIconChanged
windowIconTextChanged
customContextMenuRequested
setEnabled
setDisabled
setWindowModified
setWindowTitle
setStyleSheet
setFocus
update
repaint
setVisible
setHidden
show
hide
showMinimized
showMaximized
showFullScreen
showNormal
close
raise
lower
updateMicroFocus
_q_showIfNotHidden
grab
grab
iconSizeChanged
toolButtonStyleChanged
setAnimated
setDockNestingEnabled
setUnifiedTitleAndToolBarOnMac
rowsInserted(0,0)
rowsInserted(1,1)
rowsInserted(2,2)
currentChanged(0,0)<-(-1,-1)
selectionChanged(0) |
|
Всё работает, сигналы приходят, а наших слотов в метаданных нет.
Исправить это упущение можно, украсив слоты декораторами:
| Python | 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
| @pyqtSlot(QModelIndex)
def on_tableview_activated(self, idx):
print('activated({!s},{!s})'.format(idx.row(), idx.column()))
@pyqtSlot(QModelIndex)
def on_tableview_clicked(self, idx):
print('clicked({!s},{!s})'.format(idx.row(), idx.column()))
@pyqtSlot(QModelIndex)
def on_tableview_doubleClicked(self, idx):
print('doubleClicked({!s},{!s})'.format(idx.row(), idx.column()))
@pyqtSlot(QModelIndex)
def on_tableview_entered(self, idx):
print('entered({!s},{!s})'.format(idx.row(), idx.column()))
@pyqtSlot(QModelIndex)
def on_tableview_pressed(self, idx):
print('pressed({!s},{!s})'.format(idx.row(), idx.column()))
@pyqtSlot(QModelIndex, int, int)
def on_itemmodel_rowsInserted(self, p, first, last):
print('rowsInserted({!s},{!s})'.format(first, last))
@pyqtSlot(QModelIndex, QModelIndex)
def on_selectmodel_currentChanged(self, c_idx, p_idx):
print('currentChanged({!s},{!s})<-({!s},{!s})'.format(
c_idx.row(), c_idx.column(), p_idx.row(), p_idx.column()))
@pyqtSlot(QItemSelection, QItemSelection)
def on_selectmodel_selectionChanged(self, s, d):
print('selectionChanged({!s})'.format(s.indexes()[0].row())) |
|
А поскольку вся эта бодяга затевалась с целью отслеживания генерации сигналов
в режиме реального времени, то ввиду отсутствия гербовой (т.е. отладочного ядра Qt)
пришлось писать на простой, обильно уснащая код отладочной печатью .
И вариант B в этом случае принял вот такой окончательный вид:
| Python | 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
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys, traceback
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWidgets import QAbstractItemView
from PyQt5.QtWidgets import QTableView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtCore import QMetaObject, QItemSelection, QModelIndex
from PyQt5.QtCore import QItemSelectionModel
def slot_trace_note(client):
return '{!s} --> {!s} --> {!s}'.format(
client.sender().objectName(),
client.sender().metaObject().method(
client.senderSignalIndex()).name().data().decode('utf-8'),
traceback.extract_stack()[-2][2])
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setObjectName('mainwindow')
self.setGeometry(300, 200, 500, 400)
w_view = QTableView(self)
w_view.setObjectName('tableview')
w_model = QStandardItemModel(self)
w_model.setObjectName('itemmodel')
w_model.setHorizontalHeaderLabels(['Деталь', 'Длина', 'Поставщик'])
w_view.setModel(w_model)
# в списке детей w_view появляется экземпляр QItemSelectionModel
w_select = w_view.findChild(QItemSelectionModel,
'', Qt.FindDirectChildrenOnly)
if w_select is not None:
w_select.setObjectName('selectmodel')
w_view.setSortingEnabled(True)
w_view.setSelectionBehavior(QAbstractItemView.SelectRows)
w_view.horizontalHeader().setStretchLastSection(True)
w_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
w_view.setSelectionMode(QAbstractItemView.SingleSelection)
meta = self.metaObject()
print(meta.methodCount())
for i in range(meta.methodCount()):
print(meta.method(i).name().data().decode('utf-8'))
print()
QMetaObject.connectSlotsByName(self)
rows = [['Стойка', 200, 'Фабрика'],
['Штанга', 10, 'Завод'],
['Перекладина', 5, 'Мастерская']
]
for row in rows:
w_model.appendRow([QStandardItem(row[0]),
QStandardItem(str(row[1])),
QStandardItem(row[2])])
w_view.selectRow(0)
self.setCentralWidget(w_view)
@pyqtSlot(QModelIndex, name='on_tableview_activated')
@pyqtSlot(QModelIndex, name='on_tableview_clicked')
@pyqtSlot(QModelIndex, name='on_tableview_doubleClicked')
@pyqtSlot(QModelIndex, name='on_tableview_entered')
@pyqtSlot(QModelIndex, name='on_tableview_pressed')
@pyqtSlot(QModelIndex)
def on_tableview_signals(self, idx):
print('{!s}({!s},{!s})'.format(slot_trace_note(self),
idx.row(), idx.column()
))
@pyqtSlot(QModelIndex, int, int)
def on_itemmodel_rowsInserted(self, p, first, last):
print('{!s}({!s},{!s})'.format(slot_trace_note(self),
first, last
))
@pyqtSlot(QModelIndex, QModelIndex)
def on_selectmodel_currentChanged(self, c_idx, p_idx):
print('{!s}({!s},{!s})<--({!s},{!s})'.format(slot_trace_note(self),
c_idx.row(), c_idx.column(), p_idx.row(), p_idx.column()
))
@pyqtSlot(QItemSelection, QItemSelection)
def on_selectmodel_selectionChanged(self, s, d):
print('{!s}({!s})'.format(slot_trace_note(self),
s.indexes()[0].row()
))
if __name__ == '__main__':
app = QApplication(sys.argv)
mwin = MainWindow()
mwin.show()
app.exec_()
sys.exit() |
|
Ну, да, не без греха. Как получить имя выполняющегося слота средствами Qt разобраться
пока не сумел - пришлось traceback запользовать. Может быть кто знающий подскажет?
И вот такой симпатичный протокольчик по итогам:
| Code | 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
| user@linux:~> python3 by_name_probe_b.py
46
destroyed
destroyed
objectNameChanged
deleteLater
_q_reregisterTimers
windowTitleChanged
windowIconChanged
windowIconTextChanged
customContextMenuRequested
setEnabled
setDisabled
setWindowModified
setWindowTitle
setStyleSheet
setFocus
update
repaint
setVisible
setHidden
show
hide
showMinimized
showMaximized
showFullScreen
showNormal
close
raise
lower
updateMicroFocus
_q_showIfNotHidden
grab
grab
iconSizeChanged
toolButtonStyleChanged
setAnimated
setDockNestingEnabled
setUnifiedTitleAndToolBarOnMac
on_selectmodel_currentChanged
on_itemmodel_rowsInserted
on_tableview_activated
on_tableview_clicked
on_tableview_doubleClicked
on_tableview_entered
on_tableview_pressed
on_tableview_signals
on_selectmodel_selectionChanged
itemmodel --> rowsInserted --> on_itemmodel_rowsInserted(0,0)
itemmodel --> rowsInserted --> on_itemmodel_rowsInserted(1,1)
itemmodel --> rowsInserted --> on_itemmodel_rowsInserted(2,2)
selectmodel --> currentChanged --> on_selectmodel_currentChanged(0,0)<--(-1,-1)
selectmodel --> selectionChanged --> on_selectmodel_selectionChanged(0)
selectmodel --> selectionChanged --> on_selectmodel_selectionChanged(1)
selectmodel --> currentChanged --> on_selectmodel_currentChanged(1,0)<--(0,0)
tableview --> activated --> on_tableview_signals(1,0)
selectmodel --> currentChanged --> on_selectmodel_currentChanged(2,0)<--(1,0)
selectmodel --> selectionChanged --> on_selectmodel_selectionChanged(2)
tableview --> pressed --> on_tableview_signals(2,0)
tableview --> clicked --> on_tableview_signals(2,0)
tableview --> activated --> on_tableview_signals(2,0)
selectmodel --> currentChanged --> on_selectmodel_currentChanged(1,0)<--(2,0)
selectmodel --> selectionChanged --> on_selectmodel_selectionChanged(1)
tableview --> pressed --> on_tableview_signals(1,0)
tableview --> clicked --> on_tableview_signals(1,0)
tableview --> activated --> on_tableview_signals(1,0)
tableview --> pressed --> on_tableview_signals(1,0)
tableview --> clicked --> on_tableview_signals(1,0)
tableview --> activated --> on_tableview_signals(1,0)
tableview --> doubleClicked --> on_tableview_signals(1,0)
tableview --> clicked --> on_tableview_signals(1,0)
tableview --> activated --> on_tableview_signals(1,0)
selectmodel --> currentChanged --> on_selectmodel_currentChanged(2,0)<--(1,0)
selectmodel --> selectionChanged --> on_selectmodel_selectionChanged(2)
tableview --> pressed --> on_tableview_signals(2,0)
tableview --> clicked --> on_tableview_signals(2,0)
tableview --> activated --> on_tableview_signals(2,0)
tableview --> doubleClicked --> on_tableview_signals(2,0)
tableview --> clicked --> on_tableview_signals(2,0)
tableview --> activated --> on_tableview_signals(2,0) |
|
Нельзя не отметить, что поведение под Windows отличается от поведения под Linux
(возможно, в силу разницы в версиях PyQt).
|