【问题标题】:Pyqt5: How can I create a manually editable QDateEdit widget?Pyqt5:如何创建手动可编辑的 QDateEdit 小部件?
【发布时间】:2021-01-04 09:44:57
【问题描述】:

我知道对于 QComboBox 有一个名为 editable 的方法,您可以使用它手动编辑当前值: combo.setEditable(True)。我找不到与 QDateEdit 类似的内容。我希望用户能够删除整个日期字符串并手动输入年份值或将其留空。

【问题讨论】:

  • 你不能因为 QDateEdit 继承自 QDateTimeEdit,而 QDateTimeEdit 又继承自 QAbstractSpinBox,而不是 QComboBox。您真的需要旋转框功能(箭头和向上/向下键支持)吗?因为如果您不这样做,您可以简单地将 QLineEdit 与自定义 QCompleter 一起使用。
  • 我有一个带有日历弹出窗口的 QDateEdit,默认设置为今天(年-月-日)。但用户需要能够手动添加仅年份年份(例如 2020) 而不是完整日期或将其留空以防他们不知道日期。
  • 这不是我问的。 QDateEdit 使用向上/向下箭头(和向上/向下键)无需键入即可更改日/月/年。你需要吗?
  • 没有,因为我添加了一个日历弹出窗口,所以只有一个 向下 箭头。我还想保留日历。添加图片。

标签: python pyqt5 qcombobox qdateedit


【解决方案1】:

无法进行所请求的编辑,因为 QDateEdit(基于 QDateTimeEdit)继承自 QAbstractSpinBox,它已经包含 QLineEdit 小部件,但对可以输入的内容有严格的规定。

虽然子类化 QDateEdit 可能的,但它可能有点复杂,因为它使用高级控件(最重要的是“当前部分”,它告诉正在编辑日期的哪一部分)。切换日期格式(“yyyy-MM-dd”和“yyyy”)是可能的,但不是自动的,并且需要大量检查,可能使用正则表达式和高级文本光标控制。

根据我的经验,如果没有错误或意外行为,更改 QDateTimeEdit 类的键盘行为确实很难实现,并且由于此处不需要旋转框的主要功能(向上/向下箭头和向上/向下箭头键),您可以创建一个嵌入 QLineEdit 和子 QCalendarWidget 的控件,该控件使用按钮作为弹出窗口打开。

我还实现了一个小型验证器来忽略大多数无效输入(但您仍然可以输入无效日期,例如 2020-31-31)。

class SimpleDateValidator(QtGui.QValidator):
    def validate(self, text, pos):
        if not text:
            return self.Acceptable, text, pos
        fmt = self.parent().format()
        _sep = set(fmt.replace('y', '').replace('M', '').replace('d', ''))
        for l in text:
            # ensure that the typed text is either a digit or a separator
            if not l.isdigit() and l not in _sep:
                return self.Invalid, text, pos
        years = fmt.count('y')
        if len(text) <= years and text.isdigit():
            return self.Acceptable, text, pos
        if QtCore.QDate.fromString(text, fmt).isValid():
            return self.Acceptable, text, pos
        return self.Intermediate, text, pos


class DateEdit(QtWidgets.QWidget):
    customFormat = 'yyyy-MM-dd'
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self.lineEdit = QtWidgets.QLineEdit()
        layout.addWidget(self.lineEdit)
        self.lineEdit.setMaxLength(len(self.format()))
        self.validator = SimpleDateValidator(self)
        self.lineEdit.setValidator(self.validator)

        self.dropDownButton = QtWidgets.QToolButton()
        layout.addWidget(self.dropDownButton)
        self.dropDownButton.setIcon(
            self.style().standardIcon(QtWidgets.QStyle.SP_ArrowDown))
        self.dropDownButton.setMaximumHeight(self.lineEdit.sizeHint().height())
        self.dropDownButton.setCheckable(True)
        self.dropDownButton.setFocusPolicy(QtCore.Qt.NoFocus)

        self.calendar = QtWidgets.QCalendarWidget()
        self.calendar.setWindowFlags(QtCore.Qt.Popup)
        self.calendar.installEventFilter(self)

        self.dropDownButton.pressed.connect(self.showPopup)
        self.dropDownButton.released.connect(self.calendar.hide)
        self.lineEdit.editingFinished.connect(self.editingFinished)
        self.calendar.clicked.connect(self.setDate)
        self.calendar.activated.connect(self.setDate)

        self.setDate(QtCore.QDate.currentDate())

    def editingFinished(self):
        # optional: clear the text if the date is not valid when leaving focus;
        # this will only work if *NO* validator is set
        if self.calendar.isVisible():
            return
        if not self.isValid():
            self.lineEdit.setText('')

    def format(self):
        return self.customFormat or QtCore.QLocale().dateFormat(QtCore.QLocale.ShortFormat)

    def setFormat(self, format):
        # only accept numeric date formats
        if format and 'MMM' in format or 'ddd' in format:
            return
        self.customFormat = format
        self.setDate(self.calendar.selectedDate())
        self.calendar.hide()
        self.lineEdit.setMaxLength(self.format())
        self.validator.setFormat(self.format())

    def text(self):
        return self.lineEdit.text()

    def date(self):
        if not self.isValid():
            return None
        date = QtCore.QDate.fromString(self.text(), self.format())
        if date.isValid():
            return date
        return int(self.text())

    def setDate(self, date):
        self.lineEdit.setText(date.toString(self.format()))
        self.calendar.setSelectedDate(date)
        self.calendar.hide()

    def setDateRange(self, minimum, maximum):
        self.calendar.setDateRange(minimum, maximum)

    def isValid(self):
        text = self.text()
        if not text:
            return False
        date = QtCore.QDate.fromString(text, self.format())
        if date.isValid():
            self.setDate(date)
            return True
        try:
            year = int(text)
            start = self.calendar.minimumDate().year()
            end = self.calendar.maximumDate().year()
            if start <= year <= end:
                return True
        except:
            pass
        return False

    def hidePopup(self):
        self.calendar.hide()

    def showPopup(self):
        pos = self.lineEdit.mapToGlobal(self.lineEdit.rect().bottomLeft())
        pos += QtCore.QPoint(0, 1)
        rect = QtCore.QRect(pos, self.calendar.sizeHint())
        self.calendar.setGeometry(rect)
        self.calendar.show()
        self.calendar.setFocus()

    def eventFilter(self, source, event):
        # press or release the button when the calendar is shown/hidden
        if event.type() == QtCore.QEvent.Hide:
            self.dropDownButton.setDown(False)
        elif event.type() == QtCore.QEvent.Show:
            self.dropDownButton.setDown(True)
        return super().eventFilter(source, event)

    def keyPressEvent(self, event):
        if event.key() in (QtCore.Qt.Key_Down, QtCore.Qt.Key_F4):
            if not self.calendar.isVisible():
                self.showPopup()
        super().keyPressEvent(event)

【讨论】:

  • 非常有帮助的答案。我正在寻找类似的东西。 +1
猜你喜欢
  • 2022-12-18
  • 1970-01-01
  • 2018-11-27
  • 1970-01-01
  • 2021-09-06
  • 2017-11-06
  • 1970-01-01
  • 2019-01-30
  • 1970-01-01
相关资源
最近更新 更多