How to make a nestable expandable widget in Qt - python

Similar to How to make an expandable/collapsable section widget in Qt I want expandable sections in my Qt app. So I've translated the example to PySide2(Qt5).
Everything looks fine until I nest expanders:
As far as my current understanding goes it seems that the maximumHeight is only calculated once (when setContentLayout is called).
I guess I would need to update parents maximumHeight when expanding via the inner animation, but I have no Idea where to start.
Nested reproduction example
from PySide2 import QtCore, QtGui, QtWidgets
class Expander(QtWidgets.QWidget):
def __init__(self, parent=None, title='', animationDuration=300):
"""
References:
# Adapted from PyQt4 version
https://stackoverflow.com/a/37927256/386398
# Adapted from c++ version
https://stackoverflow.com/a/37119983/386398
"""
super(Expander, self).__init__(parent=parent)
self.animationDuration = animationDuration
self.toggleAnimation = QtCore.QParallelAnimationGroup()
self.contentArea = QtWidgets.QScrollArea()
self.headerLine = QtWidgets.QFrame()
self.toggleButton = QtWidgets.QToolButton()
self.mainLayout = QtWidgets.QGridLayout()
toggleButton = self.toggleButton
toggleButton.setStyleSheet("QToolButton { border: none; }")
toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toggleButton.setArrowType(QtCore.Qt.RightArrow)
toggleButton.setText(str(title))
toggleButton.setCheckable(True)
toggleButton.setChecked(False)
headerLine = self.headerLine
headerLine.setFrameShape(QtWidgets.QFrame.HLine)
headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
headerLine.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# start out collapsed
self.contentArea.setMaximumHeight(0)
self.contentArea.setMinimumHeight(0)
# let the entire widget grow and shrink with its content
toggleAnimation = self.toggleAnimation
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"minimumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"maximumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, b"maximumHeight"))
# don't waste space
mainLayout = self.mainLayout
mainLayout.setVerticalSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
row = 0
mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft)
mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
row += 1
mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
self.setLayout(self.mainLayout)
def start_animation(checked):
arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward
toggleButton.setArrowType(arrow_type)
self.toggleAnimation.setDirection(direction)
self.toggleAnimation.start()
self.toggleButton.clicked.connect(start_animation)
def setContentLayout(self, contentLayout):
# Not sure if this is equivalent to self.contentArea.destroy()
self.contentArea.destroy()
self.contentArea.setLayout(contentLayout)
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = contentLayout.sizeHint().height()
for i in range(self.toggleAnimation.animationCount()-1):
expandAnimation = self.toggleAnimation.animationAt(i)
expandAnimation.setDuration(self.animationDuration)
expandAnimation.setStartValue(collapsedHeight)
expandAnimation.setEndValue(collapsedHeight + contentHeight)
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(0)
contentAnimation.setEndValue(contentHeight)
from PySide2.QtWidgets import *
app = QApplication()
window = QWidget()
layout = QVBoxLayout()
window.setLayout(layout)
layout.addWidget(QLabel('above'))
inner = QVBoxLayout()
inner.addWidget(QLabel('innertop'))
inex = Expander(title='inner')
innest = QVBoxLayout()
innest.addWidget(QLabel('content'))
inex.setContentLayout(innest)
inner.addWidget(inex)
inner.addWidget(QLabel('innerbottom'))
ex = Expander(title='outer')
ex.setContentLayout(inner)
layout.addWidget(ex)
layout.addWidget(QLabel('below'))
window.show()
app.exec_()

