【问题标题】:Is there a way to stack widgets and ignore overlapping?有没有办法堆叠小部件并忽略重叠?
【发布时间】:2021-10-13 09:59:26
【问题描述】:

我正在使用 PyQt5 制作一个 GUI,当您将鼠标悬停在小部件上时,我让小部件的背景改变颜色。但是,当我将鼠标悬停在一个小部件上时,它下方的小部件会重叠,并且底部的背景会被切断。

由于我制作背景的方式,我不得不将小部件间隔得很近,并增加边框大小以实现背景效果,但是就像我之前所说的那样,它与悬停混淆了。有什么方法可以忽略这种重叠,或者我可以使用不同的方法来绘制背景而不必重叠小部件?

这是我想要的截图,以防我的解释不好(底部小部件工作正常,因为它下面没有任何东西可以重叠)

我还会在此处附上我的代码,以便您查看我所做的事情

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

class MainWindow(QWidget):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.layout = QVBoxLayout()
        self.layout.addWidget(MyBar(self))
        self.setLayout(self.layout)
        self.layout.setContentsMargins(0,0,0,0)
        self.setFixedSize(450,550)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setStyleSheet('background-color: #121A2B;')

        self.checkbox_style='''
        QCheckBox
        {
            background-color : rgb(25,34,52);
            border-radius : 5px;
            spacing : 10px;
            padding : 15px;
            min-height : 15px;
        }
        QCheckBox::unchecked
        {
            color : rgb(159,172,168);
        }
        QCheckBox::checked
        {
            color : rgb(217,223,227);
        }
        QCheckBox::indicator
        {
            border : 2px solid rgb(105, 139, 194);
            width : 12px;
            height : 12px;
            border-radius : 8px;
        }
        QCheckBox::indicator:checked
        {
            image : url(red.png);
            border : 2px solid rgb(221, 54, 77);
        }
        QCheckBox::hover
        {
            background-color: #263450
        }
        '''

        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(11)
        font.setBold(True)

        self.checkbox_layout = QVBoxLayout()

        self.checkbox1 = QCheckBox('checkbox1')
        self.checkbox_layout.addWidget(self.checkbox1)
        self.checkbox1.setFont(font)
        self.checkbox1.setStyleSheet(self.checkbox_style)

        self.checkbox2 = QCheckBox('checkbox2')
        self.checkbox_layout.addWidget(self.checkbox2)
        self.checkbox2.setFont(font)
        self.checkbox2.setStyleSheet(self.checkbox_style)
        
        self.checkbox3 = QCheckBox('checkbox3')
        self.checkbox_layout.addWidget(self.checkbox3)
        self.checkbox3.setFont(font)
        self.checkbox3.setStyleSheet(self.checkbox_style)

        self.checkbox3 = QCheckBox('checkbox4')
        self.checkbox_layout.addWidget(self.checkbox3)
        self.checkbox3.setFont(font)
        self.checkbox3.setStyleSheet(self.checkbox_style)

        self.layout.addLayout(self.checkbox_layout)
        self.checkbox_layout.setContentsMargins(15,7,290,350)
        self.setLayout(self.layout)
        self.layout.setAlignment(Qt.AlignTop)

class MyBar(QWidget):

    def __init__(self, parent):
        super(MyBar, self).__init__()
        self.parent = parent
        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0,0,0,0)
        self.title = QLabel("")

        font = QtGui.QFont()
        font.setFamily("Bebas Neue")
        font.setPointSize(25)
        font.setBold(False)
        self.title.setFont(font)

        self.btn_close = QPushButton()
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.btn_close.setFixedSize(40, 35)
        self.btn_close.setStyleSheet("QPushButton::hover"
        "{"
            "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #fc0703, stop: 1 #a10303);"
            "border : none;"
        "}"
        "QPushButton"
        "{"
        "background-color : rgb(25, 34, 52)"
        "}")
        self.btn_close.setFlat(True)
        self.btn_close.setIcon(QIcon("C:/Users/User/Documents/GitHub/guirebuild/close.png"))
        self.btn_close.setIconSize(QSize(15, 15))

        self.title.setFixedHeight(53)
        self.title.setAlignment(Qt.AlignTop)
        self.layout.addWidget(self.title)
        
        self.layout.addWidget(self.btn_close,alignment=Qt.AlignTop)

        self.title.setStyleSheet("""
            background-color: rgb(25, 34, 52);
            color: white;
        """)

        self.setLayout(self.layout)

        self.start = QPoint(0, 0)
        self.pressing = False

    def resizeEvent(self, QResizeEvent):
        super(MyBar, self).resizeEvent(QResizeEvent)
        self.title.setFixedWidth(self.parent.width())

    def mousePressEvent(self, event):
        self.start = self.mapToGlobal(event.pos())
        self.pressing = True

    def mouseMoveEvent(self, event):
        if self.pressing:
            self.end = self.mapToGlobal(event.pos())
            self.movement = self.end-self.start
            self.parent.setGeometry(self.mapToGlobal(self.movement).x(),
                                self.mapToGlobal(self.movement).y(),
                                self.parent.width(),
                                self.parent.height())
            self.start = self.end

    def mouseReleaseEvent(self, QMouseEvent):
        self.pressing = False

    def btn_close_clicked(self):
        self.parent.close()

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

