I know that for QComboBox there is method called editable with which you can manually edit current value:
combo.setEditable(True)
. I could not find something similar with QDateEdit. I want user be able to delete entire date string and manually enter just a year value or leave it blank.

- 110
- 2
- 11
-
1You cannot as QDateEdit inherits from QDateTimeEdit, which in turn inherits from QAbstractSpinBox, not QComboBox. Do you actually need the spinbox features (the arrows and the up/down keys support)? Because if you don't you can simply use a QLineEdit with a custom QCompleter. – musicamante Sep 17 '20 at 14:05
-
I have QDateEdit with a calendar pop-up and set to today by default **(year-month-day)**. But user needs to be able manually add only year **year (e.g 2020)** instead of full date or **leave it blank** in case they don't know the date. – jabbarlee Sep 17 '20 at 16:27
-
That's not what I asked. QDateEdit uses up/down arrows (and up/down keys) to change day/month/year without typing. Do you need them or not? – musicamante Sep 17 '20 at 16:47
-
No since I have added a calendar pop-up there is only one **down** arrow. I still wanna keep calendar. Added pic. – jabbarlee Sep 17 '20 at 17:50
1 Answers
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.
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). 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.
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.
I also implemented a small validator to ignore most invalid inputs (but you could still type an invalid date, for example 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)

- 41,230
- 6
- 33
- 58