如何在 PyQt4 中为 QDial 设置最小和最大浮点值?
Posted
技术标签:
【中文标题】如何在 PyQt4 中为 QDial 设置最小和最大浮点值?【英文标题】:How to set min and max float values for QDial in PyQt4? 【发布时间】:2019-08-26 14:41:06 【问题描述】:在一个简单的 GUI 项目上工作,它有 QDial 和 LCD Widget。我发现了如何设置 QDial 的最小值和最大值,即从 0 到 99,但我想设置从 0.02 到 0.2 的范围。 我该怎么做?
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(459, 366)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName(_fromUtf8("centralWidget"))
self.dial = QtGui.QDial(self.centralWidget)
self.dial.setGeometry(QtCore.QRect(60, 100, 111, 101))
self.dial.setNotchesVisible(True)
self.dial.setObjectName(_fromUtf8("dial"))
self.lcdNumber = QtGui.QLCDNumber(self.centralWidget)
self.lcdNumber.setGeometry(QtCore.QRect(200, 120, 101, 61))
self.lcdNumber.setObjectName(_fromUtf8("lcdNumber"))
MainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtGui.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 459, 25))
self.menuBar.setObjectName(_fromUtf8("menuBar"))
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QtGui.QToolBar(MainWindow)
self.mainToolBar.setObjectName(_fromUtf8("mainToolBar"))
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
self.statusBar = QtGui.QStatusBar(MainWindow)
self.statusBar.setObjectName(_fromUtf8("statusBar"))
MainWindow.setStatusBar(self.statusBar)
self.retranslateUi(MainWindow)
QtCore.QObject.connect(self.dial, QtCore.SIGNAL(_fromUtf8("valueChanged(int)")), self.lcdNumber.display)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
输出必须是这样的,QDial 值应该在 0.02 到 0.2 之间
【问题讨论】:
【参考方案1】:QDial 与任何其他 QAbstractSlider 后代一样,仅支持整数值。 至少有三种解决方案可以实现浮点数支持,具体取决于您的需要。 这个想法是,您使用基于整数的表盘值作为索引,通过使用数学运算或基于索引的函数来获得实际的浮动值。
定值列表
创建所有可能值的列表,也可以在它们之间使用不均匀的“间隙”(例如0.02, 0.08, 0.16, 2.0
),将最大值设置为len(valueList) - 1
,然后使用valueChanged
信号参数的值作为该列表的索引。
优点:“单向”刻度盘的实现非常容易(刻度盘只能更新值,不能反过来);缺点:如果提供的值不在列表中,则设置表盘的“值”(参见下面的代码); “tick”位置不考虑实际值,并且在使用“不均匀”步骤时可能在视觉上不一致:如果值为0.0, 0.01, 0.02, 0.1, 0.2
,则中间位置将是0.02
,而不是更直观 /em>0.1
;
基于步长值
您必须设置一个步,该步长将用作最小值和最大值之间的范围(例如0.02
),从而导致值例如为0.02, 0.04, 0.06, [...], 0.18, 0.2
。
在底部的示例中,我假设该步骤允许刻度盘恰好达到最大值;如果没有,您必须在最后添加另一个步骤(将QDial.maximum()
增加一个)。
实际浮点值的计算方法是将当前整数值乘以步长,然后将结果与最小浮点值相加。
这种方法类似于this answer,它正确实现了所有方法,但是如果在QDials上使用,则有基于十进制单位的固定步长的限制(意味着你不能有0.02, 0.04, 0.06
,而只有0.02, 0.03, 0.04, 0.05, etc
),意思是NB:虽然转换为字符串的值通常是四舍五入的,但请记住浮点值具有limitations,这意味着在这个特定示例中,0.2 实际上是(大约)0.20000000000000004。
优点:该步骤允许简化数据输入,减少数据范围;缺点:无论该步骤有多少位,实际值可能会由于浮动限制而“不精确”;范围(最大值 - 最小值)必须是步长值的倍数;
基于步数(精度)
在这种情况下,您可以设置在最小值和最大值之间需要多少步,从而允许您指定其值的精度。结果值取自当前刻度盘值与刻度盘最大值(步数)之间的比值,乘以最小值和最大值之间的范围,然后像以前一样添加到最小值。
优点:精度更高;缺点:即使不是不可能,也很难获得小数为零或很少的值;
SetValue()
问题
如果您需要设置刻度盘的值,您总会遇到一些问题,主要是由于上述浮动限制,但不仅如此。 此外,必须根据不同的实现应用不同的策略,最难的是“固定值列表”的情况。
问题在于实际的 QDial 索引是基于整数的,这意味着如果您想设置 0.9 的“值”,根据实现,它可能被解释为 0 或 1。
在下面的示例中,我还使用 QDoubleSpinBox 来显示当前值,并且能够在用户更改旋转框值时设置回拨值;虽然基于步进的表盘几乎没有缺陷,但固定的列表案例清楚地表明了这种限制:如果您尝试在第一个旋转框上缓慢移动鼠标滚轮,您会发现表盘并不总是正确更新,尤其是在设置时小于当前值的值。
from bisect import bisect_left
from PyQt4 import QtCore, QtGui
class FixedValuesDial(QtGui.QDial):
floatValueChanged = QtCore.pyqtSignal(float)
def __init__(self, valueList):
super(FixedValuesDial, self).__init__()
self.valueList = valueList
self.setMaximum(len(valueList) - 1)
self.valueChanged.connect(self.computeFloat)
self.currentFloatValue = self.value()
def computeFloat(self, value):
self.currentFloatValue = self.valueList[value]
self.floatValueChanged.emit(self.currentFloatValue)
def setFloatValue(self, value):
try:
# set the index, assuming the value is in the valueList
self.setValue(self.valueList.index(value))
except:
# find the most closest index, based on the value
index = bisect_left(self.valueList, value)
if 0 < index < len(self.valueList):
before = self.valueList[index - 1]
after = self.valueList[index]
# bisect_left returns the position where the value would be
# added, assuming valueList is sorted; the resulting index is the
# one preceding the one with a value greater or equal to the
# provided value, so if the difference between the next value and
# the current is greater than the difference between the previous
# and the current, the index is closest to the previous
if after - value > value - before:
index -= 1
# note that the value -the new index- is actually updated only *if*
# the new index is different from the current, otherwise there will
# no valueChanged signal emission
self.setValue(index)
class StepDial(QtGui.QDial):
floatValueChanged = QtCore.pyqtSignal(float)
def __init__(self, minimum, maximum, step):
super(StepDial, self).__init__()
self.scaleValues = []
self.minimumFloat = minimum
self.step = step
self.setMaximum((maximum - minimum) // step)
self.valueChanged.connect(self.computeFloat)
def computeFloat(self, value):
self.floatValueChanged.emit(value * self.step + self.minimumFloat)
def setFloatValue(self, value):
# compute the index by subtracting minimum from the value, divided by the
# step value, then round provide a *rounded* value, otherwise values like
# 0.9999[...] will be interpreted as 0
index = (value - self.minimumFloat) / self.step
self.setValue(int(round(index)))
class FloatDial(QtGui.QDial):
floatValueChanged = QtCore.pyqtSignal(float)
def __init__(self, minimum, maximum, stepCount=1001):
super(FloatDial, self).__init__()
self.minimumFloat = minimum
self.maximumFloat = maximum
self.floatRange = maximum - minimum
# since QDial (as all QAbstractSlider subclasses), includes its maximum()
# in its value range; to get the expected step count, we subtract 1 from
# it: maximum = minimum + (stepCount - 1)
# also, since the default minimum() == 0, the maximum is stepCount - 1.
# Note that QDial is usually symmetrical, using 240 degrees
# counterclockwise (in cartesian coordinates, as in 3-o'clock) for the
# minimum, and -60 degrees for its maximum; with this implementation we
# assume all of that, and get a correct "middle" value, but this requires
# an *odd* stepCount number so that the odd "middle" index value is
# actually what it should be.
self.stepCount = stepCount
self.setMaximum(stepCount - 1)
self.valueChanged.connect(self.computeFloat)
def computeFloat(self, value):
ratio = float(value) / self.maximum()
self.floatValueChanged.emit(self.floatRange * ratio + self.minimumFloat)
def setFloatValue(self, value):
# compute the "step", based on the stepCount then use the same concept
# as in the StepDial.setFloatValue function
step = (self.maximumFloat - self.minimumFloat) / self.stepCount
index = (value - self.minimumFloat) // step
self.setValue(int(round(index)))
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QtGui.QGridLayout()
self.setLayout(layout)
layout.addWidget(QtGui.QLabel('List based'), 0, 0)
self.listDial = FixedValuesDial([0.02, 0.03, 0.05, 0.08, 0.1, 0.15, 0.2])
layout.addWidget(self.listDial, 1, 0)
self.listDial.floatValueChanged.connect(self.updateListValue)
self.listSpin = QtGui.QDoubleSpinBox(
minimum=0.02, maximum=0.2, singleStep=0.01)
layout.addWidget(self.listSpin, 2, 0)
self.listSpin.valueChanged.connect(self.listDial.setFloatValue)
layout.addWidget(QtGui.QLabel('Step precision (0.02)'), 0, 1)
self.stepDial = StepDial(0.02, 0.2, 0.02)
layout.addWidget(self.stepDial, 1, 1)
self.stepDial.floatValueChanged.connect(self.updateStepDisplay)
self.stepSpin = QtGui.QDoubleSpinBox(
minimum=0.02, maximum=0.2, singleStep=0.02)
layout.addWidget(self.stepSpin, 2, 1)
self.stepSpin.valueChanged.connect(self.stepDial.setFloatValue)
layout.addWidget(QtGui.QLabel('Step count (21 steps)'), 0, 2)
# see the FloatDial implementation above to understand the reason of odd
# numbered steps
self.floatDial = FloatDial(0.02, 0.2, 21)
layout.addWidget(self.floatDial, 1, 2)
self.floatDial.floatValueChanged.connect(self.updateFloatValue)
self.floatSpin = QtGui.QDoubleSpinBox(
minimum=0.02, maximum=0.2, decimals=5, singleStep=0.001)
layout.addWidget(self.floatSpin, 2, 2)
self.floatSpin.valueChanged.connect(self.floatDial.setFloatValue)
def updateStepDisplay(self, value):
# prevent the spinbox sending valuechanged while updating its value,
# otherwise you might face an infinite recursion caused by the spinbox
# trying to update the dial, which will correct the value and possibly
# send the floatValueChanged back again to it; obviously, this applies
# to the following slots
self.stepSpin.blockSignals(True)
self.stepSpin.setValue(value)
self.stepSpin.blockSignals(False)
def updateFloatValue(self, value):
self.floatSpin.blockSignals(True)
self.floatSpin.setValue(value)
self.floatSpin.blockSignals(False)
def updateListValue(self, value):
self.listSpin.blockSignals(True)
self.listSpin.setValue(value)
self.listSpin.blockSignals(False)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
【讨论】:
以上是关于如何在 PyQt4 中为 QDial 设置最小和最大浮点值?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 PyQt5 Python 中的 QDial 上显示标记