单击拖动鼠标 - python / pyside时增加/减少QSpinBox值

问题描述 投票:4回答:4

如何在用户点击QSpinBox的向上或向下箭头时这样做,当光标向上拖动时,该值将增加,如果向下拖动则值将减小。我喜欢这个功能非常有用,用户只需单击并拖动光标,就可以不断点击错误。这是C#中使用的spinner的参考源代码,它在python中以我希望的方式工作。 http://www.paulneale.com/tutorials/dotNet/numericUpDown/numericUpDown.htm

import sys
from PySide import QtGui, QtCore


class Wrap_Spinner( QtGui.QSpinBox ):
    def __init__( self, minVal=0, maxVal=100, default=0):
        super( Wrap_Spinner, self ).__init__()
        self.drag_origin = None

        self.setRange( minVal, maxVal )
        self.setValue( default)

    def get_is_dragging( self ):
        # are we the widget that is also the active mouseGrabber?
        return self.mouseGrabber( ) == self

    ### Dragging Handling Methods ################################################
    def do_drag_start( self ):
        # Record position
        # Grab mouse
        self.drag_origin = QtGui.QCursor( ).pos( )
        self.grabMouse( )

    def do_drag_update( self ):
        # Transpose the motion into values as a delta off of the recorded click position
        curPos = QtGui.QCursor( ).pos( )
        offsetVal = self.drag_origin.y( ) - curPos.y( ) 
        self.setValue( offsetVal )
        print offsetVal

    def do_drag_end( self ):
        self.releaseMouse( )
        # Restore position
        # Reset drag origin value
        self.drag_origin = None

    ### Mouse Override Methods ################################################
    def mousePressEvent( self, event ):
        if QtCore.Qt.LeftButton:
            print 'start drag'
            self.do_drag_start( )
        elif self.get_is_dragging( ) and QtCore.Qt.RightButton:
            # Cancel the drag
            self.do_drag_end( )
        else:
            super( Wrap_Spinner, self ).mouseReleaseEvent( event )


    def mouseMoveEvent( self, event ):
        if self.get_is_dragging( ):
            self.do_drag_update( )
        else:
            super( Wrap_Spinner, self ).mouseReleaseEvent( event )


    def mouseReleaseEvent( self, event ):
        if self.get_is_dragging( ) and QtCore.Qt.LeftButton:
            print 'finish drag'
            self.do_drag_end( )
        else:
            super( Wrap_Spinner, self ).mouseReleaseEvent( event )


class Example(QtGui.QWidget ):
    def __init__( self):
        super( Example, self ).__init__( )
        self.initUI( )


    def initUI( self ):
        self.spinFrameCountA = Wrap_Spinner( 2, 50, 40)
        self.spinB = Wrap_Spinner( 0, 100, 10)

        self.positionLabel = QtGui.QLabel( 'POS:' )

        grid = QtGui.QGridLayout( )
        grid.setSpacing( 0 )
        grid.addWidget( self.spinFrameCountA, 0, 0, 1, 1 )
        grid.addWidget( self.spinB, 1, 0, 1, 1 )
        grid.addWidget( self.positionLabel, 2, 0, 1, 1 )
        self.setLayout( grid )
        self.setGeometry( 800, 400, 200, 150 )
        self.setWindowTitle( 'Max Style Spinner' )
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)
        self.show( )


def main( ):
    app = QtGui.QApplication( sys.argv )
    ex = Example( )
    sys.exit( app.exec_( ) )


if __name__ == '__main__':
    main()
python mouseevent pyside qspinbox
4个回答
2
投票

这已经很老了,但仍然是谷歌的热门话题。

我在网上找到了一些可能性,但没有一个是理想的。我的解决方案是top创建一种新的标签,在拖动时“擦洗”QSpinBox或QDoubleSpinBox。干得好:

////////////////////////////////////////////////////////////////////////////////
// Label for a QSpinBox or QDoubleSpinBox (or derivatives) that scrubs the spinbox value on click-drag
//
// Notes:
//  - Cursor is hidden and cursor position remains fixed during the drag
//  - Holding 'Ctrl' reduces the speed of the scrub
//  - Scrub multipliers are currently hardcoded - may want to make that a parameter in the future
template <typename SpinBoxT, typename ValueT>
class SpinBoxLabel : public QLabel
{
public:
    SpinBoxLabel(const QString& labelText, SpinBoxT& buddy)
        : QLabel(labelText)
        , Buddy(&buddy)
    {
        setBuddy(&buddy);
    }

protected:
    virtual void mouseMoveEvent(QMouseEvent* event) override {
        if (!(event->buttons() & Qt::LeftButton))
            return QLabel::mouseMoveEvent(event);

        if (!IsDragging) {
            StartDragPos = QCursor::pos();
            Value = double(Buddy->value());
            IsDragging = true;
            QApplication::setOverrideCursor(Qt::BlankCursor);
        }
        else {
            int dragDist = QCursor::pos().x() - StartDragPos.x();
            if (dragDist == 0)
                return;

            double dragMultiplier = .25 * Buddy->singleStep();
            if (!(event->modifiers() & Qt::ControlModifier))
                dragMultiplier *= 10.0;

            Value += dragMultiplier * dragDist;

            Buddy->setValue(ValueT(Value));

            QCursor::setPos(StartDragPos);
        }
    }

