【问题标题】:Make a QSpinBox which requires a double-click to edit制作一个需要双击编辑的QSpinBox
【发布时间】:2021-02-02 20:01:17
【问题描述】:

我想做一个只有在数字显示区双击才能编辑的旋转框。

我在下面的尝试在所有情况下都会禁用焦点,除非按下递增/递减按钮。

我希望递增/递减来执行操作而不窃取焦点。 我确实想要双击文本区域时正常闪烁的光标和编辑功能。

编辑后,当点击另一个小部件或按下回车键时,小部件应该释放焦点。

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt

event_dict = {v: k for k, v in QtCore.QEvent.__dict__.items() if isinstance(v, int)}

noisy_events = [
    'Paint',
    'Show',
    'Move',
    'Resize',
    'DynamicPropertyChange',
    'PolishRequest',
    'Polish',
    'ChildPolished',
    'HoverMove',
    'HoverEnter',
    'HoverLeave',
    'ChildAdded',
    'ChildRemoved',
]

class ClickableSpinBox(QtWidgets.QDoubleSpinBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.installEventFilter(self)
        self.setFocusPolicy(Qt.NoFocus)

    def eventFilter(self, a0: 'QObject', a1: 'QEvent') -> bool:
        if a0 is not self:
            return super().eventFilter(a0, a1)

        if a1.type() == QtCore.QEvent.FocusAboutToChange:
            print("intercepted focus about to change")
            return True
        if a1.type() == QtCore.QEvent.FocusIn:
            print("intercepted focus in")
            return True
        if a1.type() == QtCore.QEvent.MouseButtonPress:
            print("intercepted mouse press")
            #return True
        elif a1.type() == QtCore.QEvent.MouseButtonDblClick:
            print("intercepted double click")
            self.setFocus()
        else:
            if a1.type() in event_dict:
                evt_name = event_dict[a1.type()]
                if evt_name not in noisy_events:
                    print(evt_name)
            else:
                pass
                #print(f"Unknown event type {a1.type()}")
        return super().eventFilter(a0, a1)


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    w = QtWidgets.QWidget()
    l = QtWidgets.QHBoxLayout()
    l.addWidget(ClickableSpinBox())
    l.addWidget(ClickableSpinBox())
    l.addWidget(QtWidgets.QDoubleSpinBox())
    w.setLayout(l)
    w.show()
    app.exec_()

【问题讨论】:

  • 当旋转框不可编辑时,按钮的行为应该是什么?那么通过鼠标滚轮和箭头键递增呢?
  • @ekhumoro 向上/向下按钮应该可以工作,但箭头键应该被禁用

标签: python pyqt pyqt5 qspinbox


【解决方案1】:

编辑:

让鼠标滚动功能和增加/减少按钮工作

当您在其内部或 SpinBox 的边框中双击时,我使 QDoubleSpinBox 内部的 QLineEdit 被启用/禁用。有了这个,您仍然可以使用鼠标滚动或按钮更改其中的值。这是您修改的代码:

class ClickableSpinBox(QtWidgets.QDoubleSpinBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.installEventFilter(self)
        self.lineEdit().setEnabled(False)
        self.setFocusPolicy(Qt.NoFocus)

    def eventFilter(self, a0: 'QObject', a1: 'QEvent') -> bool:
        if a0 is not self:
            return super().eventFilter(a0, a1)
        elif a1.type() == QtCore.QEvent.MouseButtonDblClick:
            ## When double clicked inside the Disabled QLineEdit of
            ## the SpinBox, this will enable it and set the focus on it
            self.lineEdit().setEnabled(True)
            self.setFocus()
        elif a1.type() == QtCore.QEvent.FocusOut:
            ## When you lose the focus, e.g. you click on other object
            ## this will diable the QLineEdit
            self.lineEdit().setEnabled(False)
        elif a1.type() == QtCore.QEvent.KeyPress:
            ## When you press the Enter Button (Return) or the 
            ## Key Pad Enter (Enter) you will disable the QLineEdit
            if a1.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]:
                self.lineEdit().setEnabled(False)
        return super().eventFilter(a0, a1)
    
    def stepBy(self, steps):
        ## The reason of this is because if you click two consecutive times 
        ## in any of the two buttons, the object will trigger the DoubleClick
        ## event.
        self.lineEdit().setEnabled(False)
        super().stepBy(steps)
        self.lineEdit().deselect()

禁用QLineEdit 并启用按钮的结果:

只允许鼠标滚动功能

您只需使用setButtonSymbols() 从上面的代码中删除按钮。

class ClickableSpinBox(QtWidgets.QDoubleSpinBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.installEventFilter(self)
        self.lineEdit().setEnabled(False)
        self.setFocusPolicy(Qt.NoFocus)
        ## Changing button's symbol to 2 means to "delete" the buttons
        self.setButtonSymbols(2)

按钮“禁用”的结果:

上一个答案(编辑前)

我有一个棘手的解决方案,它包括启用/禁用您创建的自定义旋转框。有了这个,旋转框只有在你双击它们时才会被启用(并且可编辑),当你失去对它们的关注时,它们将被自动禁用,将焦点传递给启用的旋转框。

我这样做的原因是,当 SpinBox 启用时,只有在双击边框或递增/递减按钮时才会触发 DoubleClick 事件。禁用它们就可以解决问题,因为无论您在 SpinBox 内按什么,都会触发双击事件。

这是我修改后的代码:(代码中有 cmets 可帮助您理解我所做的)

class ClickableSpinBox(QtWidgets.QDoubleSpinBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.installEventFilter(self)
        self.setFocusPolicy(Qt.NoFocus)

    def eventFilter(self, a0: 'QObject', a1: 'QEvent') -> bool:
        if a0 is not self:
            return super().eventFilter(a0, a1)
        elif a1.type() == QtCore.QEvent.MouseButtonDblClick:
            ## When you double click inside the Disabled SpinBox
            ## this will enable it and set the focus on it
            self.setEnabled(True)
            self.setFocus()
        elif a1.type() == QtCore.QEvent.FocusOut:
            ## When you lose the focus, e.g. you click on other object
            ## this will disable the SpinBox
            self.setEnabled(False)
        elif a1.type() == QtCore.QEvent.KeyPress:
            ## When you press the Enter Button (Return) or the 
            ## Key Pad Enter (Enter) you will disable the SpinBox
            if a1.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]:
                self.setEnabled(False)
        return super().eventFilter(a0, a1)


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    w = QtWidgets.QWidget()
    l = QtWidgets.QHBoxLayout()
    ## I store the SpinBoxes to give the disable property after
    ## generating its instance
    sp1 = ClickableSpinBox()
    sp1.setEnabled(False)
    sp2 = ClickableSpinBox()
    sp2.setEnabled(False)
    sp3 = QtWidgets.QDoubleSpinBox()
    l.addWidget(sp1)
    l.addWidget(sp2)
    l.addWidget(sp3)
    w.setLayout(l)
    w.show()
    app.exec_()

以及该代码运行的一些屏幕截图:

【讨论】:

  • 干得好,非常接近,但我希望增量/减量继续工作
  • @Techniquab 好的,知道了。我将编辑我的答案来做到这一点。
【解决方案2】:

下面的演示脚本应该可以满足您的所有需求。我添加了两个额外的功能:(1)禁用文本选择,(2)禁用文本框(但不是按钮)上的鼠标滚轮增量。如果这些不符合您的口味,则可以轻松调整或删除它们(请参阅代码中的 cmets)。否则实现非常简单,因为它不依赖于控制焦点。

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class ClickableSpinBox(QDoubleSpinBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setEditingDisabled(True)
        self.lineEdit().installEventFilter(self)
        self.editingFinished.connect(self.setEditingDisabled)

    def editingDisabled(self):
        return self.lineEdit().isReadOnly()

    def setEditingDisabled(self, disable=True):
        self.lineEdit().setReadOnly(disable)
        self.setFocusPolicy(Qt.TabFocus if disable else Qt.WheelFocus)
        # optional: control selection in text-box
        if disable:
            self.clearSelection()
            self.lineEdit().selectionChanged.connect(self.clearSelection)
        else:
            self.lineEdit().selectionChanged.disconnect(self.clearSelection)
            self.lineEdit().selectAll()

    def clearSelection(self):
        self.lineEdit().setSelection(0, 0)

    def eventFilter(self, source, event):
        if (event.type() == QEvent.MouseButtonDblClick and
            source is self.lineEdit() and self.editingDisabled()):
            self.setEditingDisabled(False)
            self.setFocus()
            return True
        return super().eventFilter(source, event)

    # optional: control mouse-wheel events in text-box
    def wheelEvent(self, event):
        if self.editingDisabled():
            self.ensurePolished()
            options = QStyleOptionSpinBox()
            self.initStyleOption(options)
            rect = self.style().subControlRect(
                QStyle.CC_SpinBox, options,
                QStyle.SC_SpinBoxUp, self)
            if event.pos().x() <= rect.left():
                return
        super().wheelEvent(event)

    def keyPressEvent(self, event):
        if not self.editingDisabled():
            super().keyPressEvent(event)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        layout = QHBoxLayout(self)
        self.spinboxA = ClickableSpinBox()
        self.spinboxB = ClickableSpinBox()
        self.spinboxC = QDoubleSpinBox()
        layout.addWidget(self.spinboxA)
        layout.addWidget(self.spinboxB)
        layout.addWidget(self.spinboxC)
        self.setFocusPolicy(Qt.ClickFocus)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.setGeometry(900, 100, 200, 100)
    window.show()
    sys.exit(app.exec_())

【讨论】:

  • 干得好,非常接近 - 2 个抱怨:1- 如果常规旋转框有焦点,双击自定义旋转框不会捕获焦点(第三次单击会)。 2-点击其他地方并没有清除焦点(我想这是正常的,但不是我想要的)
  • @Techniquab 我已经解决了这两个问题。请立即尝试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-20
  • 2011-03-18
  • 1970-01-01
  • 2014-11-02
  • 2011-07-26
  • 2014-03-14
相关资源
最近更新 更多