如何在用户点击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()
这已经很老了,但仍然是谷歌的热门话题。
我在网上找到了一些可能性,但没有一个是理想的。我的解决方案是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;
我是你插件的忠实粉丝,所以我很高兴能为你解答这个问题!我假设你正在编写一个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()
我遇到了同样的问题,不幸的是,我找到的解决方案只有在你从箭头或旋转框的边框点击并拖动时才有效。但大多数用户都希望从实际的文本字段中拖动,因此这样做并不直观。
相反,您可以继承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的滑块一样将它带到下一个级别也很容易,因此步长率可以根据鼠标的垂直位置而变化。呸,也许是下雨天:)。
这就是现在的功能:
可以使用QAbstractSpinBox.setAccelerated更改旋转框增量的速度:
self.spinFrameCountA.setAccelerated(True)
启用此选项后,按住鼠标按钮的时间越长,旋转框值的变化越快。