我正在开发一个小工具.它有一个表盘和一个滑块,都可以修改相同的数字,尽管我希望滑块的更改率比表盘更高.可以把它看作是微调的表盘.例如,滑块以0.1为单位更改,但刻度盘以0.01为单位更改. 此外,只要我朝一个方向转动,我就想让刻度盘增加或减少数字的值.所以,如果我转完5圈,这个数字将增加50个单位(考虑到刻度盘‘轴’上有100个台阶).

我成功地实现了大部分,但有一个问题,我不知道如何解决它.

刻度盘按我希望的那样工作.它不受限制地转动.它的变化速度也比滑块慢.问题是,只有当我顺时针旋转(增加数字)时,这个速度才有效.

刻度盘增加到0.01,滑块增加到0.1.我可以通过操作刻度盘从1转到1.1(1.01,1.02...),滑块只会移动一次,但是如果我从1.1转回刻度盘,它将直接变为1,然后是0.9(跳过中间的数字).换句话说,只要我顺时针转,刻度盘的速度就会不同.

这就是代码

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QDial, QSlider
from PyQt5.QtCore import Qt


class DialWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self._label_number = 0.000
        self.label = QLabel(str(self._label_number), self)
        self.label.setAlignment(Qt.AlignCenter)

        self.dial = QDial(self)
        self.dial.setNotchesVisible(True)
        self.dial.setWrapping(True)
        self.dial.setMinimum(0)
        self.dial.setMaximum(100)  # 3600 for full revolutions (0-3600 = 0-360 degrees)

        self.dial.valueChanged.connect(self.dial_value_changed)

        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setMinimum(0)
        self.slider.setMaximum(80)
        self.slider.valueChanged.connect(self.slider_value_changed)
        self._slider_value = self.slider.value()

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.dial)
        layout.addWidget(self.slider)

        self.dial_value = self.dial.value()
        self.setLayout(layout)

    @property
    def label_number(self):
        return self._label_number

    @label_number.setter
    def label_number(self, number):
        print(f'    Inside label_number.setter')
        if number < 0 or number > 8:
            pass
        else:
            print(f'    Changing label_number {self._label_number} --> {number}')
            self._label_number = number
            #print(f'    Changing label text {self.label.text()} --> {str(round(number, 4))}')
            self.label.setText(str(round(number, 4)))
            print(f'    Changing slider value: {self.slider.value()} --> {number * 10.000} ')
            print(f'self.slider.setValue({number * 10.000})')
            self.slider_number = number * 10.000
            self.slider.setValue(self.slider_number)
        print(f'    Exiting label_number.setter')

    def dial_value_changed(self):
        print(f'Inside dial_value_changed')
        # Get the current value of the dial
        dial_delta = self.dial.value() - self.dial_value
        print(f'Delta number of the dial: {dial_delta}')
        if dial_delta == 1:
            print(f'dn = {1/100}')
            new_number = self.label_number + 1/100
            self.label_number = new_number
        elif dial_delta == -1:
            print(f'dn = {1/100}')
            new_number = self.label_number - 1/100
            self.label_number = new_number
        elif dial_delta == -100:
            print(f'dn = {1/100}')
            new_number = self.label_number + 1/100
            self.label_number = new_number
        elif dial_delta == 99:
            print(f'dn = {1/100}')
            new_number = self.label_number - 1/100
            self.label_number = new_number
        #print(f'Setting self.dial_value to {self.dial.value()}')
        self.dial_value = self.dial.value()
        print(f'Exiting dial_value_changed')
        print()

    def slider_value_changed(self, value):
        print(f'        Inside slider_value_changed')
        print(f'        {self.slider.value()}')
        print(f'        Value sent {value}')
        #print(f'        Changing self.label_number {self.label_number} --> {value / 10.0}')
        self.label_number = value / 10.000
        
        print(f'        Exiting slider_value_changed')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DialWidget()
    window.setWindowTitle('Dial Widget')
    window.show()
    sys.exit(app.exec_())

我添加了 fingerprint ,以便更好地跟踪航站楼的代码.我发现问题出在self.slider.valueChanged.connect(self.slider_value_changed)线上.该信号仅发送整数(因为滑块的位置以整数为单位工作).

