PyQt6/PySide6: how to stack QWidgets on top of each other? - python

I'm trying to create a custom window (namely a custom bar with close buttons, etc.) and I'm using the following code for this:
from PySide6.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout
from PySide6.QtCore import Qt, QSize, QPoint, QRect
from PySide6.QtGui import QIcon
class CustomBar(QWidget):
def init(self, main_window):
super().init()
self.main_window = main_window
self.layout = QHBoxLayout()
self.buttons_layout = QHBoxLayout()
self.buttons_layout.setContentsMargins(0, 0, 0, 0)
self.buttons_layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setStyleSheet("background-color: rgb(60, 63, 65);")
# ------- title -------
self.title = QLabel("MyApp")
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title.setFixedWidth(200)
# ------- minimize button -------
self.minimize_button = QPushButton()
self.minimize_button.setIcon(QIcon("media\\ui\\icons\\enabled_button.png"))
self.minimize_button.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
self.minimize_button.setIconSize(QSize(16, 16))
self.minimize_button.setFixedSize(QSize(16, 16))
self.minimize_button.clicked.connect(self.minimize_window)
self.buttons_layout.addWidget(self.minimize_button)
# ------- close button -------
self.close_button = QPushButton()
self.close_button.setIcon(QIcon("media\\ui\\icons\\disabled_button.png"))
self.close_button.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
self.close_button.setIconSize(QSize(16, 16))
self.close_button.setFixedSize(QSize(16, 16))
self.close_button.clicked.connect(self.close_window)
self.buttons_layout.addWidget(self.close_button)
self.layout.addWidget(self.title)
self.layout.addLayout(self.buttons_layout)
self.setLayout(self.layout)
self.current_mouse_position = QPoint(0, 0)
def minimize_window(self):
self.main_window.showMinimized()
def close_window(self):
self.main_window.close()
def mousePressEvent(self, event):
self.current_mouse_position = self.mapToGlobal(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
new_mouse_position = self.mapToGlobal(event.pos())
mouse_movement = new_mouse_position - self.current_mouse_position
self.current_mouse_position = new_mouse_position
window_pos = QPoint(self.main_window.geometry().x(), self.main_window.geometry().y())
new_window_pos = window_pos + mouse_movement
self.main_window.setGeometry(QRect(new_window_pos, self.main_window.size()))
And this is the result of this:
For clarity, I will add a stroke to each bar element.
I also added the Qt.WA_TranslucentBackground attribute to the main window to make it transparent and then rounded the corners.
The problem is this: as you can see in the screenshots, the close buttons and the title have different sizes and if I set them to gray too, it will be a jagged line, moreover, the buttons have a transparent background, while the title has a gray one.
I need to leave it like this.
Is it possible to create a placeholder widget, set styles (color, rounded corners) for it, and place the title and buttons on it? Approximately like this:

Related

Pyqt margin padding setting invalid in parent-children widget [duplicate]

I have set up an image button with some text, but the image does not align with the text due to some extra margins. How can I get rid of these margins? I have tried setting setContentsMargins(0,0,0,0) and setSpacing(0) on various components but it doesn't seem to affect the correct margins.
Here is a demo:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class ImageButton(QWidget):
def __init__(self, img_location):
QWidget.__init__(self)
self.img_location = img_location
self.button = QToolButton(self)
self.button.clicked.connect(self.handleButton)
self.button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.button.setIcon(QIcon(img_location))
self.button.setIconSize(QSize(300,400))
layout = QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
print(self.img_location)
app = QApplication([])
window = QMainWindow()
window.resize(800,600)
label_title = QLabel("bob asdfjak f ajksf asljf ajdslf aldskj ksf kslfhadjks lfhiu sofhjaklfsiuod fahklfhadisufaksufhdsuifhosa fasdf afsda")
label_title.setStyleSheet('background-color: yellow')
label_title.setAlignment(Qt.AlignCenter)
label_title.adjustSize()
label_title.setWordWrap(True)
imgbtn = ImageButton("a.png")
imgbtn.setStyleSheet('background-color: green')
layout_box = QVBoxLayout()
layout_box.setContentsMargins(0,0,0,0)
layout_box.setSpacing(0)
layout_box.addWidget(imgbtn, 0, Qt.AlignTop)
layout_box.addWidget(label_title, 0, Qt.AlignTop)
layout_box.setAlignment(Qt.AlignCenter)
layout_box.setSpacing(0)
content = QWidget()
content.setStyleSheet('background-color: blue')
content.setFixedWidth(300)
content.setFixedHeight(400)
content.setLayout(layout_box)
window.setCentralWidget(content)
window.show()
app.exec_()
The Yellow region marks the text label, the Green region marks the imagebutton, and the Blue region marks the space I'm trying to get rid of. See how the yellow region expands to the size of the blue, with the end result that the text doesn't align to the imagebutton.
How can I get rid of this blue region?
That margin is that of the QVBoxLayout that you use to place the QToolButton inside ImageButton, so the solution is to set it to 0 that margin:
# ...
class ImageButton(QWidget):
def __init__(self, img_location):
super().__init__(self)
# ...
layout = QVBoxLayout(self)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
# ...

Can't remove margins from QVBoxLayout

I have set up an image button with some text, but the image does not align with the text due to some extra margins. How can I get rid of these margins? I have tried setting setContentsMargins(0,0,0,0) and setSpacing(0) on various components but it doesn't seem to affect the correct margins.
Here is a demo:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class ImageButton(QWidget):
def __init__(self, img_location):
QWidget.__init__(self)
self.img_location = img_location
self.button = QToolButton(self)
self.button.clicked.connect(self.handleButton)
self.button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.button.setIcon(QIcon(img_location))
self.button.setIconSize(QSize(300,400))
layout = QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
print(self.img_location)
app = QApplication([])
window = QMainWindow()
window.resize(800,600)
label_title = QLabel("bob asdfjak f ajksf asljf ajdslf aldskj ksf kslfhadjks lfhiu sofhjaklfsiuod fahklfhadisufaksufhdsuifhosa fasdf afsda")
label_title.setStyleSheet('background-color: yellow')
label_title.setAlignment(Qt.AlignCenter)
label_title.adjustSize()
label_title.setWordWrap(True)
imgbtn = ImageButton("a.png")
imgbtn.setStyleSheet('background-color: green')
layout_box = QVBoxLayout()
layout_box.setContentsMargins(0,0,0,0)
layout_box.setSpacing(0)
layout_box.addWidget(imgbtn, 0, Qt.AlignTop)
layout_box.addWidget(label_title, 0, Qt.AlignTop)
layout_box.setAlignment(Qt.AlignCenter)
layout_box.setSpacing(0)
content = QWidget()
content.setStyleSheet('background-color: blue')
content.setFixedWidth(300)
content.setFixedHeight(400)
content.setLayout(layout_box)
window.setCentralWidget(content)
window.show()
app.exec_()
The Yellow region marks the text label, the Green region marks the imagebutton, and the Blue region marks the space I'm trying to get rid of. See how the yellow region expands to the size of the blue, with the end result that the text doesn't align to the imagebutton.
How can I get rid of this blue region?
That margin is that of the QVBoxLayout that you use to place the QToolButton inside ImageButton, so the solution is to set it to 0 that margin:
# ...
class ImageButton(QWidget):
def __init__(self, img_location):
super().__init__(self)
# ...
layout = QVBoxLayout(self)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
# ...

Possible rendering issue with QScrollArea and QPainter

I'm trying to create a character map visualization tool with PyQt5. With fontTools library, I'm extracting the UNICODE code points supported in a given ttf file. Then using QPainter.drawText I'm drawing the glyphs on labels. The labels are stored in a QGridLayout and the layout is in a QScrollArea
Everything works fine, except when I try to scroll. The drawn images are overlapped whenever I try to scroll too fast. It looks like this.
The labels are redrawn properly the moment the window loses focus.
Here's an MWE of what I've so far.
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFontDatabase, QFont, QColor, QPainter
from fontTools.ttLib import TTFont
class App(QWidget):
def __init__(self):
super().__init__()
self.fileName = "mukti.ttf" #the ttf file is located in the same path as the script
self.initUI()
def initUI(self):
self.setWindowTitle("Glyph Viewer")
self.setFixedSize(640, 480)
self.move(100, 100)
vBox = QtWidgets.QVBoxLayout()
self.glyphView = GlyphView()
vBox.addWidget(self.glyphView)
self.setLayout(vBox)
self.showGlyphs()
self.show()
def showGlyphs(self):
#Using the fontTools libray, the unicode blocks are obtained from the ttf file
font = TTFont(self.fileName)
charMaps = list()
for cmap in font['cmap'].tables:
charMaps.append(cmap.cmap)
charMap = charMaps[0]
fontDB = QFontDatabase()
fontID = fontDB.addApplicationFont("mukti.ttf")
fonts = fontDB.applicationFontFamilies(fontID)
qFont = QFont(fonts[0])
qFont.setPointSize(28)
self.glyphView.populateGrid(charMap, qFont)
class GlyphView(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
def populateGrid(self, charMap, qFont):
glyphArea = QtWidgets.QWidget(self)
gridLayout = QtWidgets.QGridLayout()
glyphArea.setLayout(gridLayout)
row, col = 1, 1
for char in charMap:
uni = charMap[char]
gridLayout.addWidget(Glyph(qFont, chr(char)), row, col)
if not col % 4:
col = 1
row += 1
else:
col += 1
self.setWidget(glyphArea)
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__()
self.font = font
self.char = char
self.initUI()
def initUI(self):
self.setFixedSize(48, 48)
self.setToolTip(self.char)
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font)
qp.setPen(QColor(255, 255, 255))
qp.drawText(event.rect(), Qt.AlignCenter, self.char)
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I'm not sure what is causing this. Any help is appreciated!
The paintEvent() method gives us an object that belongs to QPaintEvent, that object provides a QRect through the rect() method, that QRect is the area that is currently visible, and that information could be used to optimize the painting, for example let's say we have a widget that shows texts in several lines, if the text is large few lines will look so painting all is a waste of resources, so with a proper calculation using the aforementioned QRect we could get those lines and paint that part spending few resources. In this answer I show an example of the use of event.rect().
In your case there is no need to use event.rect() since you would be painting the text in a part of the widget widget. what you should use is self.rect():
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font())
qp.setPen(QColor(255, 255, 255))
# change event.rect() to self.rect()
qp.drawText(self.rect(), Qt.AlignCenter, self.text())
I also see unnecessary to overwrite paintEvent() method since you can point directly to the QLabel the font, the text and the alignment:
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__(font=font, text=char, alignment=Qt.AlignCenter, toolTip=char)

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.

