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() для этого.

Другие вопросы по тегам