【问题讨论】:

  • 默认情况下,小部件放在布局中时不能重叠。也就是说,请提供minimal reproducible example,因为您当前的代码不会重现该问题(项目之间有空格)并且还需要一个我们一无所知的MyBar 类。
  • 我编辑了上面的代码以包含完整的代码,因此您现在应该能够重现它。我的错。

标签: python pyqt5


【解决方案1】:

问题是由于新小部件的堆叠造成的,最简单的解决方案是为这些小部件设置一个透明背景,前提是您确定它们不会有不同的背景没有悬停:

        self.checkbox_style='''
        QCheckBox
        {
            background-color : rgb(0, 0, 0, 0);
            ...

很遗憾,这不会真正解决您的问题,因为您的实施中存在严重的逻辑问题。

首先,基于整体布局设置布局的硬编码内容边距是一个严重的问题,原因有很多:

  • 它会强制您根据菜单内容更改那些硬编码的边距;
  • 它基于您的关于当前可用字体的假设,这可能会使 UI 元素部分(或完全)隐藏;
  • 它不允许您在布局中正确添加更多对象;

认真考虑了上述事项(这些事项非常重要,并且永远不应被低估),问题是,虽然 Qt 允许通过 QSS 填充将项目布置在其可能位置“之外”,但您需要考虑小部件 z-level,默认情况下,它会在任何其他先前创建的 sibling之上堆叠一个新小部件> 小部件(具有共同祖先的小部件),这就是为什么您会看到部分隐藏的复选框。

基本的解决方案是安装一个事件过滤器,检查它是否是HoverMove 事件类型,然后调用raise_() 以确保它位于任何其他同级之上

class MainWindow(QWidget):
    def __init__(self):
        # ...
        for check in self.findChildren(QCheckBox):
            check.installEventFilter(self)

    def eventFilter(self, obj, event):
        if event.type() == event.HoverMove:
            obj.raise_()
        return super().eventFilter(obj, event)

同样,这只能部分解决您的问题,因为您很快就会发现硬编码的尺寸和位置不会正常工作(请记住,您所看到的在您的屏幕上从不其他人在他们的屏幕上看到的)。

一个可能的解决方案是为菜单创建一个单独的 QWidget 子类,在其中添加复选框,并始终计算考虑到填充的最大高度。然后,为确保小部件框正确放置在应有的位置,您应该添加一个addStretch() 或至少一个“主小部件”(通常用于“主窗口”)。

class Menu(QWidget):
    padding = 15
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout(self)
        self.setMaximumHeight(0)
        layout.setContentsMargins(0, 15, 0, 15)

        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(11)
        font.setBold(True)
        self.setFont(font)

        self.setStyleSheet('''
        QCheckBox
        {
            background-color : rgb(25,34,52);
            border-radius : 5px;
            spacing : 10px;
            padding : 15px;
            min-height : 15px;
        }
        QCheckBox::unchecked
        {
            color : rgb(159,172,168);
        }
        QCheckBox::checked
        {
            color : rgb(217,223,227);
        }
        QCheckBox::indicator
        {
            border : 2px solid rgb(105, 139, 194);
            width : 12px;
            height : 12px;
            border-radius : 8px;
        }
        QCheckBox::indicator:checked
        {
            image : url(red.png);
            border : 2px solid rgb(221, 54, 77);
        }
        QCheckBox::hover
        {
            background-color: #263450
        }
        ''')
        self.checks = []

    def addOption(self, option):
        checkBox = QCheckBox(option)
        self.checks.append(checkBox)
        checkBox.installEventFilter(self)
        self.layout().addWidget(checkBox)
        count = len(self.checks)
        self.setMaximumHeight(count * checkBox.sizeHint().height() - 15)

    def eventFilter(self, obj, event):
        if event.type() == event.HoverMove:
            obj.raise_()
        return super().eventFilter(obj, event)


class MainWindow(QWidget):
    def __init__(self):
        # ...
        self.menu = Menu()
        self.layout.addWidget(self.menu, alignment=Qt.AlignTop)

        for i in range(4):
            self.menu.addOption('checkbox{}'.format(i + 1))

        self.layout.addStretch()

请注意,在上面的代码中,我仍然使用了事件过滤器,同时仍然使用显式的不透明背景颜色,如果您对父背景没有问题,您可以避免安装和使用事件过滤器,并且只将默认背景设置为透明rgb(0, 0, 0, 0)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-24
    • 2022-01-15
    • 2018-07-24
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 2020-05-10
    相关资源
    最近更新 更多