【问题标题】:Is it possible to select/unselect QChecBox by passing th mouse over the labels?是否可以通过将鼠标移到标签上来选择/取消选择 QChecBox?
【发布时间】:2021-10-14 08:08:21
【问题描述】:

我正在尝试通过单击标签并将鼠标移动到其他复选框上来选择/取消选择 Qcheckbox

我想要的是,例如,单击“0”并保持鼠标单击并将其向下移动到“1”、“2”...通过移动这些复选框,它们必须更改它们的值(真到假)。 我不明白如何使用mouseMoveEvent

我做了一个最小的代码开始

import sys

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


class CheckBox(QCheckBox):
    def __init__(self, *args, **kwargs):
        QCheckBox.__init__(self, *args, **kwargs)
    def mouseMoveEvent(self,event):
        if event.MouseButtonPress == Qt.MouseButton.LeftButton:
            self.setChecked(not self.isChecked())

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainHBOX = QVBoxLayout()

        self.CB_list = []
        for i in range(20):
            CB = CheckBox(str(i))
            CB.setChecked(True)
            self.CB_list.append(CB)
            self.mainHBOX.addWidget(CB)

        self.centralWidget.setLayout(self.mainHBOX)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python pyqt5 qcheckbox


    【解决方案1】:

    如果您将 QCheckBox 子类化,这将更难实现,相反,我建议您将框架子类化并覆盖 mouseMoveEvent 并将所有复选框添加到该框架内。

    mouseMoveEvent 下,使用event.buttons() == QtCore.Qt.LeftButton 检查鼠标左键是否被按下。然后使用childAt获取鼠标位置的小部件,然后检查它是否是QCheckBox的实例,如果是则翻转选中状态。

    这是一个示例代码。

    import sys
    from PyQt5 import QtWidgets, QtGui, QtCore
    
    
    class CheckBox(QtWidgets.QCheckBox):
    
        def styleRect(self) -> QtCore.QRect:
            option = QtWidgets.QStyleOptionButton()
            option.initFrom(self)
            rect = self.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, option, self)
            content_rect = self.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxContents, option, self)
    
            return rect.united(content_rect)
    
        def mousePressEvent(self, event) -> bool:  
            super(CheckBox, self).mousePressEvent(event)
    
            if event.buttons() == QtCore.Qt.LeftButton and self.styleRect().contains(event.pos()):
                    self.setChecked(not self.isChecked())
        
        def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
            
            if event.buttons() == QtCore.Qt.LeftButton and self.styleRect().contains(event.pos()):
                return
            
            super(CheckBox, self).mouseMoveEvent(event)
    
    
        def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
           
            if self.styleRect().contains(event.pos()):
                return
    
            super(CheckBox, self).mouseReleaseEvent(event)
      
    class CheckBoxFrame(QtWidgets.QFrame):
    
        def __init__(self, *args, **kwargs):
            super(CheckBoxFrame, self).__init__(*args, **kwargs)
    
            self._previous_widget = None
    
        def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
            
            super(CheckBoxFrame, self).mouseMoveEvent(event)
    
            if event.buttons() == QtCore.Qt.LeftButton:
                widget = self.childAt(event.pos()) 
                if isinstance(widget, QtWidgets.QCheckBox) and widget != self._previous_widget:
                    widget.setChecked(not widget.isChecked())
                    self._previous_widget = widget 
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__()
            self.centralWidget = QtWidgets.QWidget()
            self.setCentralWidget(self.centralWidget)
            self.mainVBOX = QtWidgets.QVBoxLayout()
            
            btn_frame = CheckBoxFrame()
            btn_frame.setLayout(QtWidgets.QVBoxLayout())
    
            for i in range(20):
                CB = CheckBox(str(i))
                CB.setChecked(True)
                btn_frame.layout().addWidget(CB)
    
            self.mainVBOX.addWidget(btn_frame)
            self.centralWidget.setLayout(self.mainVBOX)
        
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 不错!这几乎是我想要的。只是,我点击的复选框不会改变它的值。如果我按下左键并离开复选框,是否可以反转值?
    • @ymmx 抱歉,我没看懂,你能再解释一下吗?
    • 是的,我很抱歉。当我单击“0”标签并将鼠标向下移动(仍然按下按钮)在“1”、“2”上时...它反转了“1”、“2”等复选框,但“0” ' 复选框没有改变。
    • @ymmx 嗯。看起来只有当鼠标按下复选框而不是标签时才会发生这种情况。有空我会研究一下。
    • @ymmx 你可以试试用这个复选框代替 QCheckBox,here is the code
    【解决方案2】:

    您的代码中有两个问题。
    首先,当一个小部件收到鼠标按钮按下时,它通常会成为“鼠标抓取器”,从那一刻起,只有 那个小部件会接收鼠标移动事件,直到按钮被释放。
    然后你没有正确检查按钮:event.MouseButtonPress 是一个常量,表示鼠标按下事件,Qt.MouseButton.LeftButton 是另一个表示鼠标左键的常量;您实际上比较了两个不同的常量,这与 if 2 == 1: 的结果相同。

    mouseMoveEvent 将始终返回 event.type() == event.MouseMove,因此无需检查它,但对于当前按钮:对于鼠标按下和释放事件,导致该事件的按钮是由event.button() 返回,而对于移动事件,当前按下的按钮event.buttons()(复数)返回。记住这个区别,因为event.button()(单数)总是会为鼠标移动事件返回NoButton(如果你仔细想想这很明显,因为事件不是由按钮生成的)。

    考虑到这一点,您可以做的是检查与映射到它的鼠标位置相对应的 的子小部件。

    为了实现这一点,您必须确保鼠标按钮已实际按下标签(使用样式函数)并将实例属性设置为标志以了解是否应跟踪其他兄弟的移动。请注意,在以下示例中,我还使用了另一个标志来检查作为父级直接后代的子级:如果设置了该标志,则只有 siblings 第一个按下的复选框将被切换,如果另一个 @ 987654331@ 实例已找到,但其父实例是第一个实例的,不执行任何操作。

    class CheckBox(QCheckBox):
        maybeDrag = False
        directChildren = True
        def mousePressEvent(self, event):
            if event.button() == Qt.LeftButton:
                opt = QStyleOptionButton()
                self.initStyleOption(opt)
                rect = self.style().subElementRect(QStyle.SE_CheckBoxContents, opt, self)
                if event.pos() in rect:
                    self.setChecked(not self.isChecked())
                    self.maybeDrag = True
                    # we do *not* want to call the default behavior here
                    return
            super().mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            if self.maybeDrag:
                parent = self.parent()
                child = parent.childAt(self.mapToParent(event.pos()))
                if isinstance(child, CheckBox) and child != self:
                    # with the directChildren flag set, we will only toggle CheckBox
                    # instances that are direct children of this (siblings),
                    # otherwise we toggle them anyway
                    if not self.directChildren or child.parent() == parent:
                        child.setChecked(self.isChecked())
            super().mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            if self.maybeDrag:
                self.maybeDrag = False
                self.setDown(False)
            else:
                super().mouseReleaseEvent(event)
    

    此实现的唯一缺点是,如果您需要为 other 父母的孩子(他们共享一个共同的祖先,但不是同一个父母)的项目执行此操作,它将不起作用.

    在这种情况下,您需要为所有需要此行为的复选框安装事件过滤器并相应地实现上述功能,不同之处在于您需要使用顶级窗口来映射鼠标坐标(@987654333 @) 并使用该窗口的childAt()

    最后,考虑SE_CheckBoxContents 返回内容的full 区域,即使显示的文本小于可用空间;大多数样式的默认行为是仅在实际显示的内容(图标和/或文本的边界矩形)内完成点击事件时才做出反应。如果您想恢复默认行为,您需要为SE_CheckBoxClickRect 构造另一个矩形(这是包括指示器在内的整个可点击区域)并检查鼠标是否在两者的相交矩形内:

        contents = self.style().subElementRect(QStyle.SE_CheckBoxContents, opt, self)
        click = self.style().subElementRect(QStyle.SE_CheckBoxClickRect, opt, self)
        if event.pos() in (contents & click):
            # ...
    

    QRects 的& 二元运算符在逻辑意义上与按位运算符的工作方式相同:它只返回一个包含在两个源矩形中的矩形。它的字面量函数是intersected()

    【讨论】:

      猜你喜欢
      • 2011-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-20
      • 1970-01-01
      • 2021-02-01
      • 2017-07-16
      • 2011-12-06
      相关资源
      最近更新 更多