有没有办法堆叠小部件并忽略重叠?
Is there a way to stack widgets and ignore overlapping?
我正在使用 PyQt5 制作 GUI,当您将鼠标悬停在小部件上时,我让小部件的背景颜色发生变化。但是,当我将鼠标悬停在一个小部件上时,它下面的小部件重叠并且背景在底部被切断。
这是因为我制作背景的方式,我不得不 space 小部件非常靠近并增加边框大小以实现背景效果,但它会像我说的那样扰乱悬停前。有什么方法可以忽略这种重叠,或者我可以使用一种不同的方法来绘制背景而不必重叠小部件?
这是我想要的截图,以防我的解释不好(底部的小部件工作正常,因为它下面没有任何东西可以重叠)
我也会在这里附上我的代码,这样你就可以看到我做了什么
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_())
问题是由于新小部件的堆叠,最简单的解决方案是为这些小部件设置 透明 背景,如果您确定它们不会不悬停时有不同的背景:
self.checkbox_style='''
QCheckBox
{
background-color : rgb(0, 0, 0, 0);
...
不幸的是,这不会真正解决您的问题,因为您的实施中存在严重的逻辑问题。
首先,基于整体布局设置布局的硬编码内容边距是一个严重的问题,原因有很多:
- 它会强制您根据菜单内容更改那些硬编码的边距;
- 它基于你的关于当前可用字体的假设,这可能会使 UI 元素部分(或完全)隐藏;
- 它不允许您向布局正确添加更多对象;
经过认真考虑以上事项(非常重要,应该永远不会被低估),问题是,虽然 Qt 允许通过 QSS 填充将项目布置在它们可能位置的“外部”,但您需要考虑小部件 z 级别,它默认堆叠一个新小部件高于任何其他先前创建的 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()
或至少一个“主小部件”(通常用于“主 window”)。
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()
请注意,在上面的代码中,我仍然使用事件过滤器,同时仍然使用显式 opaque 背景颜色,如果您对父背景没问题,则可以避免安装和使用事件过滤器,仅将默认背景设置为透明 rgb(0, 0, 0, 0)
.
我正在使用 PyQt5 制作 GUI,当您将鼠标悬停在小部件上时,我让小部件的背景颜色发生变化。但是,当我将鼠标悬停在一个小部件上时,它下面的小部件重叠并且背景在底部被切断。
这是因为我制作背景的方式,我不得不 space 小部件非常靠近并增加边框大小以实现背景效果,但它会像我说的那样扰乱悬停前。有什么方法可以忽略这种重叠,或者我可以使用一种不同的方法来绘制背景而不必重叠小部件?
这是我想要的截图,以防我的解释不好(底部的小部件工作正常,因为它下面没有任何东西可以重叠)
我也会在这里附上我的代码,这样你就可以看到我做了什么
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_())
问题是由于新小部件的堆叠,最简单的解决方案是为这些小部件设置 透明 背景,如果您确定它们不会不悬停时有不同的背景:
self.checkbox_style='''
QCheckBox
{
background-color : rgb(0, 0, 0, 0);
...
不幸的是,这不会真正解决您的问题,因为您的实施中存在严重的逻辑问题。
首先,基于整体布局设置布局的硬编码内容边距是一个严重的问题,原因有很多:
- 它会强制您根据菜单内容更改那些硬编码的边距;
- 它基于你的关于当前可用字体的假设,这可能会使 UI 元素部分(或完全)隐藏;
- 它不允许您向布局正确添加更多对象;
经过认真考虑以上事项(非常重要,应该永远不会被低估),问题是,虽然 Qt 允许通过 QSS 填充将项目布置在它们可能位置的“外部”,但您需要考虑小部件 z 级别,它默认堆叠一个新小部件高于任何其他先前创建的 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()
或至少一个“主小部件”(通常用于“主 window”)。
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()
请注意,在上面的代码中,我仍然使用事件过滤器,同时仍然使用显式 opaque 背景颜色,如果您对父背景没问题,则可以避免安装和使用事件过滤器,仅将默认背景设置为透明 rgb(0, 0, 0, 0)
.