PyQt5 - 如何在 QDateEdit 上禁用周末

PyQt5 - How can I disable weekend on a QDateEdit

我正在使用 QDateEdit 选择特定日期,但我想取消周末,我只想选择工作日。

self.date =  QDateEdit(calendarPopup = True)
self.date.setDisplayFormat("dd-MM-yyyy")
self.date.setMinimumDate(QDate(2021,10,1))    
self.date.setDate(QDate(datetime.today()))

QCalendarWidget 只允许接受一个范围内的日期,并且可以选择该范围内的所有日期。

我能想到的唯一解决方案(除了从头开始创建您自己的日历)是子类化 QCalendarWidget,访问底层 QTableView(显示日历的内容)并执行以下操作:

  • 设置选择模式为NoSelection;
  • 在视图(过滤按键)视图的视口(过滤鼠标事件)上安装事件过滤器;
  • 实现 dateForIndex 以检索 table 的特定索引处显示的日期;
  • 当鼠标位置的索引在工作日时,将选择模式设置为SingleSelection,否则设置回NoSelection
  • 在使用键盘导航时 avoid/skip 周末实施适当的选择;
class CalendarWidget(QtWidgets.QCalendarWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setSelectionMode(self.NoSelection)
        self.view = self.findChild(QtWidgets.QAbstractItemView, 'qt_calendar_calendarview')
        self.view.installEventFilter(self)
        self.view.viewport().installEventFilter(self)

    def dateForIndex(self, index):
        row = index.row()
        column = index.column()
        if self.horizontalHeaderFormat():
            row -= 1
        if self.verticalHeaderFormat():
            column -= 1
        if not 0 <= row <= 5 or not 0 <= column <= 6:
            return QtCore.QDate()

        day = index.data()
        month = self.monthShown()
        year = self.yearShown()

        # day numbers bigger than 21 cannot be shown in the first 3 rows
        if row <= 2 and day > 21:
            month -= 1
            if month <= 0:
                month = 12
                year -= 1
        # day numbers smaller than 15 cannot be shown in the last 3 rows
        elif row >= 3 and day < 15:
            month += 1
            if month >= 13:
                month = 1
                year += 1

        date = QtCore.QDate(year, month, day)
        if self.minimumDate() <= date <= self.maximumDate():
           return date
        return QtCore.QDate()

    def moveCursor(self, key):
        currentDate = self.dateForIndex(self.view.currentIndex())
        delta = 1

        if key == QtCore.Qt.Key_Up:
            newDate = currentDate.addDays(-7)
        elif key == QtCore.Qt.Key_Down:
            newDate = currentDate.addDays(7)
        elif key == QtCore.Qt.Key_Left:
            newDate = currentDate.addDays(-1)
        elif key == QtCore.Qt.Key_Right:
            newDate = currentDate.addDays(1)
        elif key == QtCore.Qt.Key_Home:
            newDate = QtCore.QDate(currentDate.year(), currentDate.month(), 1)
            delta = -1
        elif key == QtCore.Qt.Key_End:
            newDate = QtCore.QDate(currentDate.year(), currentDate.month(), 
                currentDate.daysInMonth())
            delta = -1
        elif key == QtCore.Qt.Key_PageUp:
            newDate = currentDate.addMonths(-1)
            delta = -1
        elif key == QtCore.Qt.Key_PageDown:
            newDate = currentDate.addMonths(1)
            delta = -1
        else:
            return

        newDate = max(self.minimumDate(), min(newDate, self.maximumDate()))
        if currentDate != newDate:
            # if it's a day of the weekend, add the delta until a work day is
            # found; for Home/End/Page keys the delta is inverted, as we need to
            # ensure that we stay in the days of the selected month, and if the
            # function reaches a weekend it could skip a month
            while newDate.dayOfWeek() > 5:
                if newDate > currentDate:
                    newDate = newDate.addDays(delta)
                else:
                    newDate = newDate.addDays(-delta)
            if self.minimumDate() <= newDate <= self.maximumDate():
                return newDate

    def eventFilter(self, obj, event):
        if (event.type() in (event.MouseButtonPress, event.MouseButtonRelease, event.MouseButtonDblClick) 
            and event.button() == QtCore.Qt.LeftButton):
                index = self.view.indexAt(event.pos())
                if index.isValid():
                    date = self.dateForIndex(index)
                    if date.dayOfWeek() <= 5:
                        self.setSelectionMode(self.SingleSelection)
                    else:
                        self.setSelectionMode(self.NoSelection)

        elif event.type() == event.MouseMove and event.buttons() == QtCore.Qt.LeftButton:
            index = self.view.indexAt(event.pos())
            if index.isValid():
                date = self.dateForIndex(index)
                if not date.isValid() or date.dayOfWeek() > 5:
                    # ignore mouse move events for weekends
                    return True

        elif event.type() == event.KeyPress:
            newDate = self.moveCursor(event.key())
            if newDate:
                self.setSelectedDate(newDate)
                return True
        return super().eventFilter(obj, event)

此实现的唯一问题是,如果设置了 dateEditEnabled(默认设置),则无法阻止选择周末,除非连接到 activatedselectionChanged 发出信号并最终将所选日期重置为有效日期。