    virtual void mouseReleaseEvent(QMouseEvent* event) override {
        if (!IsDragging || event->button() != Qt::LeftButton)
            return QLabel::mouseReleaseEvent(event);

        IsDragging = false;
        QApplication::restoreOverrideCursor();
    }

private:
    SpinBoxT* Buddy;
    bool IsDragging = false;
    QPoint StartDragPos;
    double Value = 0.0;
};

typedef SpinBoxLabel<QDoubleSpinBox, double> DoubleSpinBoxLabel;
typedef SpinBoxLabel<QSpinBox, int> IntSpinBoxLabel;

3
投票

我是你插件的忠实粉丝,所以我很高兴能为你解答这个问题!我假设你正在编写一个pyside中的Max插件,因为当我遇到同样的问题时,这正是我正在做的事情(我也喜欢Max默认的“scrubby”微调器)。

解决方案实际上非常简单,您只需手动完成即可。我将QSpinBox子类化并捕获鼠标事件,使用它来计算相对于第一次开始单击窗口小部件时的y位置。这是代码,这是pyside2,因为从3DS Max和Maya 2018开始,这就是Autodesk使用的:

from PySide2 import QtWidgets, QtGui, QtCore
import MaxPlus

class SampleUI(QtWidgets.QDialog):

    def __init__(self, parent=MaxPlus.GetQMaxMainWindow()):
        super(SampleUI, self).__init__(parent)

        self.setWindowTitle("Max-style spinner")
        self.initUI()
        MaxPlus.CUI.DisableAccelerators()

    def initUI(self):

        mainLayout = QtWidgets.QHBoxLayout()

        lbl1 = QtWidgets.QLabel("Test Spinner:")
        self.spinner = SuperSpinner(self)
        #self.spinner = QtWidgets.QSpinBox()        -- here's the old version
        self.spinner.setMaximum(99999)

        mainLayout.addWidget(lbl1)
        mainLayout.addWidget(self.spinner)

        self.setLayout(mainLayout)


    def closeEvent(self, e):
        MaxPlus.CUI.EnableAccelerators()

class SuperSpinner(QtWidgets.QSpinBox):
    def __init__(self, parent):
        super(SuperSpinner, self).__init__(parent)

        self.mouseStartPosY = 0
        self.startValue = 0

    def mousePressEvent(self, e):
        super(SuperSpinner, self).mousePressEvent(e)
        self.mouseStartPosY = e.pos().y()
        self.startValue = self.value()

    def mouseMoveEvent(self, e):
        self.setCursor(QtCore.Qt.SizeVerCursor)

        multiplier = .5
        valueOffset = int((self.mouseStartPosY - e.pos().y()) * multiplier)
        print valueOffset
        self.setValue(self.startValue + valueOffset)

    def mouseReleaseEvent(self, e):
        super(SuperSpinner, self).mouseReleaseEvent(e)
        self.unsetCursor()


if __name__ == "__main__":

    try:
        ui.close()
    except:
        pass

    ui = SampleUI()
    ui.show()

1
投票

我遇到了同样的问题,不幸的是,我找到的解决方案只有在你从箭头或旋转框的边框点击并拖动时才有效。但大多数用户都希望从实际的文本字段中拖动,因此这样做并不直观。

相反,您可以继承QLineEdit以获得正确的行为。单击它时,它将保存其当前值,以便在用户拖动时获取鼠标位置的增量并将其应用回旋转框。

这是我自己使用的完整示例。很抱歉,它是Maya的属性样式而不是Max的,所以你单击并拖动鼠标中键来设置值。通过一些调整,你可以很容易地让它像Max一样工作:

from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets


