【问题标题】:increases/decreases QSpinBox value when click drag mouse - python / pyside单击拖动鼠标时增加/减少 QSpinBox 值 - python / pyside
【发布时间】:2014-01-22 06:07:51
【问题描述】:

我怎样才能做到这一点,当用户点击 QSpinBox 的向上或向下箭头时,值会随着光标向上拖动而增加,如果向下拖动,值会减小。我喜欢这个功能非常有用,用户只需单击并拖动光标即可,而不是不断单击错误。这是用 C# 制作的微调器的参考源代码,它的工作方式与我希望在 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()

【问题讨论】:

  • 对我来说,这已经适用于标准旋转框。也就是说,如果我单击向上按钮,则在按住鼠标按钮的同时,值会继续增加。然后我可以拖到向下按钮上,使值朝相反的方向移动。
  • 它运行得非常缓慢。我想根据光标的位置使微调器增加得更快……向上或向下。许多 3d 应用程序都是这样工作的。
  • 有一个简单的解决方法:请参阅我的答案。
  • 甜心!!!!!! ctrl V ctrl V

标签: python mouseevent pyside qspinbox


【解决方案1】:

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

    self.spinFrameCountA.setAccelerated(True)

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

【讨论】:

  • 你能告诉我如何做一个鼠标点击和拖动事件,例如。如果有帮助,我可以告诉你我希望微调器如何反应。
  • 我有一个用 c# 编写的工具,它可以按照我希望我的微调器在 python 中工作的方式工作。您可以在此处下载并查看。 jokermartini.com/2011/11/23/spacer
  • @JokerMartini。那是一个 Windows 程序,似乎没有任何源代码。无论如何,我的解决方案有什么问题:它或多或少做同样的事情,不是吗?
  • @JokerMartini。是的,我可以提供帮助——但我不会为你编写所有代码。您至少应该努力研究事物并在您的问题中包含尝试的解决方案。您所要求的可能是可行的,而不是微不足道的(这就是我给出答案的原因)。
  • 我用我目前所拥有的更新了上面的代码。微调器值在将本地值从单击时的位置偏移方面不太有效,但请检查一下。
【解决方案2】:

这是旧的,但仍然是 Google 上的热门产品。

我在网上找到了一些可能性,但没有一个是理想的。我的解决方案是创建一种新型标签,在拖动时“擦洗”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;

【讨论】:

  • @nish 查看我上面的答案以获取 python 解决方案
【解决方案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()

【讨论】:

  • SuperSpinner 很棒,但按钮似乎停止正常工作。我做错了什么,还是有办法解决这个问题?我希望同时具有按向上/向下按钮时的常规行为和擦洗功能。
  • 感谢@FilipS。出于某种原因,我有 self.setSingleStep(0) 破坏了这个功能。现在想不起来为什么了。但无论如何我解决了这个问题,并且超级鼠标按下和释放事件将带回预期的功能。我现在无法测试这段代码,所以如果有任何问题,请告诉我,但应该这样做! (如果这对您有帮助,请不要忘记投票!)
  • 感谢您的快速回复!新版本似乎按预期工作。从现在开始,我肯定会用 SuperSpinBoxes 替换我所有的 spinboxes :)
  • @FilipS。哈哈甜! Qt 很有趣,我真的很喜欢它。
【解决方案4】:

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

相反,您可以将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 和 shift 变化率)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-16
    • 2018-01-12
    • 1970-01-01
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多