QDial 每转角度
QDial ange per revolution
我正在尝试使用 PyQt5、Python 3.7.3 使用 Raspberry pi4B 和 5 英寸触摸屏制作应用程序。
问题是我需要制作一个 QDial,但如果它从最小范围到最大范围,我希望它进行不止一次革命。例如,如果 Qdial 的范围是 0 到 500,我希望它每转 100 个点,那么你必须完整旋转 5 次才能从最小值到最大值。
这是我试过的:
`
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QGridLayout()
self.setLayout(layout)
self.dial = QDial()
self.dial.setMinimum(0)
self.dial.setMaximum(100)
self.dial.setValue(40)
self.dial.valueChanged.connect(self.sliderMoved)
self.dial.setWrapping(True)
self.text=QLabel()
layout.addWidget(self.dial)
layout.addWidget(self.text)
self.isHigher=False
def sliderMoved(self):
print("Dial value = %i" % (self.dial.value()))
self.text.setText(str(self.dial.value()))
if(self.dial.value()==100 and self.isHigher==False):
self.higher_range()
self.isHigher=True
if(self.dial.value()==100 and self.isHigher==True):
self.lower_range()
self.isHigher=False
def higher_range(self):
self.dial.setRange(100,200)
self.dial.setValue(105)
def lower_range(self):
self.dial.setRange(0,100)
self.dial.setValue(95)
app = QApplication(sys.argv)
screen = Window()
screen.show()
sys.exit(app.exec_())
`
但这不起作用,它不断从 95 变为 105,反之亦然。
QDial 是一个非常奇特的控件。虽然它仍然受到支持,但实施得很差,我相信这是一种选择:由于其性质,很难添加更多功能。我对它有相当多的经验,我知道它不是一个容易处理的元素。
它的一个问题是它表示一个单维范围,但从视觉上和 UI 来说,它是一个二维对象。
您要实现的目标 是 可能的,但请考虑 UI 元素应该 始终 显示其状态有明确的方式,并有相应的适当行为;这是 UI 可以告诉用户状态的唯一方法。物理表盘没有这个问题:你还有一个 触觉 响应,告诉你齿轮何时到达终点。
根据我的经验,我可以告诉您应该尽可能避免使用它:它看起来是一个不错且直观的小部件,但实际上很难获得对用户来说真正直观的正确结果。在某些情况下使用它是有意义的(在我的例子中,表示电子乐器的物理旋钮)。我建议你对 skeumorphism 和 UX 方面做一些研究。
也就是说,这是一个可能的 raw 实现。我已经覆盖了一些方面(最重要的是 valueChanged
信号,用于命名一致性),但为了正确实施,您应该做更多的工作(和测试)。
诀窍是根据“转数”设置范围:如果最大值为 500 并且选择了 5 转,则表盘将具有 实际 最大值100。然后,每当值发生变化时,我们检查 previous 值是否低于或高于实际范围的 minimum/maximum,并相应地更改转数。
两个重要说明:
- 由于QDial继承自QAbstractSlider,它有一个range(minimum, maximum + 1),由于除法可以休息一下,“最后”的革命将有一个不同的范围;
- 我没有实现车轮事件,因为这需要进一步检查并根据“先前”值和转数选择适当的行为;
class SpecialDial(QDial):
_cycleValueChange = pyqtSignal(int)
def __init__(self, minimum=0, maximum=100, cycleCount=2):
super().__init__()
assert cycleCount > 1, 'cycles must be 2 or more'
self.setWrapping(True)
self.cycle = 0
self.cycleCount = cycleCount
self._minimum = minimum
self._maximum = maximum
self._normalMaximum = (maximum - minimum) // cycleCount
self._lastMaximum = self._normalMaximum + (maximum - minimum) % self._normalMaximum
self._previousValue = super().value()
self._valueChanged = self.valueChanged
self.valueChanged = self._cycleValueChange
self._valueChanged.connect(self.adjustValueChanged)
self.setRange(0, self._normalMaximum)
def value(self):
return super().value() + self._normalMaximum * self.cycle
def minimum(self):
return self._minimum
def maximum(self):
return self._maximum()
def dialMinimum(self):
return super().minimum()
def dialMaximum(self):
return super().maximum()
def adjustValueChanged(self, value):
if value < self._previousValue:
if (value < self.dialMaximum() * .3 and self._previousValue > self.dialMaximum() * .6 and
self.cycle + 1 < self.cycleCount):
self.cycle += 1
if self.cycle == self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
elif (value > self.dialMaximum() * .6 and self._previousValue < self.dialMaximum() * .3 and
self.cycle > 0):
self.cycle -= 1
if self.cycle == 0:
self.setMaximum(self._normalMaximum)
new = self.value()
if self._previousValue != new:
self._previousValue = value
self.valueChanged.emit(self.value())
def setValue(self, value):
value = max(self._minimum, min(self._maximum, value))
if value == self.value():
return
block = self.blockSignals(True)
self.cycle, value = divmod(value, self._normalMaximum)
if self.dialMaximum() == self._normalMaximum and self.cycle == self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
elif self.dialMaximum() == self._lastMaximum and self.cycle < self.cycleCount - 1:
self.setMaximum(self._normalMaximum)
super().setValue(value)
self.blockSignals(block)
self._previousValue = self.value()
self.valueChanged.emit(self._previousValue)
def keyPressEvent(self, event):
key = event.key()
if key in (Qt.Key_Right, Qt.Key_Up):
step = self.singleStep()
elif key in (Qt.Key_Left, Qt.Key_Down):
step = -self.singleStep()
elif key == Qt.Key_PageUp:
step = self.pageStep()
elif key == Qt.Key_PageDown:
step = -self.pageStep()
elif key in (Qt.Key_Home, Qt.Key_End):
if key == Qt.Key_Home or self.invertedControls():
if super().value() > 0:
self.cycle = 0
block = self.blockSignals(True)
super().setValue(0)
self.blockSignals(block)
self.valueChanged.emit(self.value())
else:
if self.cycle != self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
self.cycle = self.cycleCount - 1
if super().value() != self._lastMaximum:
block = self.blockSignals(True)
super().setValue(self._lastMaximum)
self.blockSignals(block)
self.valueChanged.emit(self.value())
return
else:
super().keyPressEvent(event)
return
if self.invertedControls():
step *= -1
current = self.value()
new = max(self._minimum, min(self._maximum, current + step))
if current != new:
super().setValue(super().value() + (new - current))
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QGridLayout()
self.setLayout(layout)
self.dial = SpecialDial()
self.dial.valueChanged.connect(self.sliderMoved)
self.text=QLabel()
layout.addWidget(self.dial)
layout.addWidget(self.text)
def sliderMoved(self):
self.text.setText(str(self.dial.value()))
强烈建议您抽出时间:
- 考虑这是否真的是您想要的,因为如前所述,从用户体验的角度来看,这种控制可能非常棘手;
- 仔细阅读代码,理解其逻辑;
我正在尝试使用 PyQt5、Python 3.7.3 使用 Raspberry pi4B 和 5 英寸触摸屏制作应用程序。 问题是我需要制作一个 QDial,但如果它从最小范围到最大范围,我希望它进行不止一次革命。例如,如果 Qdial 的范围是 0 到 500,我希望它每转 100 个点,那么你必须完整旋转 5 次才能从最小值到最大值。
这是我试过的: `
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QGridLayout()
self.setLayout(layout)
self.dial = QDial()
self.dial.setMinimum(0)
self.dial.setMaximum(100)
self.dial.setValue(40)
self.dial.valueChanged.connect(self.sliderMoved)
self.dial.setWrapping(True)
self.text=QLabel()
layout.addWidget(self.dial)
layout.addWidget(self.text)
self.isHigher=False
def sliderMoved(self):
print("Dial value = %i" % (self.dial.value()))
self.text.setText(str(self.dial.value()))
if(self.dial.value()==100 and self.isHigher==False):
self.higher_range()
self.isHigher=True
if(self.dial.value()==100 and self.isHigher==True):
self.lower_range()
self.isHigher=False
def higher_range(self):
self.dial.setRange(100,200)
self.dial.setValue(105)
def lower_range(self):
self.dial.setRange(0,100)
self.dial.setValue(95)
app = QApplication(sys.argv)
screen = Window()
screen.show()
sys.exit(app.exec_())
`
但这不起作用,它不断从 95 变为 105,反之亦然。
QDial 是一个非常奇特的控件。虽然它仍然受到支持,但实施得很差,我相信这是一种选择:由于其性质,很难添加更多功能。我对它有相当多的经验,我知道它不是一个容易处理的元素。
它的一个问题是它表示一个单维范围,但从视觉上和 UI 来说,它是一个二维对象。
您要实现的目标 是 可能的,但请考虑 UI 元素应该 始终 显示其状态有明确的方式,并有相应的适当行为;这是 UI 可以告诉用户状态的唯一方法。物理表盘没有这个问题:你还有一个 触觉 响应,告诉你齿轮何时到达终点。
根据我的经验,我可以告诉您应该尽可能避免使用它:它看起来是一个不错且直观的小部件,但实际上很难获得对用户来说真正直观的正确结果。在某些情况下使用它是有意义的(在我的例子中,表示电子乐器的物理旋钮)。我建议你对 skeumorphism 和 UX 方面做一些研究。
也就是说,这是一个可能的 raw 实现。我已经覆盖了一些方面(最重要的是 valueChanged
信号,用于命名一致性),但为了正确实施,您应该做更多的工作(和测试)。
诀窍是根据“转数”设置范围:如果最大值为 500 并且选择了 5 转,则表盘将具有 实际 最大值100。然后,每当值发生变化时,我们检查 previous 值是否低于或高于实际范围的 minimum/maximum,并相应地更改转数。
两个重要说明:
- 由于QDial继承自QAbstractSlider,它有一个range(minimum, maximum + 1),由于除法可以休息一下,“最后”的革命将有一个不同的范围;
- 我没有实现车轮事件,因为这需要进一步检查并根据“先前”值和转数选择适当的行为;
class SpecialDial(QDial):
_cycleValueChange = pyqtSignal(int)
def __init__(self, minimum=0, maximum=100, cycleCount=2):
super().__init__()
assert cycleCount > 1, 'cycles must be 2 or more'
self.setWrapping(True)
self.cycle = 0
self.cycleCount = cycleCount
self._minimum = minimum
self._maximum = maximum
self._normalMaximum = (maximum - minimum) // cycleCount
self._lastMaximum = self._normalMaximum + (maximum - minimum) % self._normalMaximum
self._previousValue = super().value()
self._valueChanged = self.valueChanged
self.valueChanged = self._cycleValueChange
self._valueChanged.connect(self.adjustValueChanged)
self.setRange(0, self._normalMaximum)
def value(self):
return super().value() + self._normalMaximum * self.cycle
def minimum(self):
return self._minimum
def maximum(self):
return self._maximum()
def dialMinimum(self):
return super().minimum()
def dialMaximum(self):
return super().maximum()
def adjustValueChanged(self, value):
if value < self._previousValue:
if (value < self.dialMaximum() * .3 and self._previousValue > self.dialMaximum() * .6 and
self.cycle + 1 < self.cycleCount):
self.cycle += 1
if self.cycle == self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
elif (value > self.dialMaximum() * .6 and self._previousValue < self.dialMaximum() * .3 and
self.cycle > 0):
self.cycle -= 1
if self.cycle == 0:
self.setMaximum(self._normalMaximum)
new = self.value()
if self._previousValue != new:
self._previousValue = value
self.valueChanged.emit(self.value())
def setValue(self, value):
value = max(self._minimum, min(self._maximum, value))
if value == self.value():
return
block = self.blockSignals(True)
self.cycle, value = divmod(value, self._normalMaximum)
if self.dialMaximum() == self._normalMaximum and self.cycle == self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
elif self.dialMaximum() == self._lastMaximum and self.cycle < self.cycleCount - 1:
self.setMaximum(self._normalMaximum)
super().setValue(value)
self.blockSignals(block)
self._previousValue = self.value()
self.valueChanged.emit(self._previousValue)
def keyPressEvent(self, event):
key = event.key()
if key in (Qt.Key_Right, Qt.Key_Up):
step = self.singleStep()
elif key in (Qt.Key_Left, Qt.Key_Down):
step = -self.singleStep()
elif key == Qt.Key_PageUp:
step = self.pageStep()
elif key == Qt.Key_PageDown:
step = -self.pageStep()
elif key in (Qt.Key_Home, Qt.Key_End):
if key == Qt.Key_Home or self.invertedControls():
if super().value() > 0:
self.cycle = 0
block = self.blockSignals(True)
super().setValue(0)
self.blockSignals(block)
self.valueChanged.emit(self.value())
else:
if self.cycle != self.cycleCount - 1:
self.setMaximum(self._lastMaximum)
self.cycle = self.cycleCount - 1
if super().value() != self._lastMaximum:
block = self.blockSignals(True)
super().setValue(self._lastMaximum)
self.blockSignals(block)
self.valueChanged.emit(self.value())
return
else:
super().keyPressEvent(event)
return
if self.invertedControls():
step *= -1
current = self.value()
new = max(self._minimum, min(self._maximum, current + step))
if current != new:
super().setValue(super().value() + (new - current))
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QGridLayout()
self.setLayout(layout)
self.dial = SpecialDial()
self.dial.valueChanged.connect(self.sliderMoved)
self.text=QLabel()
layout.addWidget(self.dial)
layout.addWidget(self.text)
def sliderMoved(self):
self.text.setText(str(self.dial.value()))
强烈建议您抽出时间:
- 考虑这是否真的是您想要的,因为如前所述,从用户体验的角度来看,这种控制可能非常棘手;
- 仔细阅读代码,理解其逻辑;