然后,我try 通过添加一个额外的属性来解决这个问题,该属性包含滑块的位置或数量,但它是一个浮点.当我try 这样做时,刻度盘在两个方向上都工作得很好,但是如果我按下它,滑块就不会移动.

class DialWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self._label_number = 0.000
        self.label = QLabel(str(self._label_number), self)
        self.label.setAlignment(Qt.AlignCenter)

        self.dial = QDial(self)
        self.dial.setNotchesVisible(True)
        self.dial.setWrapping(True)
        self.dial.setMinimum(0)
        self.dial.setMaximum(100)  # 3600 for full revolutions (0-3600 = 0-360 degrees)

        self.dial.valueChanged.connect(self.dial_value_changed)

        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setMinimum(0)
        self.slider.setMaximum(80)
        self.slider.valueChanged.connect(self.slider_value_changed)
        self._slider_number = self.slider.value()

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.dial)
        layout.addWidget(self.slider)

        self.dial_value = self.dial.value()
        self.setLayout(layout)

    @property
    def label_number(self):
        return self._label_number

    @label_number.setter
    def label_number(self, number):
        print(f'    Inside label_number.setter')
        if number < 0 or number > 8:
            pass
        else:
            print(f'    Changing label_number {self._label_number} --> {number}')
            self._label_number = number
            #print(f'    Changing label text {self.label.text()} --> {str(round(number, 4))}')
            self.label.setText(str(round(number, 4)))
            print(f'    Changing slider value: {self.slider.value()} --> {number * 10.000} ')
            print(f'self.slider.setValue({number * 10.000})')
            self.slider_number = number * 10.000
            self.slider.setValue(self.slider_number)
            #self.slider.setValue(number * 10.000)
        print(f'    Exiting label_number.setter')

    @property
    def slider_number(self):
        return self._slider_number

    @slider_number.setter
    def slider_number(self, number):
        self._slider_number = number

    def dial_value_changed(self):
        print(f'Inside dial_value_changed')
        # Get the current value of the dial
        dial_delta = self.dial.value() - self.dial_value
        print(f'Delta number of the dial: {dial_delta}')
        if dial_delta == 1:
            print(f'dn = {1/100}')
            new_number = self.label_number + 1/100
            self.label_number = new_number
        elif dial_delta == -1:
            print(f'dn = {1/100}')
            new_number = self.label_number - 1/100
            self.label_number = new_number
        elif dial_delta == -100:
            print(f'dn = {1/100}')
            new_number = self.label_number + 1/100
            self.label_number = new_number
        elif dial_delta == 99:
            print(f'dn = {1/100}')
            new_number = self.label_number - 1/100
            self.label_number = new_number
        #print(f'Setting self.dial_value to {self.dial.value()}')
        self.dial_value = self.dial.value()
        print(f'Exiting dial_value_changed')
        print()

    def slider_value_changed(self, value):
        print(f'        Inside slider_value_changed')
        print(f'        {self.slider.value()}')
        print(f'        Value sent {value}')
        #print(f'        Changing self.label_number {self.label_number} --> {value / 10.0}')
        value = self.slider_number
        self.label_number = value / 10.000
        
        #self.slider.setValue(self.slider_value)
        print(f'        Exiting slider_value_changed')

这就是我试过的.我添加了SLIDER_NUMBER作为属性. 我一整天都在试图解决这个问题.如果另一个大脑能帮助我,我将不胜感激.

EDIT (Possible Solution):

我设法解决了我的问题,当拨盘被操作(dial_value_changed)时,我断开了slider.valueChangedslider_value_changed的连接,并在我退出功能后再次连接.这是因为当slider.valueChanged被呼叫两次时,问题就出现了,第一次是如果数字通过移动刻度盘以0.1个单位改变,则呼叫set_number(),而set_number()又再次呼叫slider.valueChanged.

    def dial_value_changed(self):
        dial_delta = self.dial.value() - self.dial_value
        self.slider.valueChanged.disconnect()
        if dial_delta == 1:
            new_number = self.number + 1/10000
            self.set_number(new_number)
        elif dial_delta == -1:
            new_number = self.number - 1/10000
            self.set_number(new_number)
        elif dial_delta == -100:
            new_number = self.number + 1/10000
            self.set_number(new_number)
        elif dial_delta == 99:
            new_number = self.number - 1/10000
            self.set_number(new_number)
        self.dial_value = self.dial.value()
        self.slider.valueChanged.connect(self.slider_value_changed)