I ended up consuming the containers resizeEvent to update the min/max constraints.
Here's the working example
from PySide2 import QtCore, QtGui, QtWidgets
class ScrollArea(QtWidgets.QScrollArea):
resized = QtCore.Signal()
def resizeEvent(self, e):
self.resized.emit()
return super(ScrollArea, self).resizeEvent(e)
class Expander(QtWidgets.QWidget):
def __init__(self, parent=None, title='', animationDuration=300):
"""
References:
# Adapted from PyQt4 version
https://stackoverflow.com/a/37927256/386398
# Adapted from c++ version
https://stackoverflow.com/a/37119983/386398
"""
super(Expander, self).__init__(parent=parent)
self.animationDuration = animationDuration
self.toggleAnimation = QtCore.QParallelAnimationGroup()
self.contentArea = ScrollArea()
self.headerLine = QtWidgets.QFrame()
self.toggleButton = QtWidgets.QToolButton()
self.mainLayout = QtWidgets.QGridLayout()
toggleButton = self.toggleButton
toggleButton.setStyleSheet("QToolButton { border: none; }")
toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toggleButton.setArrowType(QtCore.Qt.RightArrow)
toggleButton.setText(str(title))
toggleButton.setCheckable(True)
toggleButton.setChecked(False)
headerLine = self.headerLine
headerLine.setFrameShape(QtWidgets.QFrame.HLine)
headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
headerLine.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# start out collapsed
self.contentArea.setMaximumHeight(0)
self.contentArea.setMinimumHeight(0)
self.contentArea.resized.connect(self.updateContentLayout)
# let the entire widget grow and shrink with its content
toggleAnimation = self.toggleAnimation
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"minimumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"maximumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, b"maximumHeight"))
mainLayout = self.mainLayout
mainLayout.setVerticalSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
row = 0
mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft)
mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
row += 1
mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
self.setLayout(self.mainLayout)
def start_animation(checked):
arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward
toggleButton.setArrowType(arrow_type)
self.toggleAnimation.setDirection(direction)
self.toggleAnimation.start()
self.toggleButton.clicked.connect(start_animation)
def updateContentLayout(self):
if self.toggleButton.isChecked() and self.toggleAnimation.state() != self.toggleAnimation.Running:
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = self.contentArea.layout().sizeHint().height()
self.setMinimumHeight(collapsedHeight + contentHeight)
self.setMaximumHeight(collapsedHeight + contentHeight)
self.contentArea.setMaximumHeight(contentHeight)
self.updateGeometry()
p = self.parent()
if isinstance(p, ScrollArea):
p.resized.emit()
def setContentLayout(self, contentLayout):
# Not sure if this is equivalent to self.contentArea.destroy()
self.contentArea.destroy()
self.contentArea.setLayout(contentLayout)
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = contentLayout.sizeHint().height()
for i in range(self.toggleAnimation.animationCount()-1):
expandAnimation = self.toggleAnimation.animationAt(i)
expandAnimation.setDuration(self.animationDuration)
expandAnimation.setStartValue(collapsedHeight)
expandAnimation.setEndValue(collapsedHeight + contentHeight)
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(0)
contentAnimation.setEndValue(contentHeight)
from PySide2.QtWidgets import *
app = QApplication()
window = QWidget()
layout = QFormLayout()
window.setLayout(layout)
layout.addRow(QLabel('above'))
inner = QFormLayout()
inner.addRow(QLabel('innertop'))
inex = Expander(title='inner')
innest = QFormLayout()
innest.addRow(QLabel('content'))
inex.setContentLayout(innest)
inner.addRow(inex)
inner.addRow(QLabel('innerbottom'))
ex = Expander(title='outer')
ex.setContentLayout(inner)
layout.addRow(ex)
layout.addRow(QLabel('below'))
window.show()
app.exec_()

Related

How to stop a layout in a frame from resizing when the size of the frame changes? [duplicate]