class CustomSpinBox(QtWidgets.QLineEdit):

    """
    Tries to mimic behavior from Maya's internal slider that's found in the channel box.
    """

    IntSpinBox = 0
    DoubleSpinBox = 1

    def __init__(self, spinbox_type, value=0, parent=None):
        super(CustomSpinBox, self).__init__(parent)

        self.setToolTip(
            "Hold and drag middle mouse button to adjust the value\n"
            "(Hold CTRL or SHIFT change rate)")

        if spinbox_type == CustomSpinBox.IntSpinBox:
            self.setValidator(QtGui.QIntValidator(parent=self))
        else:
            self.setValidator(QtGui.QDoubleValidator(parent=self))

        self.spinbox_type = spinbox_type
        self.min = None
        self.max = None
        self.steps = 1
        self.value_at_press = None
        self.pos_at_press = None

        self.setValue(value)

    def wheelEvent(self, event):
        super(CustomSpinBox, self).wheelEvent(event)

        steps_mult = self.getStepsMultiplier(event)

        if event.delta() > 0:
            self.setValue(self.value() + self.steps * steps_mult)
        else:
            self.setValue(self.value() - self.steps * steps_mult)

    def mousePressEvent(self, event):
        if event.buttons() == QtCore.Qt.MiddleButton:
            self.value_at_press = self.value()
            self.pos_at_press = event.pos()
            self.setCursor(QtGui.QCursor(QtCore.Qt.SizeHorCursor))
        else:
            super(CustomSpinBox, self).mousePressEvent(event)
            self.selectAll()

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            self.value_at_press = None
            self.pos_at_press = None
            self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
            return

        super(CustomSpinBox, self).mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() != QtCore.Qt.MiddleButton:
            return

        if self.pos_at_press is None:
            return

        steps_mult = self.getStepsMultiplier(event)

        delta = event.pos().x() - self.pos_at_press.x()
        delta /= 6  # Make movement less sensitive.
        delta *= self.steps * steps_mult

        value = self.value_at_press + delta
        self.setValue(value)

        super(CustomSpinBox, self).mouseMoveEvent(event)

    def getStepsMultiplier(self, event):
        steps_mult = 1

        if event.modifiers() == QtCore.Qt.CTRL:
            steps_mult = 10
        elif event.modifiers() == QtCore.Qt.SHIFT:
            steps_mult = 0.1

        return steps_mult

    def setMinimum(self, value):
        self.min = value

    def setMaximum(self, value):
        self.max = value

    def setSteps(self, steps):
        if self.spinbox_type == CustomSpinBox.IntSpinBox:
            self.steps = max(steps, 1)
        else:
            self.steps = steps

    def value(self):
        if self.spinbox_type == CustomSpinBox.IntSpinBox:
            return int(self.text())
        else:
            return float(self.text())

    def setValue(self, value):
        if self.min is not None:
            value = max(value, self.min)

        if self.max is not None:
            value = min(value, self.max)

        if self.spinbox_type == CustomSpinBox.IntSpinBox:
            self.setText(str(int(value)))
        else:
            self.setText(str(float(value)))


class MyTool(QtWidgets.QWidget):

    """
    Example of how to use the spinbox.
    """

    def __init__(self, parent=None):
        super(MyTool, self).__init__(parent)

        self.setWindowTitle("Custom spinboxes")
        self.resize(300, 150)

        self.int_spinbox = CustomSpinBox(CustomSpinBox.IntSpinBox, parent=self)
        self.int_spinbox.setMinimum(-50)
        self.int_spinbox.setMaximum(100)

        self.float_spinbox = CustomSpinBox(CustomSpinBox.DoubleSpinBox, parent=self)
        self.float_spinbox.setSteps(0.1)

        self.main_layout = QtWidgets.QVBoxLayout()
        self.main_layout.addWidget(self.int_spinbox)
        self.main_layout.addWidget(self.float_spinbox)
        self.setLayout(self.main_layout)


# Run the tool.
global tool_instance
tool_instance = MyTool()
tool_instance.show()

我试图让这些函数与Qt的原生spinBox相匹配。在我的情况下我不需要它,但是当值在发布时发生变化时添加信号会很容易。像Houdini的滑块一样将它带到下一个级别也很容易,因此步长率可以根据鼠标的垂直位置而变化。呸,也许是下雨天:)。

这就是现在的功能:

  • 可以做整数或双旋转箱
  • 单击然后拖动鼠标中键以设置值
  • 在拖动时,按住ctrl可提高速率或按住shift可降低速率
  • 您仍然可以像正常一样输入值
  • 您还可以通过滚动鼠标滚轮来更改值(按住ctrl并切换更改率)

Example


-1
投票

可以使用QAbstractSpinBox.setAccelerated更改旋转框增量的速度:

    self.spinFrameCountA.setAccelerated(True)

启用此选项后,按住鼠标按钮的时间越长,旋转框值的变化越快。

© www.soinside.com 2019 - 2024. All rights reserved.