推荐答案

QDial有与用户体验相关的重要问题,最重要的是,当使用鼠标时,它会将二维矢量映射到一维(线性)空间.

当使用wrapping时,这个问题更加明显(也是问题),因为没有直接的方法来知道鼠标的"移动"是为了顺时针旋转还是逆时针旋转.请记住,鼠标移动事件不是连续的:它们只是apparent次移动,但实际上鼠标"跳"到了illusion次移动的点.

这意味着没有直接的方法知道滑块值是否应该保持不变,或者当旋转通过表盘的最小值和最大值之间的边缘时,它最终应该加/减.

我们不能仅仅依靠以前的值和当前值之间的差异;更小的新值可能并不总是意味着刻度盘已逆时针移动:例如,如果值是50和40,那么假设是有效的,但如果只是将刻度盘从99移动到2会怎么样?

一种可能的解决方案是使用自定义信号子类化QDial,并根据触发的操作的前一个值and发出它.

多亏了actionTriggered的信号,这是可能的:

当发出信号时,已经根据操作调整了liderPosition,但是值还没有被传播(这意味着valueChanged()信号还没有发出),视觉显示也没有更新.

诀窍是将该信号连接到一个函数,该函数跟踪当前滑块位置(在值实际更改之前)和触发它的操作,然后连接到标准valueChanged信号并使用可以根据先前的值和相关操作更改的值发出定制信号.

新值将按整个刻度盘范围递增或减go ,无论何时边缘被"穿过".

有些动作知道方向,所以很容易知道结果:如果之前的值是SliderSingleStepAdd,新值是0,动作是SliderSingleStepAdd,那么我们知道应该将滑块增加1,依此类推.

鼠标移动只需要通过前一个值获得方向,所以我们必须假设通过获得值之间的差异,但使用全范围的模数.然后我们必须做一个假设:如果差小于范围的一半,这意味着Angular 是顺时针的.

diff = (newValue - oldValue) % 100
if diff < 50: # clockwise
    if newValue < oldValue:
        # increment the slider by 1
        newValue += 100
elif newValue > oldValue: # counterclockwise is implied
    # decrement by 1
    newValue -= 100
# emit signal with newValue

上述结果将得出以下结果:

  • 对于oldValue = 90newValue = 10,diff将是20,因此旋转是顺时针方向的,由于newValue小于oldValue,所以我们将其递增oldValue = 90,因此滑块将增加1;
  • 如果值分别是1090,diff就是80,所以旋转是逆时针方向的,因为newValue大于oldValue,所以我们将它减go 10,这样滑块就会减少1;

请注意,由于前面提到的问题,当"旋转"没有"越过"边缘时,这仍然可能产生意外或不可靠的结果:例如,如果当前拨号值是25,并且在75之后单击,则实际上可能会导致逆时针旋转.

然后,在主窗口小部件中,我们连接自定义信号:如果值小于0,则我们知道必须从滑块值中减go 1,如果大于or等于or,则加1.

请注意,为了避免递归并提供适当的范围边界,我们还应该阻止滑块的valueChanged信号传播,以便每当最终值达到最小值或最大值时,最终可以将刻度盘调整到0.

最后,由于换行可能会导致鼠标/键盘范围在最大值和最小值之间非常窄(并且不可靠),因此最好防止刻度盘实际达到最大值:每当位置等于actionTriggered时,我们就会根据之前的位置或动作调整它(如上所述),因此如果它大于50,它将变成0,否则它将是99.这是在连接到actionTriggered的函数中完成的,因为在这一点上该值尚未调整.

