Python 如何在QDateTimeEdit中显示日历弹出窗口,而不在显示的格式字符串中显示日期?
我正在尝试实现一个QDateTimeEdit子类,它将只显示时间部分(显示格式为“HH:mm”)和一个日历图标,单击该图标将触发日历弹出窗口,允许用户设置日期部分。我需要它有一个较短的datetime表示形式,因为很少需要更改日期 在这里添加.setCalendarPopup(True)是不够的,因为它仅在格式字符串包含日期部分时才有效,事实并非如此 我的尝试(按钮嵌入取自): 我的解决方案不能作为Python 如何在QDateTimeEdit中显示日历弹出窗口,而不在显示的格式字符串中显示日期?,python,pyqt,pyqt5,Python,Pyqt,Pyqt5,我正在尝试实现一个QDateTimeEdit子类,它将只显示时间部分(显示格式为“HH:mm”)和一个日历图标,单击该图标将触发日历弹出窗口,允许用户设置日期部分。我需要它有一个较短的datetime表示形式,因为很少需要更改日期 在这里添加.setCalendarPopup(True)是不够的,因为它仅在格式字符串包含日期部分时才有效,事实并非如此 我的尝试(按钮嵌入取自): 我的解决方案不能作为self.calendarWidget()返回None(参见最后一行) 所以问题是,是否有任何方法
self.calendarWidget()
返回None
(参见最后一行)
所以问题是,是否有任何方法可以在不使用显示格式字符串中的日期部分的情况下从QDateTimeEdit触发日历弹出窗口(并使用它提供的值)?QDateTimeEdit是一种特殊类型的小部件,它也基于其显示格式实现其功能。长话短说,如果您设置的显示格式不显示任何日期值,它的行为将完全类似于QTimeEdit(如果您不显示时间值,它的行为将类似于QDateEdit) 这显然是出于设计原因,我想也是出于优化原因,但这种方法也会带来一些问题,就像您的案例一样 事实上,如果显示格式没有显示任何日期值,您甚至无法设置自己的日历小部件(Qt将对此发出警告) 为了实现您想要的,需要对现有方法进行一些重写。
还可以考虑通过添加子控件来尝试“破解”。不仅您的实现无法按预期工作,而且还可能导致意外的行为和绘制工件,这至少会使小部件难看(如果不是不可用的话)。最好的方法通常是尝试使用小部件已经提供的内容,即使它看起来可能比看起来更复杂。在这种情况下,您可以通过使用用于绘制和单击检测的QStyle函数,手动绘制组合框箭头,该箭头将通过
setCalendarPopup(True)
为“正常”QDateTimeEdit显示
最后,由于前面介绍的设计选择,每次更改日期、日期时间或显示格式时,如果显示格式不显示日期值,则日期范围将限制为当前日期,因此每次都需要将范围设置回原来的日期,否则无法从日历中选择任何其他日期
下面是一个可能的实现:
class ShortDatetimeEdit(QDateTimeEdit):
def __init__(self, *args, **kwargs):
super(ShortDatetimeEdit, self).__init__(*args, **kwargs)
self.setCurrentSection(QDateTimeEdit.MinuteSection)
self.setWrapping(True)
self.setDisplayFormat("HH:mm")
self._calendarPopup = QCalendarWidget()
self._calendarPopup.setWindowFlags(Qt.Popup)
self._calendarPopup.setFocus()
self._calendarPopup.activated.connect(self.setDateFromPopup)
self._calendarPopup.clicked.connect(self.setDateFromPopup)
def getControlAtPos(self, pos):
opt = QStyleOptionComboBox()
opt.initFrom(self)
opt.editable = True
opt.subControls = QStyle.SC_All
return self.style().hitTestComplexControl(
QStyle.CC_ComboBox, opt, pos, self)
def showPopup(self):
self._calendarPopup.setDateRange(self.minimumDate(), self.maximumDate())
# the following lines are required to ensure that the popup will always
# be visible within the current screen geometry boundaries
rect = self.rect()
isRightToLeft = self.layoutDirection() == Qt.RightToLeft
pos = rect.bottomRight() if isRightToLeft else rect.bottomLeft()
pos2 = rect.topRight() if isRightToLeft else rect.topLeft()
pos = self.mapToGlobal(pos)
pos2 = self.mapToGlobal(pos2)
size = self._calendarPopup.sizeHint()
for screen in QApplication.screens():
if pos in screen.geometry():
geo = screen.availableGeometry()
break
else:
geo = QApplication.primaryScreen().availableGeometry()
if isRightToLeft:
pos.setX(pos.x() - size.width())
pos2.setX(pos2.x() - size.width())
if pos.x() < geo.left():
pos.setX(max(pos.x(), geo.left()))
elif pos.x() + size.width() > screen.right():
pos.setX(max(pos.x() - size.width(), geo.right() - size.width()))
else:
if pos.x() + size.width() > geo.right():
pos.setX(geo.right() - size.width())
if pos.y() + size.height() > geo.bottom():
pos.setY(pos2.y() - size.height())
elif pos.y() < geo.top():
pos.setY(geo.top())
if pos.y() < geo.top():
pos.setY(geo.top())
if pos.y() + size.height() > geo.bottom():
pos.setY(geo.bottom() - size.height())
self._calendarPopup.move(pos)
self._calendarPopup.show()
def setDateFromPopup(self, date):
self.setDate(date)
self._calendarPopup.close()
self.setFocus()
def setDate(self, date):
if self.date() == date:
return
dateRange = self.minimumDate(), self.maximumDate()
time = self.time()
# when the format doesn't display the date, QDateTimeEdit tries to reset
# the date range and emits an incorrect dateTimeChanged signal, so we
# need to block signals and emit the correct date change afterwards
self.blockSignals(True)
super().setDateTime(QDateTime(date, time))
self.setDateRange(*dateRange)
self.blockSignals(False)
self.dateTimeChanged.emit(self.dateTime())
def setDisplayFormat(self, fmt):
dateRange = self.minimumDate(), self.maximumDate()
super().setDisplayFormat(fmt)
self.setDateRange(*dateRange)
def mousePressEvent(self, event):
if self.getControlAtPos(event.pos()) == QStyle.SC_ComboBoxArrow:
self.showPopup()
def paintEvent(self, event):
# the "combobox arrow" is not displayed, so we need to draw it manually
opt = QStyleOptionSpinBox()
self.initStyleOption(opt)
optCombo = QStyleOptionComboBox()
optCombo.initFrom(self)
optCombo.editable = True
optCombo.frame = opt.frame
optCombo.subControls = opt.subControls
if self.hasFocus():
optCombo.activeSubControls = self.getControlAtPos(
self.mapFromGlobal(QCursor.pos()))
optCombo.state = opt.state
qp = QPainter(self)
self.style().drawComplexControl(QStyle.CC_ComboBox, optCombo, qp, self)
显然,这是一种简化:对于完整显示的日期/时间显示格式,您应该添加相关的实现
def stepBy(self, step):
section = self.currentSection()
if section == self.MSecSection:
newDateTime = self.dateTime.addMSecs(step)
elif section == self.SecondSection:
newDateTime = self.dateTime.addSecs(step)
elif self.currentSection() == self.MinuteSection:
newDateTime = self.dateTime().addSecs(60 * step)
elif self.currentSection() == self.HourSection:
newDateTime = self.dateTime().addSecs(3600 * step)
elif section == self.DaySection:
newDateTime = self.dateTime.addDays(step)
elif section == self.MonthSection:
newDateTime = self.dateTime.addMonths(step)
elif section == self.YearSection:
newDateTime = self.dateTime.addYears(step)
else:
super().stepBy(step)
return
if newDateTime.date() == self.date():
self.setTime(newDateTime.time())
else:
self.setDateTime(newDateTime)
最后(再次!),如果要确保鼠标滚轮始终响应鼠标光标下的部分,则需要在调用默认的wheeleevent()
之前将文本光标放置到适当的位置
def wheelEvent(自身,事件):
cursorPosition=self.lineEdit().cursorPositionAt(event.pos())
如果光标位置
您是否尝试构建自己的QCalendarWidget
?您的问题还不清楚,您是否可以将您想要获得的内容放在图像中,并提供
def stepBy(self, step):
if self.currentSection() == self.MinuteSection:
newDateTime = self.dateTime().addSecs(60 * step)
elif self.currentSection() == self.HourSection:
newDateTime = self.dateTime().addSecs(3600 * step)
else:
super().stepBy(step)
return
if newDateTime.date() == self.date():
self.setTime(newDateTime.time())
else:
self.setDateTime(newDateTime)
def stepBy(self, step):
section = self.currentSection()
if section == self.MSecSection:
newDateTime = self.dateTime.addMSecs(step)
elif section == self.SecondSection:
newDateTime = self.dateTime.addSecs(step)
elif self.currentSection() == self.MinuteSection:
newDateTime = self.dateTime().addSecs(60 * step)
elif self.currentSection() == self.HourSection:
newDateTime = self.dateTime().addSecs(3600 * step)
elif section == self.DaySection:
newDateTime = self.dateTime.addDays(step)
elif section == self.MonthSection:
newDateTime = self.dateTime.addMonths(step)
elif section == self.YearSection:
newDateTime = self.dateTime.addYears(step)
else:
super().stepBy(step)
return
if newDateTime.date() == self.date():
self.setTime(newDateTime.time())
else:
self.setDateTime(newDateTime)
def wheelEvent(self, event):
cursorPosition = self.lineEdit().cursorPositionAt(event.pos())
if cursorPosition < len(self.lineEdit().text()):
letterAt = self.lineEdit().text()[cursorPosition - 1]
letterWidth = self.fontMetrics().width(letterAt) // 2
opt = QStyleOptionFrame()
opt.initFrom(self)
frameWidth = self.style().pixelMetric(
QStyle.PM_DefaultFrameWidth, opt, self)
letterWidth += frameWidth
pos = event.pos() - QPoint(letterWidth, 0)
otherCursorPosition = max(0, self.lineEdit().cursorPositionAt(pos))
if otherCursorPosition != cursorPosition:
cursorPosition = otherCursorPosition
self.lineEdit().setCursorPosition(max(0, cursorPosition))
super().wheelEvent(event)