首先。Qt 的表盘一团糟。它们是不错的小部件,但它们主要是为简单的用例而开发的。
如果您需要“特殊”行为,则需要重写一些重要方法。这个例子显然需要paintEvent
覆盖,但最重要的部分是鼠标事件和滚轮事件。跟踪将单步和页步设置为值范围所需的键盘事件,并“覆盖”原始valueChanged
信号以确保发出的值范围始终在 -1 和 1 之间。显然,您可以通过添加专用函数来更改这些值。
从理论上讲,QDial 小部件应始终使用 240|-60 度角,但将来可能会改变,因此我决定启用环绕以将度数保持为“内部”值。请记住,您可能还需要提供自己的value()
属性实现。
from PyQt4 import QtCore, QtGui
from math import sin, cos, atan2, degrees, radians
import sys
class Dial(QtGui.QDial):
MinValue, MidValue, MaxValue = -1, 0, 1
__valueChanged = QtCore.pyqtSignal(int)
def __init__(self, valueRange=120):
QtGui.QDial.__init__(self)
self.setWrapping(True)
self.setRange(0, 359)
self.valueChanged.connect(self.emitSanitizedValue)
self.valueChanged = self.__valueChanged
self.valueRange = valueRange
self.__midValue = valueRange / 2
self.setPageStep(valueRange)
self.setSingleStep(valueRange)
QtGui.QDial.setValue(self, 180)
self.oldValue = None
# uncomment this if you want to emit the changed value only when releasing the slider
# self.setTracking(False)
self.notchSize = 5
self.notchPen = QtGui.QPen(QtCore.Qt.black, 2)
self.actionTriggered.connect(self.checkAction)
def emitSanitizedValue(self, value):
if value < 180:
self.valueChanged.emit(self.MinValue)
elif value > 180:
self.valueChanged.emit(self.MaxValue)
else:
self.valueChanged.emit(self.MidValue)
def checkAction(self, action):
value = self.sliderPosition()
if action in (self.SliderSingleStepAdd, self.SliderPageStepAdd) and value < 180:
value = 180 + self.valueRange
elif action in (self.SliderSingleStepSub, self.SliderPageStepSub) and value > 180:
value = 180 - self.valueRange
elif value < 180:
value = 180 - self.valueRange
elif value > 180:
value = 180 + self.valueRange
else:
value = 180
self.setSliderPosition(value)
def valueFromPosition(self, pos):
y = self.height() / 2. - pos.y()
x = pos.x() - self.width() / 2.
angle = degrees(atan2(y, x))
if angle > 90 + self.__midValue or angle < -90:
value = self.MinValue
final = 180 - self.valueRange
elif angle >= 90 - self.__midValue:
value = self.MidValue
final = 180
else:
value = self.MaxValue
final = 180 + self.valueRange
self.blockSignals(True)
QtGui.QDial.setValue(self, final)
self.blockSignals(False)
return value
def value(self):
rawValue = QtGui.QDial.value(self)
if rawValue < 180:
return self.MinValue
elif rawValue > 180:
return self.MaxValue
return self.MidValue
def setValue(self, value):
if value < 0:
QtGui.QDial.setValue(self, 180 - self.valueRange)
elif value > 0:
QtGui.QDial.setValue(self, 180 + self.valueRange)
else:
QtGui.QDial.setValue(self, 180)
def mousePressEvent(self, event):
self.oldValue = self.value()
value = self.valueFromPosition(event.pos())
if self.hasTracking() and self.oldValue != value:
self.oldValue = value
self.valueChanged.emit(value)
def mouseMoveEvent(self, event):
value = self.valueFromPosition(event.pos())
if self.hasTracking() and self.oldValue != value:
self.oldValue = value
self.valueChanged.emit(value)
def mouseReleaseEvent(self, event):
value = self.valueFromPosition(event.pos())
if self.oldValue != value:
self.valueChanged.emit(value)
def wheelEvent(self, event):
delta = event.delta()
oldValue = QtGui.QDial.value(self)
if oldValue < 180:
if delta < 0:
outValue = self.MinValue
value = 180 - self.valueRange
else:
outValue = self.MidValue
value = 180
elif oldValue == 180:
if delta < 0:
outValue = self.MinValue
value = 180 - self.valueRange
else:
outValue = self.MaxValue
value = 180 + self.valueRange
else:
if delta < 0:
outValue = self.MidValue
value = 180
else:
outValue = self.MaxValue
value = 180 + self.valueRange
self.blockSignals(True)
QtGui.QDial.setValue(self, value)
self.blockSignals(False)
if oldValue != value:
self.valueChanged.emit(outValue)
def paintEvent(self, event):
QtGui.QDial.paintEvent(self, event)
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
qp.translate(.5, .5)
rad = radians(self.valueRange)
qp.setPen(self.notchPen)
c = -cos(rad)
s = sin(rad)
# use minimal size to ensure that the circle used for notches
# is always adapted to the actual dial size if the widget has
# width/height ratio very different from 1.0
maxSize = min(self.width() / 2, self.height() / 2)
minSize = maxSize - self.notchSize
center = self.rect().center()
qp.drawLine(center.x(), center.y() -minSize, center.x(), center.y() - maxSize)
qp.drawLine(center.x() + s * minSize, center.y() + c * minSize, center.x() + s * maxSize, center.y() + c * maxSize)
qp.drawLine(center.x() - s * minSize, center.y() + c * minSize, center.x() - s * maxSize, center.y() + c * maxSize)
class Test(QtGui.QWidget):
def __init__(self, *sizes):
QtGui.QWidget.__init__(self)
layout = QtGui.QGridLayout()
self.setLayout(layout)
if not sizes:
sizes = 70, 90, 120
self.dials = []
for col, size in enumerate(sizes):
label = QtGui.QLabel(str(size))
label.setAlignment(QtCore.Qt.AlignCenter)
dial = Dial(size)
self.dials.append(dial)
dial.valueChanged.connect(lambda value, dial=col: self.dialChanged(dial, value))
layout.addWidget(label, 0, col)
layout.addWidget(dial, 1, col)
def dialChanged(self, dial, value):
print('dial {} changed to {}'.format(dial, value))
def setDialValue(self, dial, value):
self.dials[dial].setValue(value)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialexample = Test(70, 90, 120)
# Change values here
dialexample.setDialValue(1, 1)
dialexample.show()
sys.exit(app.exec_())
编辑:我更新了代码以实现键盘导航并避免不必要的多信号发射。