class Dial(QDial):
    AddActions = (
        QAbstractSlider.SliderSingleStepAdd, 
        QAbstractSlider.SliderPageStepAdd, 
    )
    SubActions = (
        QAbstractSlider.SliderSingleStepSub, 
        QAbstractSlider.SliderPageStepSub, 
    )
    lastAction = QAbstractSlider.SliderNoAction

    customValueChanged = pyqtSignal(int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setNotchesVisible(True)
        self.setWrapping(True)
        self.setRange(0, 100)
        self.lastValue = self.value()

        self.actionTriggered.connect(self.checkActionTriggered)
        self.valueChanged.connect(self.emitValueChanged)

    def checkActionTriggered(self, action):
        self.lastAction = action
        self.lastValue = self.value()
        # prevent the dial to get to 100
        if self.sliderPosition() == 100:
            if action in self.AddActions:
                self.setSliderPosition(0)
            elif action in self.SubActions:
                self.setSliderPosition(99)
            elif action == self.SliderMove:
                if self.lastValue > 50:
                    self.setSliderPosition(0)
                else:
                    self.setSliderPosition(99)

    def emitValueChanged(self, value):
        if self.lastAction in self.AddActions:
            if value < self.lastValue:
                value += 100
        elif self.lastAction in self.SubActions:
            if value > self.lastValue:
                value -= 100
        elif self.lastAction == self.SliderMove:
            if self.lastValue == self.minimum() and value > 50:
                value -= 100
            else:
                diff = (value - self.lastValue) % 100
                if diff <= 50:
                    if value < self.lastValue:
                        value += 100
                elif value > self.lastValue:
                    value -= 100
        self.customValueChanged.emit(value)


class DialWidget(QWidget):
    def __init__(self, minimum=0, maximum=80):
        super().__init__()
        self.label = QLabel()
        self.label.setAlignment(Qt.AlignCenter)

        self.dial = Dial()

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setMinimum(minimum)
        self.slider.setMaximum(maximum)

        layout = QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.dial)
        layout.addWidget(self.slider)

        self.dial.customValueChanged.connect(self.dial_value_changed)
        self.slider.valueChanged.connect(self.slider_value_changed)

        self.updateLabel()

    @property
    def value(self):
        return self.slider.value() + self.dial.value() * .01

    @value.setter
    def value(self, value):
        value = max(self.slider.minimum(), min(value, self.slider.maximum()))
        if self.value != value:
            with QSignalBlocker(self.slider):
                intVal, decimal = divmod(value, 1.)
                self.slider.setValue(int(intVal))
                self.dial.setValue(int(decimal * 100))
            self.updateLabel()

    def dial_value_changed(self, value):
        if value < 0:
            if self.slider.value() > self.slider.minimum():
                with QSignalBlocker(self.slider):
                    self.slider.setValue(self.slider.value() - 1)
            else:
                self.dial.setValue(0)
        elif value >= 100:
            with QSignalBlocker(self.slider):
                self.slider.setValue(self.slider.value() + 1)
            if self.slider.value() == self.slider.maximum():
                self.dial.setValue(0)
        elif self.slider.value() == self.slider.maximum():
            self.dial.setValue(0)

        self.updateLabel()

    def slider_value_changed(self, value):
        if value == self.slider.minimum() or value == self.slider.maximum():
            self.dial.setValue(0)

        self.updateLabel()

    def updateLabel(self):
        self.label.setText(str(round(self.value, 2)))

Python-3.x相关问答推荐

Pandas groupby基于索引的连续列值相等

Pandas 数据帧断言等同于NaN

在Python中从列创建新行

如何计算累积几何平均数?

Django 模型类方法使用错误的 `self`

如何查找以开头并替换的字符串

我想使用命令提示符安装 cv2

过滤并获取数据框中条件之间的行

python2和python3中的列表生成器

使用条件参数为 super() 调用 __init__

SqlAlchemy - 从 oracle db 中检索长文本

使用 pandas 数据帧映射到中转( node )点的跨容量请求

python 3中的SQLAlchemy ER图

在气流中运行 DAG 时出现处理信号:ttou消息

Python 3.5 中编码 utf-8 和 utf8 的区别

两个字符串之间的正则表达式匹配?

django.core.exceptions.ImproperlyConfigured

如何在多核上运行 Keras?

如果一个失败,如何取消收集中的所有剩余任务?

Python中的多行日志(log)记录