I am using PyQt4, and I am trying to create a collapsible box where it will contains a couple of child widgets where the child widgets are already created and layout using a QVboxLayout
How do I go about creating it? Currently I am unable to find any commands eg. QCollapseBox etc.
If it is not expanded:
+ Collapsible Box Header
If expanded:
- Collapsible Box Header
|- Widget01
|- Widget02
Where there is a + or - sign, or an arrow sign that can helps determine if it has been expanded or not
Using as base the logic that is implemented in the solution of #xsquared modifying certain parts we obtain the following:
PyQt4 version
from PyQt4 import QtCore, QtGui
class CollapsibleBox(QtGui.QWidget):
def __init__(self, title="", parent=None):
super(CollapsibleBox, self).__init__(parent)
self.toggle_button = QtGui.QToolButton(
text=title, checkable=True, checked=False
)
self.toggle_button.setStyleSheet("QToolButton { border: none; }")
self.toggle_button.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon
)
self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.toggle_animation = QtCore.QParallelAnimationGroup(self)
self.content_area = QtGui.QScrollArea(maximumHeight=0, minimumHeight=0)
self.content_area.setSizePolicy(
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed
)
self.content_area.setFrameShape(QtGui.QFrame.NoFrame)
lay = QtGui.QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self, b"minimumHeight")
)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self, b"maximumHeight")
)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self.content_area, b"maximumHeight")
)
#QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
self.toggle_animation.setDirection(
QtCore.QAbstractAnimation.Forward
if not checked
else QtCore.QAbstractAnimation.Backward
)
self.toggle_animation.start()
def setContentLayout(self, layout):
lay = self.content_area.layout()
del lay
self.content_area.setLayout(layout)
collapsed_height = (
self.sizeHint().height() - self.content_area.maximumHeight()
)
content_height = layout.sizeHint().height()
for i in range(self.toggle_animation.animationCount()):
animation = self.toggle_animation.animationAt(i)
animation.setDuration(500)
animation.setStartValue(collapsed_height)
animation.setEndValue(collapsed_height + content_height)
content_animation = self.toggle_animation.animationAt(
self.toggle_animation.animationCount() - 1
)
content_animation.setDuration(500)
content_animation.setStartValue(0)
content_animation.setEndValue(content_height)
if __name__ == "__main__":
import sys
import random
app = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
w.setCentralWidget(QtGui.QWidget())
dock = QtGui.QDockWidget("Collapsible Demo")
w.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dock)
scroll = QtGui.QScrollArea()
dock.setWidget(scroll)
content = QtGui.QWidget()
scroll.setWidget(content)
scroll.setWidgetResizable(True)
vlay = QtGui.QVBoxLayout(content)
for i in range(10):
box = CollapsibleBox("Collapsible Box Header-{}".format(i))
vlay.addWidget(box)
lay = QtGui.QVBoxLayout()
for j in range(8):
label = QtGui.QLabel("{}".format(j))
color = QtGui.QColor(*[random.randint(0, 255) for _ in range(3)])
label.setStyleSheet(
"background-color: {}; color : white;".format(color.name())
)
label.setAlignment(QtCore.Qt.AlignCenter)
lay.addWidget(label)
box.setContentLayout(lay)
vlay.addStretch()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
PyQt5 version
from PyQt5 import QtCore, QtGui, QtWidgets
class CollapsibleBox(QtWidgets.QWidget):
def __init__(self, title="", parent=None):
super(CollapsibleBox, self).__init__(parent)
self.toggle_button = QtWidgets.QToolButton(
text=title, checkable=True, checked=False
)
self.toggle_button.setStyleSheet("QToolButton { border: none; }")
self.toggle_button.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon
)
self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.toggle_animation = QtCore.QParallelAnimationGroup(self)
self.content_area = QtWidgets.QScrollArea(
maximumHeight=0, minimumHeight=0
)
self.content_area.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
self.content_area.setFrameShape(QtWidgets.QFrame.NoFrame)
lay = QtWidgets.QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self, b"minimumHeight")
)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self, b"maximumHeight")
)
self.toggle_animation.addAnimation(
QtCore.QPropertyAnimation(self.content_area, b"maximumHeight")
)
#QtCore.pyqtSlot()
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(
QtCore.Qt.DownArrow if not checked else QtCore.Qt.RightArrow
)
self.toggle_animation.setDirection(
QtCore.QAbstractAnimation.Forward
if not checked
else QtCore.QAbstractAnimation.Backward
)
self.toggle_animation.start()
def setContentLayout(self, layout):
lay = self.content_area.layout()
del lay
self.content_area.setLayout(layout)
collapsed_height = (
self.sizeHint().height() - self.content_area.maximumHeight()
)
content_height = layout.sizeHint().height()
for i in range(self.toggle_animation.animationCount()):
animation = self.toggle_animation.animationAt(i)
animation.setDuration(500)
animation.setStartValue(collapsed_height)
animation.setEndValue(collapsed_height + content_height)
content_animation = self.toggle_animation.animationAt(
self.toggle_animation.animationCount() - 1
)
content_animation.setDuration(500)
content_animation.setStartValue(0)
content_animation.setEndValue(content_height)
if __name__ == "__main__":
import sys
import random
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow()
w.setCentralWidget(QtWidgets.QWidget())
dock = QtWidgets.QDockWidget("Collapsible Demo")
w.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dock)
scroll = QtWidgets.QScrollArea()
dock.setWidget(scroll)
content = QtWidgets.QWidget()
scroll.setWidget(content)
scroll.setWidgetResizable(True)
vlay = QtWidgets.QVBoxLayout(content)
for i in range(10):
box = CollapsibleBox("Collapsible Box Header-{}".format(i))
vlay.addWidget(box)
lay = QtWidgets.QVBoxLayout()
for j in range(8):
label = QtWidgets.QLabel("{}".format(j))
color = QtGui.QColor(*[random.randint(0, 255) for _ in range(3)])
label.setStyleSheet(
"background-color: {}; color : white;".format(color.name())
)
label.setAlignment(QtCore.Qt.AlignCenter)
lay.addWidget(label)
box.setContentLayout(lay)
vlay.addStretch()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

