简体   繁体   English

Pyqt5:如何创建可手动编辑的 QDateEdit 小部件?

[英]Pyqt5: How can I create a manually editable QDateEdit widget?

I know that for QComboBox there is method called editable with which you can manually edit current value: combo.setEditable(True) .我知道对于QComboBox有一种称为editable 的方法,您可以使用它手动编辑当前值: combo.setEditable(True) I could not find something similar with QDateEdit .我找不到与QDateEdit类似的东西 I want user be able to delete entire date string and manually enter just a year value or leave it blank.我希望用户能够删除整个日期字符串并手动输入一个年份值或将其留空。

在此处输入图片说明

Editing such as what is requested is not possible, since QDateEdit (which is based on QDateTimeEdit) inherits from QAbstractSpinBox, which already contains a QLineEdit widget, but has strict rules about what can be typed.像所请求的那样编辑是不可能的,因为 QDateEdit(它基于 QDateTimeEdit)继承自 QAbstractSpinBox,它已经包含一个 QLineEdit 小部件,但对可以输入的内容有严格的规则。

While subclassing QDateEdit is possible, it could be a bit complex, as it uses advanced controls (most importantly the "current section", which tells what part of the date is being edited).虽然子类化 QDateEdit可能的,但它可能有点复杂,因为它使用高级控件(最重要的是“当前部分”,它说明正在编辑日期的哪一部分)。 Switching date formats ("yyyy-MM-dd" and "yyyy") is possible, but not automatically, and it would take lots of checking, possibly with regex and advanced text cursor control.切换日期格式(“yyyy-MM-dd”和“yyyy”)是可能的,但不是自动的,并且需要大量检查,可能使用正则表达式和高级文本光标控制。

In my experience, changing the keyboard behavior of QDateTimeEdit classes is really hard to achieve without bugs or unexpected behavior, and since the main features of the spinbox (arrows up/down and up/down arrow keys) are not required here, you can create a control that embeds both a QLineEdit and a child QCalendarWidget that is opened as a popup using a button.根据我的经验,如果没有错误或意外行为,改变 QDateTimeEdit 类的键盘行为真的很难实现,而且由于这里不需要旋转框的主要功能(向上/向下和向上/向下箭头键),您可以创建嵌入 QLineEdit 和子 QCalendarWidget 的控件,该控件使用按钮作为弹出窗口打开。

I also implemented a small validator to ignore most invalid inputs (but you could still type an invalid date, for example 2020-31-31).我还实现了一个小型验证器来忽略大多数无效输入(但您仍然可以输入无效日期,例如 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM