将 horizontal/vertical 滑块转换为单个按钮
Convert horizontal/vertical slider to a single button
我一直在寻找一种方法将水平滑块 + 垂直滑块转换为框区域中的单个按钮。你知道或认为 Qt 有能力做到这一点吗?
这张图就是我想要的
如果使用鼠标事件,这种小部件很容易创建:
- mousePressEvent: 如果鼠标在光标上方,则光标可以移动
- mouseReleaseEvent: 禁用光标
- mouseMoveEvent:如果启用了移动,则移动光标
然后,使用paintEvent
在其位置显示光标。
要定义光标的值,您必须将以像素为单位的位置转换为基于轴边界的值。
H 值介于 -10 和 20 之间且 V 值介于 -10 和 10 之间的操纵杆的快速示例:
from PyQt4.QtGui import QWidget, QApplication, QPainter
from PyQt4 import QtCore
import sys
class Joystick(QWidget):
def __init__(self, parent=None):
super(Joystick, self).__init__(parent)
self.setFixedSize(100, 100)
self._minimumX = -10
self._maximumX = 20
self._minimumY = -10
self._maximumY = 10
self.cursorPosition = QtCore.QPointF(10, 90)
self.grabCursor = False
def valueX(self):
return (self.cursorPosition.x() - 10) * (self._maximumX - self._minimumX) / (self.width() - 20) + self._minimumX
def valueY(self):
return (self.cursorPosition.y() - 10) * (self._maximumY - self._minimumY) / (self.width() - 20) + self._minimumY
def paintEvent(self, event):
painter = QPainter(self)
painter.setBrush(QtCore.Qt.lightGray)
painter.setPen(QtCore.Qt.NoPen)
painter.drawRect(0, 0, self.width(), self.height())
painter.setBrush(QtCore.Qt.blue)
painter.drawEllipse(self.cursorRect())
def boundedCursor(self, position):
def bound(low, high, value):
return max(low, min(high, value))
x = bound(10, self.width() - 10, position.x())
y = bound(10, self.height() - 10, position.y())
return QtCore.QPointF(x, y)
def cursorRect(self):
return QtCore.QRectF(-5, -5, 10, 10).translated(self.cursorPosition)
def mousePressEvent(self, ev):
self.grabCursor = self.cursorRect().contains(ev.pos())
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, event):
self.grabCursor = False
self.update()
def mouseMoveEvent(self, event):
if self.grabCursor:
print("Moving")
self.cursorPosition = self.boundedCursor(event.pos())
self.update()
print(self.valueX(), self.valueY())
if __name__ == '__main__':
# Create main application window
app = QApplication([])
joystick = Joystick()
joystick.show()
sys.exit(app.exec_())
根据 Romha Korev 给出的 ,我做了一个变体。
此示例允许设置不同的参数并可以跟踪鼠标位置,无论您有什么数据和限制。它没有考虑任何固定的小部件 size/range(至少在我的测试中),但仍会根据小部件的大小进行调整。
class Bubble(QtWidgets.QWidget):
xChanged = QtCore.pyqtSignal(int)
yChanged = QtCore.pyqtSignal(int)
valuesChanged = QtCore.pyqtSignal(int, int)
moving = False
def __init__(self, minX=0, maxX=100, minY=0, maxY=100, cursorSize=10):
QtWidgets.QWidget.__init__(self)
self.margin = 10
self.setContentsMargins(0, 0, 0, 0)
self.minX = minX
self.minY = minY
self.maxX = maxX
self.maxY = maxY
self.extentX = abs(maxX - minX)
self.extentY = abs(maxY - minY)
self.setMinimumSize(self.extentX + self.margin * 2, self.extentY + self.margin * 2)
self.cursorSize = cursorSize
self.halfCursorSize = cursorSize * .5
self.cursorColor = QtGui.QColor(0, 174, 239)
self.movingCursorColor = QtGui.QColor(120, 124, 205)
self.cursorRect = QtCore.QRectF(-self.halfCursorSize, -self.halfCursorSize, cursorSize, cursorSize)
self.cursorX = self.minX + self.extentX * .5
self.cursorY = self.minY + self.extentY * .5
self.trackAnywhere = True
def visualPos(self):
# convert values to visual coordinates
return QtCore.QPointF(self.margin + self.halfCursorSize + ((self.cursorX - self.minX) / self.extentX) * (self.width() - self.margin * 2 - self.cursorSize),
self.margin + self.halfCursorSize + ((self.cursorY - self.minY) / self.extentY) * (self.height() - self.margin * 2 - self.cursorSize))
def realX(self, visualX):
visualExtent = self.width() - self.margin * 2 - self.cursorSize
realX = max(0, min(float(visualX - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentX
return self.minX + realX
def realY(self, visualY):
visualExtent = self.height() - self.margin * 2 - self.cursorSize
realY = max(0., min(float(visualY - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentY
return self.minY + realY
def realValues(self, pos):
return self.realX(pos.x()), self.realY(pos.y())
def mousePressEvent(self, event):
pos = event.pos()
visualPos = self.visualPos()
if self.trackAnywhere:
self.moving = True
if pos in self.cursorRect.translated(visualPos):
self.mouseDelta = visualPos - event.pos()
else:
self.mouseDelta = QtCore.QPoint()
elif pos in self.cursorRect.translated(visualPos):
self.moving = True
self.mouseDelta = visualPos - event.pos()
else:
self.moving = False
self.update()
def mouseMoveEvent(self, event):
if self.moving:
self.cursorX = self.realX(event.x() + self.mouseDelta.x())
self.cursorY = self.realY(event.y() + self.mouseDelta.y())
self.xChanged.emit(self.cursorX)
self.yChanged.emit(self.cursorY)
self.valuesChanged.emit(self.cursorX, self.cursorY)
self.update()
def mouseReleaseEvent(self, event):
self.moving = False
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# *** unnecessary, but it could be useful to show the visible range
qp.save()
qp.translate(.5, .5)
qp.drawRoundedRect(self.rect().adjusted(self.margin - 1, self.margin - 1, -self.margin, -self.margin), 2, 2)
qp.restore()
# comment the lines from the *** to this point, if not interested in the visual range
qp.setPen(QtCore.Qt.NoPen)
if self.moving:
qp.setBrush(self.movingCursorColor)
else:
qp.setBrush(self.cursorColor)
qp.drawEllipse(self.cursorRect.translated(self.visualPos()))
此解决方案的众多优点之一是可以跟踪单击 "slider" 的位置,同时保持一致性。
请注意 realX
、realY
和 realValues
是浮点数,它们在信号中自动转换为 int,因为它们的声明使用该对象类型。
我一直在寻找一种方法将水平滑块 + 垂直滑块转换为框区域中的单个按钮。你知道或认为 Qt 有能力做到这一点吗?
这张图就是我想要的
如果使用鼠标事件,这种小部件很容易创建:
- mousePressEvent: 如果鼠标在光标上方,则光标可以移动
- mouseReleaseEvent: 禁用光标
- mouseMoveEvent:如果启用了移动,则移动光标
然后,使用paintEvent
在其位置显示光标。
要定义光标的值,您必须将以像素为单位的位置转换为基于轴边界的值。
H 值介于 -10 和 20 之间且 V 值介于 -10 和 10 之间的操纵杆的快速示例:
from PyQt4.QtGui import QWidget, QApplication, QPainter
from PyQt4 import QtCore
import sys
class Joystick(QWidget):
def __init__(self, parent=None):
super(Joystick, self).__init__(parent)
self.setFixedSize(100, 100)
self._minimumX = -10
self._maximumX = 20
self._minimumY = -10
self._maximumY = 10
self.cursorPosition = QtCore.QPointF(10, 90)
self.grabCursor = False
def valueX(self):
return (self.cursorPosition.x() - 10) * (self._maximumX - self._minimumX) / (self.width() - 20) + self._minimumX
def valueY(self):
return (self.cursorPosition.y() - 10) * (self._maximumY - self._minimumY) / (self.width() - 20) + self._minimumY
def paintEvent(self, event):
painter = QPainter(self)
painter.setBrush(QtCore.Qt.lightGray)
painter.setPen(QtCore.Qt.NoPen)
painter.drawRect(0, 0, self.width(), self.height())
painter.setBrush(QtCore.Qt.blue)
painter.drawEllipse(self.cursorRect())
def boundedCursor(self, position):
def bound(low, high, value):
return max(low, min(high, value))
x = bound(10, self.width() - 10, position.x())
y = bound(10, self.height() - 10, position.y())
return QtCore.QPointF(x, y)
def cursorRect(self):
return QtCore.QRectF(-5, -5, 10, 10).translated(self.cursorPosition)
def mousePressEvent(self, ev):
self.grabCursor = self.cursorRect().contains(ev.pos())
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, event):
self.grabCursor = False
self.update()
def mouseMoveEvent(self, event):
if self.grabCursor:
print("Moving")
self.cursorPosition = self.boundedCursor(event.pos())
self.update()
print(self.valueX(), self.valueY())
if __name__ == '__main__':
# Create main application window
app = QApplication([])
joystick = Joystick()
joystick.show()
sys.exit(app.exec_())
根据 Romha Korev 给出的
此示例允许设置不同的参数并可以跟踪鼠标位置,无论您有什么数据和限制。它没有考虑任何固定的小部件 size/range(至少在我的测试中),但仍会根据小部件的大小进行调整。
class Bubble(QtWidgets.QWidget):
xChanged = QtCore.pyqtSignal(int)
yChanged = QtCore.pyqtSignal(int)
valuesChanged = QtCore.pyqtSignal(int, int)
moving = False
def __init__(self, minX=0, maxX=100, minY=0, maxY=100, cursorSize=10):
QtWidgets.QWidget.__init__(self)
self.margin = 10
self.setContentsMargins(0, 0, 0, 0)
self.minX = minX
self.minY = minY
self.maxX = maxX
self.maxY = maxY
self.extentX = abs(maxX - minX)
self.extentY = abs(maxY - minY)
self.setMinimumSize(self.extentX + self.margin * 2, self.extentY + self.margin * 2)
self.cursorSize = cursorSize
self.halfCursorSize = cursorSize * .5
self.cursorColor = QtGui.QColor(0, 174, 239)
self.movingCursorColor = QtGui.QColor(120, 124, 205)
self.cursorRect = QtCore.QRectF(-self.halfCursorSize, -self.halfCursorSize, cursorSize, cursorSize)
self.cursorX = self.minX + self.extentX * .5
self.cursorY = self.minY + self.extentY * .5
self.trackAnywhere = True
def visualPos(self):
# convert values to visual coordinates
return QtCore.QPointF(self.margin + self.halfCursorSize + ((self.cursorX - self.minX) / self.extentX) * (self.width() - self.margin * 2 - self.cursorSize),
self.margin + self.halfCursorSize + ((self.cursorY - self.minY) / self.extentY) * (self.height() - self.margin * 2 - self.cursorSize))
def realX(self, visualX):
visualExtent = self.width() - self.margin * 2 - self.cursorSize
realX = max(0, min(float(visualX - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentX
return self.minX + realX
def realY(self, visualY):
visualExtent = self.height() - self.margin * 2 - self.cursorSize
realY = max(0., min(float(visualY - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentY
return self.minY + realY
def realValues(self, pos):
return self.realX(pos.x()), self.realY(pos.y())
def mousePressEvent(self, event):
pos = event.pos()
visualPos = self.visualPos()
if self.trackAnywhere:
self.moving = True
if pos in self.cursorRect.translated(visualPos):
self.mouseDelta = visualPos - event.pos()
else:
self.mouseDelta = QtCore.QPoint()
elif pos in self.cursorRect.translated(visualPos):
self.moving = True
self.mouseDelta = visualPos - event.pos()
else:
self.moving = False
self.update()
def mouseMoveEvent(self, event):
if self.moving:
self.cursorX = self.realX(event.x() + self.mouseDelta.x())
self.cursorY = self.realY(event.y() + self.mouseDelta.y())
self.xChanged.emit(self.cursorX)
self.yChanged.emit(self.cursorY)
self.valuesChanged.emit(self.cursorX, self.cursorY)
self.update()
def mouseReleaseEvent(self, event):
self.moving = False
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# *** unnecessary, but it could be useful to show the visible range
qp.save()
qp.translate(.5, .5)
qp.drawRoundedRect(self.rect().adjusted(self.margin - 1, self.margin - 1, -self.margin, -self.margin), 2, 2)
qp.restore()
# comment the lines from the *** to this point, if not interested in the visual range
qp.setPen(QtCore.Qt.NoPen)
if self.moving:
qp.setBrush(self.movingCursorColor)
else:
qp.setBrush(self.cursorColor)
qp.drawEllipse(self.cursorRect.translated(self.visualPos()))
此解决方案的众多优点之一是可以跟踪单击 "slider" 的位置,同时保持一致性。
请注意 realX
、realY
和 realValues
是浮点数,它们在信号中自动转换为 int,因为它们的声明使用该对象类型。