如何在 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 设置最小和最大浮点值?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过环境变量设置 Java 的最小和最大堆大小?

如何将 QSizeGrip 放在 QDial 的右下角?

如何在 PyQt5 Python 中的 QDial 上显示标记

如何在HTML / CSS中为页面上的所有内容设置边框/边距?

使用样式表自定义 QDial

在 qtableview pyqt4 python 中使用图像委托?