在 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_())
我目前正在制作一个具有日历的 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_())