将 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" 的位置,同时保持一致性。
请注意 realXrealYrealValues 是浮点数,它们在信号中自动转换为 int,因为它们的声明使用该对象类型。