I am working on a small widget. It has a dial and a slider and both modify the same number, although I want the slider to have a higher changing rate than the dial. Think of it as the dial being the fine tuning. For example, the slider changes in units of 0.1, but the dial changes in 0.01. Additionally, I want the dial to increase or decrease the value of the number as long as I turn it in one direction. So, if I turn 5 complete revolutions the number will increase in 50 units (considering there are 100 steps in the dial 'axis').
I successfully implemented most of this, but there is a problem and I do not know how to solve it.
The dial works as I want it to. It turns without bounds. It also changes with a slower rate than the slider. The problem is that this rate only works if I turn clockwise (increasing the number).
The dial increases in 0.01 and the slider in 0.1. I can go from 1 to 1.1 (1.01, 1.02 ...) by actioning the dial and the slider will only move once, however if I bo back with the dial from 1.1, it will change directly to 1 and then 0.9 (skipping the numbers in between). In other words the rate of the dial will be different as long as I turn clockwise.
This is the code
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_())
I added prints to better follow the code in the terminal. I found that the problem stems from the line self.slider.valueChanged.connect(self.slider_value_changed)
. The signal only sends integers (because the position of the slider works in integers).
Then I tried solving this by adding an extra attribute that contains the position or number of the slider, but as a float. When I try to do this the dial works fine in both directions, however the slider will not move if I press it.
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')
This is what I tried. I added the slider_number as a property. I have been trying to solve this the whole day long. I would much appreciate another brain to help me.
EDIT (Possible Solution):
I managed to solve the issue I had by disconnecting slider.valueChanged
from slider_value_changed
when the dial was actioned (dial_value_changed
) and connecting it again once I exited the function. This worked because the problem arises when slider.valueChanged
is called twice, one when if the number changes in 0.1 units by moving the dial, calling set_number()
which in turn calls slider.valueChanged
again.
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)