在 QCalendarWidget 中向 QTableView 添加项目

Adding items to a QTableView within QCalendarWidget

我目前正在制作一个具有日历的 to-do 应用程序。每当用户在特定日期有事件时,左上角会出现一个红色圆圈。每当用户双击日期时,我希望它显示一个新的 window,其中包含当天的事件信息。但是,我无法将信息存储到每个日期中。我怎样才能使每个日期都有一种可以存储事件的列表?

这里是 UI:

这是子类 QCalendarWidget 的代码:

class TodoCalendar(QtWidgets.QCalendarWidget):
    def __init__(self, list_of_events, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.list_of_events = list_of_events
        //list_of_events is a list of all events the user has created

        self.table = self.findChild(QtWidgets.QTableView)
        self.table.viewport().installEventFilter(self)

    def paintCell(self, painter, rect, date):
        super().paintCell(painter, rect, date)
        for event in self.list_of_events.values():
            if event.due_time == date:
                painter.setBrush(Qt.red)
                painter.drawEllipse(rect.topLeft() + QPoint(12, 7), 3, 3)

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.MouseButtonDblClick and source is self.table.viewport()):
            index = self.table.indexAt(event.pos())
            print(f"row: {index.row()}, column: {index.column()}, text: {index.data()}")
        return super().eventFilter(source, event)

这里还有 list_of_events 的列表:

{'test changed': <CustomWidgets.TodoEvent object at 0x00000230A5A72908>, 'due 10/8': <CustomWidgets.TodoEvent object at 0x00000230A5AA5080>, 'also due 10/9': <CustomWidgets.TodoEvent object at 0x00000230A5AC4B00>, 'also due 10/9 too': <CustomWidgets.TodoEvent object at 0x00000230A5AD0550>, 'due 10/9 too too': <CustomWidgets.TodoEvent object at 0x00000230A5AD0A90>, '10/9 2': <CustomWidgets.TodoEvent object at 0x00000230A5AD6438>, '10/10': <CustomWidgets.TodoEvent object at 0x00000230A5AD64A8>, '10/10 also': <CustomWidgets.TodoEvent object at 0x00000230A5AD64E0>, '10/10 2': <CustomWidgets.TodoEvent object at 0x00000230A5AD6550>, '10/10 3': <CustomWidgets.TodoEvent object at 0x00000230A5AD65C0>, '10/10 4': <CustomWidgets.TodoEvent object at 0x00000230A5AD6630>, 'due 10/9 changed': <CustomWidgets.TodoEvent object at 0x00000230A5AD6668>}

每个 toDoEvent 都有一个标题、due_time、remind_time 和描述

不是按日期存储某些事件,另一种方法是获取给定行和列的日期,然后过滤事件。

问题是没有 public 方法来计算给定行和列的日期,所以我的解决方案使用 Qt private API code.

综合以上,解决方案是:

import random
from dataclasses import dataclass

from PyQt5 import QtCore, QtGui, QtWidgets


@dataclass
class Todo:
    date: QtCore.QDate
    name: str


class TodoCalendar(QtWidgets.QCalendarWidget):
    def __init__(self, list_of_events, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.list_of_events = list_of_events

        self.table = self.findChild(QtWidgets.QTableView)
        self.table.viewport().installEventFilter(self)

    def paintCell(self, painter, rect, date):
        super().paintCell(painter, rect, date)
        for event in self.list_of_events:
            if event.date == date:
                painter.setBrush(QtCore.Qt.red)
                painter.drawEllipse(rect.topLeft() + QtCore.QPoint(12, 7), 3, 3)

    def eventFilter(self, source, event):
        if (
            event.type() == QtCore.QEvent.MouseButtonDblClick
            and source is self.table.viewport()
        ):
            index = self.table.indexAt(event.pos())
            date = self.dateForCell(index.row(), index.column())
            today_events = [ev for ev in self.list_of_events if ev.date == date]
            if today_events:
                print(today_events)
        return super().eventFilter(source, event)

    def referenceDate(self):
        refDay = 1
        while refDay <= 31:
            refDate = QtCore.QDate(self.yearShown(), self.monthShown(), refDay)
            if refDate.isValid():
                return refDate
            refDay += 1
        return QtCore.QDate()

    @property
    def firstColumn(self):
        return (
            1
            if self.verticalHeaderFormat() == QtWidgets.QCalendarWidget.ISOWeekNumbers
            else 0
        )

    @property
    def firstRow(self):
        return (
            0
            if self.horizontalHeaderFormat()
            == QtWidgets.QCalendarWidget.NoHorizontalHeader
            else 1
        )

    def columnForDayOfWeek(self, day):
        if day < 1 or day > 7:
            return -1
        column = day - self.firstDayOfWeek()
        if column < 0:
            column += 7
        return column + self.firstColumn

    def columnForFirstOfMonth(self, date):
        return (self.columnForDayOfWeek(date.dayOfWeek()) - (date.day() % 7) + 8) % 7

    def dateForCell(self, row, column):
        if (
            row < self.firstRow
            or row > (self.firstRow + 6 - 1)
            or column < self.firstColumn
            or column > (self.firstColumn + 7 - 1)
        ):
            return QtCore.QDate()
        refDate = self.referenceDate()
        if not refDate.isValid():
            return QtCore.QDate()
        columnForFirstOfShownMonth = self.columnForFirstOfMonth(refDate)
        if columnForFirstOfShownMonth - self.firstColumn < 1:
            row -= 1
        requestedDay = (
            7 * (row - self.firstRow)
            + column
            - columnForFirstOfShownMonth
            - refDate.day()
            + 1
        )
        return refDate.addDays(requestedDay)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    events = [
        Todo(QtCore.QDate.currentDate().addDays(random.randint(1, 10)), f"name-{i}")
        for i in range(15)
    ]

    w = TodoCalendar(events)
    w.show()
    sys.exit(app.exec_())