why the QGridLayout do not work for QLabel

I am using QGridLayout in my project, and the code is:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
widget = QWidget()
self.setCentralWidget(widget)
layout = QGridLayout()
widget.setLayout(layout)
lay1 = QVBoxLayout()
lay1Header = QHBoxLayout()
lay1Header.addWidget(QLabel('lay1'))
lay1.addLayout(lay1Header)
label1 = QLabel('label1')
label1.setStyleSheet('background: rgb(255, 0, 0)')
lay1.addWidget(label1)
lay2 = QVBoxLayout()
lay2Header = QHBoxLayout()
lay2Header.addWidget(QLabel('lay2'))
lay2Header.addWidget(QLineEdit())
lay2.addLayout(lay2Header)
label2 = QLabel('label2')
label2.setStyleSheet('background: rgb(0, 0, 255)')
lay2.addWidget(label2)
layout.addLayout(lay1, 0, 0, 1, 1)
layout.addLayout(lay2, 0, 1, 1, 1)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
And the result is:
My environment is:
win 10
anaconda
pyqt5 5.14.0
How can I make the size of label1/label2 the same?
The size of the items in a QGridLayout depends on all the items in the column or row, not just one of them. So for this case the solution is to set the same alignment factor for the columns and set the height of the first QLabel to be that of the QLineEdit:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
widget = QWidget()
self.setCentralWidget(widget)
layout = QGridLayout(widget)
lbl1 = QLabel("lay1")
lay1 = QVBoxLayout()
lay1Header = QHBoxLayout()
lay1Header.addWidget(lbl1)
lay1.addLayout(lay1Header)
label1 = QLabel("label1")
label1.setStyleSheet("background: rgb(255, 0, 0)")
lay1.addWidget(label1)
le1 = QLineEdit()
lay2 = QVBoxLayout()
lay2Header = QHBoxLayout()
lay2Header.addWidget(QLabel("lay2"))
lay2Header.addWidget(le1)
lay2.addLayout(lay2Header)
label2 = QLabel("label2")
label2.setStyleSheet("background: rgb(0, 0, 255)")
lay2.addWidget(label2)
layout.addLayout(lay1, 0, 0, 1, 1)
layout.addLayout(lay2, 0, 1, 1, 1)
layout.setColumnStretch(0, 1)
layout.setColumnStretch(1, 1)
lbl1.setFixedHeight(le1.sizeHint().height())