PySide Frame doesn't appear on Grid Layout

I'm trying to add a simple square to a grid layout, but it doesn't seem to work.
Here is the code:
self.resultFrame = QFrame()
self.resultFrame.setGeometry(100, 200, 0, 0)
self.resultFrame.setStyleSheet("QWidget { background-color: #000 }")
gridLayout.addWidget(self.resultFrame, 0, 0, 1, 4)
If I switch self.resultFrame to, for example, a QLabel or a QPushButton, it seems to work fine, but not with QFrame.
What could I be doing wrong?
It's hard to make out what you could be doing wrong since we don't see the rest of the code, but at least I can confirm that this simple example draws a black frame and button inside a grid layout.
from PySide import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent = None):
super(MyWindow, self).__init__(parent)
self.resultFrame = QtGui.QFrame()
self.resultFrame.setGeometry(100, 200, 0, 0)
self.resultFrame.setStyleSheet("QFrame { background-color: #000 }")
self.myButton = QtGui.QPushButton(self, 'test')
gridLayout = QtGui.QGridLayout()
gridLayout.addWidget(self.resultFrame, 0, 0)
gridLayout.addWidget(self.myButton, 0, 1)
self.setLayout(gridLayout)
self.resize(400, 400)
self.show()
win = MyWindow()
It could also be the way you use spans when using the grid layout's addWidget method with the rest of your items. For example, if I used gridLayout.addWidget(self.resultFrame, 0, 0, 1 ,4) in the code above the button will no longer be in view!

Categories

Resources