PyQt: испускать сигнал при вводе ячейки в QCalendarWidget
В моем приложении Qt я использую QCalendarWidget
и я хотел бы получать уведомления, когда мышь входит в новую ячейку календаря. Я знаю что QCalendarWidget
использует QTableView
внутренне, который наследует от QAbstractItemView, и это имеет entered
сигнал:
Этот сигнал испускается, когда курсор мыши входит в элемент, указанный индексом. Для работы этой функции необходимо включить отслеживание мыши.
Я пытался получить сигнал со следующим кодом:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyCalendar:
def __init__(self):
app = QApplication(sys.argv)
window = QMainWindow()
cal = QCalendarWidget(window)
window.resize(320, 240)
cal.resize(320, 240)
table = cal.findChild(QTableView)
table.setMouseTracking(True)
table.entered.connect(self.onCellEntered)
window.show()
sys.exit(app.exec_())
def onCellEntered(self, index):
print("CellEntered")
if __name__ == "__main__":
window = MyCalendar()
Но моя функция обратного вызова никогда не вызывается. У тебя есть идеи почему?
2 ответа
QCalendarWidget
класс использует пользовательское табличное представление, которое обходит обычные обработчики событий мыши, поэтому enetered
сигнал никогда не излучается. Однако можно обойти это, используя фильтр событий, чтобы испустить пользовательский сигнал, который делает то же самое.
Вот демонстрационный скрипт, который реализует это:
import sys
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
cellEntered = QtCore.pyqtSignal(object)
def __init__(self):
super(Window, self).__init__()
self.calendar = QtGui.QCalendarWidget(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.calendar)
self._table = self.calendar.findChild(QtGui.QTableView)
self._table.setMouseTracking(True)
self._table.installEventFilter(self)
self._index = None
self.cellEntered.connect(self.handleCellEntered)
def eventFilter(self, source, event):
if source is self._table:
if event.type() == QtCore.QEvent.MouseMove:
index = QtCore.QPersistentModelIndex(
source.indexAt(event.pos()))
if index != self._index:
self._index = index
self.cellEntered.emit(QtCore.QModelIndex(index))
elif event.type() == QtCore.QEvent.Leave:
self._index = None
return super(Window, self).eventFilter(source, event)
def handleCellEntered(self, index):
print(index.row(), index.column())
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
Я немного исследовал и думаю, что знаю, почему это происходит.
QCalendarWidget
создает частный подкласс QTableView
называется QCalendarView
и создает его как ребенка самого себя. (Этот экземпляр называется qt_calendar_calendarview
.)
Если вы посмотрите на QCalendarView
код (Qt 5), вы увидите это:
void QCalendarView::mouseMoveEvent(QMouseEvent *event)
{
[...]
if (!calendarModel) {
QTableView::mouseMoveEvent(event);
return;
}
[...]
}
Это значит, только если нет calendarModel
суперклассы mouseMoveEvent
называется который отвечает за излучение entered
сигнал. Все это - деталь реализации `QCalendarWidget, поэтому, вероятно, лучше не полагаться ни на что из этого.
Поэтому, чтобы обойти это, я не уверен, что лучший подход. Вам нужно поймать событие, прежде чем оно попадет к столу. Это можно сделать с помощью QObject.installEventFilter()
или повторная реализация QWidget.mouseMoveEvent()
, но тогда вы не получите модельный индекс напрямую. Вы могли бы, вероятно, использовать QAbstractItemView.indexAt()
для этого.