Message with rounded corners with Qt

I'm trying to create a widget like the message box at the bottom in this picture:
It should overlay the main widget. I have 2 ways to do this:
With a QFrame and rounded corners
With a mask
But there's a problem with each of them:
The QFrame approach seems like a better idea but the background isn't transparent. This means that even though the borders have a radius, the background is still making it a rectangle. It's somewhat noticeable in the picture. Unfortunately, this doesn't seem to work. Same for self.setStyleSheet("background:transparent").
The mask looks very pixelated, and it's not the expected behavior because the mask used can only be a simple QRegion. These don't have squircles, which would be ideal.
Here's what the code looks like:
class Message(QFrame):
"""
A temporary message to show information in the GUI.
"""
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
# Main layout
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
# The label
label = QLabel(msg)
label.setFont(Fonts.text)
label.setStyleSheet(f"color: {Colors.fg};"
"padding: 20px;")
self.layout.addWidget(label)
self.setStyleSheet(f"background-color: {Colors.bg};"
"border-radius: 30px;")
# region = QRegion(self.x(), self.y(), self.sizeHint().width(),
# self.sizeHint().height(), QRegion.Ellipse)
# self.setMask(region)
self.adjustSize()
Edit for S. Nick: Your solution only works if the Message widget is the only widget in the application. The intended usage is this:
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
msg = Message("I can't make this work", self)
where player is the main widget, and the message overlays it when it appears. Imagine it being an image, which is overlayed by the message. Sorry for not explaining myself correctly.
Try it:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QWidget): #(QFrame): #
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) # <---
self.setAttribute(Qt.WA_TranslucentBackground) # <---
Colors_fg = "#fa0"
Colors_bg = "rgba( 155, 155, 155, 150)" # <---
# Main layout
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
# The label
label = QLabel(msg, alignment=Qt.AlignCenter)
label.setFont(QFont("Times", 17, QFont.Bold, italic=True)) #(Fonts.text)
label.setStyleSheet(f"color: {Colors_fg};"
"padding: 0px;")
self.layout.addWidget(label)
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
self.setStyleSheet(f"background-color: {Colors_bg};"
"min-height: 70px;"
"max-height: 70px;"
"width: 200px;"
"border-radius: 30px;"
)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
self.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Message("Hello \nWorld")
w.resize(400, 200)
w.show()
sys.exit(app.exec_())
Update
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QDialog): #(QFrame):
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
self.setWindowFlags(self.windowFlags() |
Qt.FramelessWindowHint |
Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.widget = QWidget(self)
self.widget.setObjectName('Custom_Widget')
layout = QVBoxLayout(self)
layout.addWidget(self.widget)
self.layout = QGridLayout(self.widget)
self.layout.addItem(QSpacerItem(
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0)
self.layout.addWidget(QPushButton('r', self,
clicked=self.accept,
objectName='closeButton'), 0, 1)
# The label
label = QLabel(msg)
label.setFont(QFont("Times", 17, QFont.Bold, italic=True)) #((Fonts.text)
self.layout.addWidget(label, 2, 0, 5, 2, alignment=Qt.AlignCenter)
self.adjustSize()
def mousePressEvent(self, event):
self.old_Pos = event.globalPos()
self.old_width = self.width()
self.old_height = self.height()
def mouseMoveEvent(self, event):
if (event.buttons() == Qt.LeftButton):
delta = QPoint (event.globalPos() - self.old_Pos)
if (self.old_Pos.x() > self.x() + self.old_width - 20) or \
(self.old_Pos.y() > self.y() + self.old_height - 20):
w = self.old_width+delta.x() if self.old_width+delta.x() > 500 else 500
h = self.old_height+delta.y() if self.old_height+delta.y() > 400 else 400
self.setFixedSize(w, h)
else:
self.move(self.x() + delta.x(), self.y() + delta.y())
self.old_Pos = event.globalPos()
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
self.msg = Message("I can't make this work")#, self)
self.msg.show()
Stylesheet = """
#Custom_Widget {
background: rgba( 155, 155, 155, 150);
border-radius: 20px;
border: 2px solid #ff2025;
}
#closeButton {
min-width: 36px;
min-height: 36px;
font-family: "Webdings";
qproperty-text: "r";
border-radius: 10px;
}
#closeButton:hover {
color: #ccc;
background: red;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(Stylesheet)
w = MainWindow(QPushButton("player"))
w.resize(400, 200)
w.show()
sys.exit(app.exec_())
Update 2
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QFrame): # QDialog
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
# self.setWindowFlags(self.windowFlags() |
# Qt.FramelessWindowHint #|Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.widget = QWidget(self)
self.widget.setObjectName('Custom_Widget')
layout = QVBoxLayout(self)
layout.addWidget(self.widget)
self.layout = QGridLayout(self.widget)
self.layout.addItem(QSpacerItem(
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0)
self.layout.addWidget(QPushButton('r', self,
# clicked=self.accept,
clicked=self.close,
objectName='closeButton'), 0, 1)
# The label
label = QLabel(msg)
label.setFont(QFont("Times", 15, QFont.Bold, italic=True)) #((Fonts.text)
self.layout.addWidget(label, 2, 0, 5, 2, alignment=Qt.AlignCenter)
self.adjustSize()
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
player.clicked.connect(self._msg)
def _msg(self):
self.msg = Message("I can't make this work\nself.msg.setGeometry(10, 10, 480, 150)", self)
self.msg.setGeometry(10, 10, 480, 150)
self.msg.show()
Stylesheet = """
#Custom_Widget {
background: rgba( 155, 155, 155, 150);
border-radius: 20px;
border: 2px solid #ff2025;
}
#closeButton {
min-width: 36px;
min-height: 36px;
font-family: "Webdings";
qproperty-text: "r";
border-radius: 10px;
}
#closeButton:hover {
color: #ccc;
background: red;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(Stylesheet)
w = MainWindow(QPushButton("player"))
w.resize(500, 400)
w.show()
sys.exit(app.exec_())

