QTimeEdit锁定小时旋转,范围小于一小时,keyboardTracking关闭



我在Python中有一个QTimeEdit,预定义的范围小于一个小时,比方说从08:45:00到09:15:00。我读了关于输入一个新值的问题,该值在键入(https://doc.qt.io/qt-6/qdatetimeedit.html#keyboard-tracking)并将keyboardTracking设置为False时超出了这些限制。我将默认值设置为最小值(因此08:45:00),然后我不能将其更改为高于08:59:59的值,因为旋转箭头在小时字段中已停用,并且我也不能使用numpad将小时字段中的08更改为09。

对于QTimeEdit,您是否也遇到过同样的限制?

Btw,包装函数不适应时间,因为它在同一字段上循环而不增加下一个…

br

对于这个问题已经存在一些解决方案,只涉及到滚轮和箭头按钮,但他们不考虑键盘编辑。

为了实现这一点,有必要重写validate()函数(继承自QAbstractSpinBox),并最终尝试修复其内容:

class FlexibleTimeEdit(QTimeEdit):
def validate(self, input, pos):
valid, newInput, newPos = super().validate(input, pos)
if valid == QValidator.Invalid:
possible = QTime.fromString(newInput)
if possible.isValid():
fixed = max(self.minimumTime(), min(possible, self.maximumTime()))
newInput = fixed.toString(self.displayFormat())
valid = QValidator.Acceptable
return valid, newInput, newPos

更完整的解决方案

由于这些方面实际上在其他相关类(QDateTimeEdit和QDateEdit)中很常见,因此我提出了一个更全面的修复,可以作为所有三种类型的混合使用,为这些方面提供键盘输入和箭头/轮子修复。

通过使用"abstract"类,必须与多重继承一起使用(它优先于Qt类),并提供以下内容:

  • 通过根据鼠标位置设置光标位置可选择覆盖滚轮行为,允许在不使用键盘或点击更改的情况下更新特定部分(即:如果当前部分是小时一,鼠标在分钟上,则滚轮将更新分钟);
  • 根据可用范围更新箭头按钮(和相关的stepBy()呼叫),而不限制范围到部分:如果当前小时是23,并且当前范围允许超过午夜,则加紧将相应地更新值;
  • 验证允许完整当前范围内的值,而不限制在section范围内;

注意,这有点高级,所以我强烈建议仔细研究下面的代码,以了解它是如何工作的。

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class _DateTimeEditFix(object):
_fullRangeStepEnabled = False
_wheelFollowsMouse = True
_deltaFuncs = {
QDateTimeEdit.YearSection: lambda obj, delta: obj.__class__.addYears(obj, delta), 
QDateTimeEdit.MonthSection: lambda obj, delta: obj.__class__.addMonths(obj, delta), 
QDateTimeEdit.DaySection: lambda obj, delta: obj.__class__.addDays(obj, delta), 
QDateTimeEdit.HourSection: lambda obj, delta: obj.__class__.addSecs(obj, delta * 3600), 
QDateTimeEdit.MinuteSection: lambda obj, delta: obj.__class__.addSecs(obj, delta * 60), 
QDateTimeEdit.SecondSection: lambda obj, delta: obj.__class__.addSecs(obj, delta), 
QDateTimeEdit.MSecSection: lambda obj, delta: obj.__class__.addMSecs(obj, delta), 
}
_typeRefs = {
QTimeEdit: ('Time', QTime), 
QDateEdit: ('Date', QDate), 
QDateTimeEdit: ('DateTime', QDateTime)
}
_sectionTypes = {
QDateTimeEdit.YearSection: 'date', 
QDateTimeEdit.MonthSection: 'date', 
QDateTimeEdit.DaySection: 'date', 
QDateTimeEdit.HourSection: 'time', 
QDateTimeEdit.MinuteSection: 'time', 
QDateTimeEdit.MSecSection: 'time'
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for cls in QTimeEdit, QDateEdit, QDateTimeEdit:
if isinstance(self, cls):
ref, self._baseType = self._typeRefs[cls]
break
else:
raise TypeError('Only QDateTimeEdit subclasses can be used')
self._getter = getattr(self, ref[0].lower() + ref[1:])
self._setter = getattr(self, 'set' + ref)
self._minGetter = getattr(self, 'minimum' + ref)
self._maxGetter = getattr(self, 'maximum' + ref)
@pyqtProperty(bool)
def fullRangeStepEnabled(self):
'''
Enable the arrows if the current value is still within the *full*
range of the widget, even if the current section is at the minimum
or maximum of its value.
If the value is False (the default), using a maximum time of 20:30, 
having the current time at 20:29 and the current section at
HourSection, the up arrow will be disabled. If the value is set to
True, the arrow is enabled, and going up (using arrow keys or mouse
wheel) will set the new time to 20:30.
'''
return self._fullRangeStepEnabled
@fullRangeStepEnabled.setter
def fullRangeStepEnabled(self, enabled):
if self._fullRangeStepEnabled != enabled:
self._fullRangeStepEnabled = enabled
self.update()
def setFullRangeStepEnabled(self, enabled):
self.fullRangeStepEnabled = enabled
@pyqtProperty(bool)
def wheelFollowsMouse(self):
'''
By default, QDateTimeEdit "scrolls" with the mouse wheel updating
the section in which the cursor currently is, even if the mouse
pointer hovers another section.
Setting this property to True always tries to update the section
that is *closer* to the mouse cursor.
'''
return self._wheelFollowsMouse
@wheelFollowsMouse.setter
def wheelFollowsMouse(self, follow):
self._wheelFollowsMouse = follow
def wheelEvent(self, event):
if self._wheelFollowsMouse:
edit = self.lineEdit()
edit.setCursorPosition(edit.cursorPositionAt(event.pos() - edit.pos()))
super().wheelEvent(event)
def stepBy(self, steps):
section = self.currentSection()
if section in self._deltaFuncs:
new = self._deltaFuncs[section](self._getter(), steps)
self._setter(
max(self._minGetter(), min(new, self._maxGetter()))
)
self.setSelectedSection(section)
else:
super().stepBy(steps)
def _stepPossible(self, value, target, section):
if self._fullRangeStepEnabled:
return value < target
if value > target:
return False
if section in self._deltaFuncs:
return self._deltaFuncs[section](value, 1) < target
return False
def stepEnabled(self):
enabled = super().stepEnabled()
current = self._getter()
section = self.currentSection()
if (
not enabled & self.StepUpEnabled 
and self._stepPossible(current, self._maxGetter(), section)
):
enabled |= self.StepUpEnabled
if (
not enabled & self.StepDownEnabled
and self._stepPossible(self._minGetter(), current, section)
):
enabled |= self.StepDownEnabled
return enabled
def validate(self, input, pos):
valid, newInput, newPos = super().validate(input, pos)
if valid == QValidator.Invalid:
# note: Qt6 deprecated some fromString() forms and QLocale functions
# should be preferred instead; see the documentation
possible = self._baseType.fromString(newInput, self.displayFormat())
if possible.isValid():
m = self._minGetter()
M = self._maxGetter()
fixedUp = max(m, min(possible, M))
if (
self._fullRangeStepEnabled
or m <= fixedUp <= M
):
newInput = fixedUp.toString(self.displayFormat())
valid = QValidator.Acceptable
return valid, newInput, newPos

class BetterDateTimeSpin(_DateTimeEditFix, QDateTimeEdit): pass
class BetterTimeSpin(_DateTimeEditFix, QTimeEdit): pass
class BetterDateSpin(_DateTimeEditFix, QDateEdit): pass

if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
test = QWidget()
layout = QVBoxLayout(test)
fullRangeCheck = QCheckBox('Allow full range')
layout.addWidget(fullRangeCheck)
timeSpin = BetterTimeSpin(
displayFormat='hh:mm:ss', 
minimumTime=QTime(8, 45, 0), 
maximumTime=QTime(9, 15, 50), 
)
layout.addWidget(timeSpin)
dateSpin = BetterDateTimeSpin(
displayFormat='dd/MM/yy hh:mm', 
minimumDateTime=QDateTime(2022, 9, 15, 19, 25), 
maximumDateTime=QDateTime(2023, 2, 12, 4, 58), 
)
layout.addWidget(dateSpin)
fullRangeCheck.toggled.connect(lambda full: [
timeSpin.setFullRangeStepEnabled(full), 
dateSpin.setFullRangeStepEnabled(full), 
])
test.show()
sys.exit(app.exec())

注意:与标准的QTimeEdit控件一样,它仍然不可能使用具有最小时间大于最大时间的范围的时间编辑(即:从20:00到08:00)。

最新更新