Qlabel in front of a QGridLayout

I am trying to make a QLabel appear over a QGridLayout, but I cannot figure how to do it.
This is a sample code:
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QFrame, QLabel
import sys
class Foo(QWidget):
def __init__(self):
super().__init__()
grid_layout = QGridLayout()
rect1 = QLabel('RECT1')
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 1)
rect2 = QLabel('RECT2')
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
self.setLayout(grid_layout)
self.show()
app = QApplication(sys.argv)
foo = Foo()
sys.exit(app.exec_())
which produces the following output:
For instance, I want to create another QLabel in red, and display it over them, in the center of the image:
red_label = QLabel('red')
red_labe.setStyleSheet("font-size:20pt; color: red;");
Something like this:
How can I achieve that?
A possible solution is to make red_label son of the QWidget for it you must pass the self parameter when the object is created. In addition to this the QLabel must change size when the window does, emulating the layout task for it will create a signal that will be emited in the resizeEvent event:
import sys
from PyQt5 import QtCore, QtWidgets
class Foo(QtWidgets.QWidget):
sizeChanged = QtCore.pyqtSignal(QtCore.QSize)
def __init__(self):
super().__init__()
grid_layout = QtWidgets.QGridLayout(self)
rect1 = QtWidgets.QLabel("RECT1")
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 1)
rect2 = QtWidgets.QLabel("RECT2")
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
red_label = QtWidgets.QLabel("red", self)
red_label.setAlignment(QtCore.Qt.AlignCenter)
red_label.setStyleSheet("font-size: 20pt; color: red;")
self.sizeChanged.connect(red_label.resize)
def resizeEvent(self, event):
self.sizeChanged.emit(event.size())
super().resizeEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
foo = Foo()
foo.show()
sys.exit(app.exec_())
This may be a little less elegant of a solution, but it does keep the red labeled centered as the window is resized.
class Foo(QWidget):
def __init__(self):
super().__init__()
grid_layout = QGridLayout()
rect1 = QLabel('RECT1')
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 0)
rect2 = QLabel('RECT2')
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
grid_layout_two = QGridLayout()
blank_label = QLabel()
red_label = QLabel('red')
red_label.setStyleSheet("font-size:20pt; color: red;")
grid_layout_two.addWidget(blank_label, 0, 0)
grid_layout_two.addWidget(red_label, 0, 1)
grid_layout_two.addWidget(blank_label, 0, 2)
grid_layout_three = QGridLayout()
grid_layout_three.addItem(grid_layout, 0, 0)
grid_layout_three.addItem(grid_layout_two, 0, 0)
self.setLayout(grid_layout_three)
self.show()
app = QApplication(sys.argv)
foo = Foo()
sys.exit(app.exec_())
Basically making three grid layouts, positioning the elements so that the red label is in the center of the other two labels but in a grid layout that is in front of the other labels layout.

Cannot QWidget margins, empty space between widgets

I have a problem with empty space between widgets in my QT code.
I tried with padding, margins but nothing...
Tried to set styles and setMargin on QWidget, QVBoxLayout but without any result.
Screenshot: http://imageshack.us/a/img515/4796/gami.png
def __init__(self, GamiObject):
""" Create initial window to put contents of installer to (layout) """
self.Gami = GamiObject
self.app = QApplication(sys.argv)
self.window = QWidget()
self.window.setWindowTitle("Instalator systemu Gentoo Linux")
self.window.setMinimumSize(600, 350)
self.layout = QVBoxLayout()
self.layout.setMargin(0)
# Title layout {
TLay = QWidget()
TLay.setAttribute(Qt.WA_TranslucentBackground, True)
TLay.setStyleSheet("margin: 0; padding: 0; background-color: rgb(20,20,20, 50%);")
TLay.setMaximumHeight(80)
TLay.setMinimumHeight(80)
TLayStyle = QHBoxLayout()
#TLayStyle.setAttribute(Qt.WA_TranslucentBackground, True)
TLayStyle.addWidget(QLabel("Etap 1/4 - wybor lokalizacji"))
TLay.setLayout(TLayStyle)
# Title layout }
# Empty space {
self.Content = QFrame()
# Empty Space }
# Down layout {
HBox = QWidget()
HBoxLayout = QHBoxLayout()
HBoxLayout.setSpacing(10)
HBoxLayout.setMargin(0)
buttonBack = QPushButton("Wstecz")
buttonNext = QPushButton("Dalej")
# Signals
self.window.connect(buttonBack, SIGNAL("clicked()"), self.Gami.previousStep)
self.window.connect(buttonNext, SIGNAL("clicked()"), self.Gami.nextStep)
self.Gami.Hooking.connectHook("Gami.previousStep", self.buttonPrevious)
self.Gami.Hooking.connectHook("Gami.nextStep", self.buttonNext)
#buttonBack.setMinimumWidth(50)
buttonBack.setMaximumWidth(100)
buttonBack.setMinimumWidth(90)
buttonBack.setMinimumHeight(45)
buttonBack.setDisabled(True)
#buttonNext.setMinimumWidth(50)
buttonNext.setMaximumWidth(100)
buttonNext.setMinimumWidth(90)
buttonNext.setMinimumHeight(45)
HBoxLayout.addWidget(buttonBack, 1, alignment=Qt.Alignment(2))
HBoxLayout.addWidget(buttonNext, 0, alignment=Qt.Alignment(2))
HBox.setLayout(HBoxLayout)
HBox.setStyleSheet("margin: 4px;")
# Down layout }
self.layout.addWidget(TLay)
self.layout.addWidget(self.Content, 1)
self.layout.addWidget(HBox)
self.window.setLayout(self.layout)
self.window.show()
self.app.exec_()

Categories

Resources