Pier-Justin_Mora | 2021-04-16 14:49:24 UTC | #1
Hello guys I have created a custom button and a custom bar. Both are created in a separate class. Because I want to extend it and use it like a construction plan by putting all the parts together. There is only one problem. This is also represented in the following image. The custom widgets should be lined up without a gap. How can I make it so that there is no space between the widgets?
Here the code:
from PyQt5 import QtWidgets, QtCore, QtGui, Qt
def clear(layout):
if layout is not None:
while layout.count():
child = layout.takeAt(0)
print(child)
if child.widget() is not None:
child.widget().setParent(None)
# child.widget().deleteLater() ##also deletes the content of the file
if child.layout() is not None:
# child.layout().setParent(None)
clear(child.layout())
class TopBar(QtWidgets.QWidget):
back = QtCore.pyqtSignal()
new = QtCore.pyqtSignal()
clicked = QtCore.pyqtSignal(str)
def __init__(self, name, icon_path, width, height, parent=None, **kwargs):
super().__init__(parent)
self.__color = QtGui.QColor("#215dde")
self.__name = name
self.__icon_path = icon_path
self.action_lst = []
self.__sig_lst = []
self.actual_action = None
self.__min_size = False
self.text_size = 30
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)
# self.setSizePolicy(
# # policy
# QSizePolicy.Fixed,
# QSizePolicy.Fixed
# # QSizePolicy.Maximum
# )
self.width = width
self.height = height
self.resize(self.width, self.height)
def __del__(self):
print('Object deleted.')
def paintEvent(self, event):
self.width = event.rect().width()
painter = QtGui.QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
fontText = QtGui.QFont(painter.font())
fontText.setFamily("Arial")
fontText.setPixelSize(self.text_size)
painter.setFont(fontText)
actual_width = 0
bar_rect = QtCore.QRect(0, 0, self.width, self.height)
painter.fillRect(bar_rect, QtGui.QBrush(QtGui.QColor(160, 166, 167, 255)))
image = QtGui.QPixmap(self.__icon_path)
text_height = self.height
icon_rect = QtCore.QRect(text_height*0.1, text_height*0.1, text_height*0.9, text_height*0.9)
painter.drawPixmap(icon_rect,image)
actual_width = text_height*0.9
size_text_line = QtCore.QSize(painter.fontMetrics().size(QtCore.Qt.TextSingleLine, self.__name))
text_rect = QtCore.QRect(QtCore.QPoint(actual_width + text_height*1 , text_height-size_text_line.height()), size_text_line)
painter.drawText(text_rect, QtCore.Qt.AlignBottom, self.__name)
painter.end()
class Custom_Button(QtWidgets.QWidget):
clicked = QtCore.pyqtSignal(bool)
def __init__(self, icon_path, parent=None, **kwargs):
# super().__init__()
super(Custom_Button, self).__init__(parent)
self.__color = QtGui.QColor("#215dde")
self.setMouseTracking(True)
self.__mouse_checked = False
self.__mouse_over = False
self.actual_action = None
self.__pixmap = QtGui.QPixmap(icon_path)
self.number_lines = 3
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)
self.setMouseTracking(True)
def sizeHint(self):
return QtCore.QSize(150,100)
def setText(self, text):
self.__text = text
def __del__(self):
print('Button deleted.')
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.button_width = 80
self.button_height = 80
self.resize(self.button_width, self.button_height)
painter.setBrush(QtGui.QColor(160, 166, 167, 255))
painter.setPen(QtCore.Qt.NoPen)
rect = QtCore.QRect(0, 0, self.width(), self.height())
painter.drawRect(rect)
if(self.__mouse_checked):
painter.setBrush(QtGui.QColor("#c00000"))
painter.setPen(QtGui.QColor("#c00000"))
painter.drawRect(QtCore.QRect(0,0,self.width(), self.height()))
for i in range(self.number_lines):
actual_height = (i+1) * self.height() / ((self.number_lines - 1)*2)
line = QtCore.QRect(self.width() *0.1 , actual_height, self.width() *0.8, 5)##QRect(QPoint(self.painter.device().width()*0.3 , actual_height), size_text_line)###QRect because to color the whole line not only the text or the area of the text
painter.setPen(QtGui.QColor(71, 77, 78))
painter.setBrush(QtGui.QColor(71, 77, 78))
painter.drawRect(line)##drawLine(line)
painter.end()
def mousePressEvent(self, event):
self.__mouse_checked = True
self.update()
def mouseReleaseEvent(self, event):
self.__mouse_checked = False
self.clicked.emit(True)
self.update()
def mouseMoveEvent(self, event):
self.__mouse_over = True
def leaveEvent(self, event):
self.__mouse_over = False
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None, **kwargs):
super(MainWindow, self).__init__(parent)
# def __init__(self):
super().__init__()
self.ui()
def ui(self):
self.widgets()
self.actions()
self.layout()
def widgets(self):
self.__top_bar = TopBar("Text", "Icons/dots.png", self.width(), 80, self)
self.__menu_button = Custom_Button("Icons/menu.png", self)
self.__actual_widget = QtWidgets.QFrame()
self.__first_widget = QtWidgets.QFrame()
def actions(self):
self.__menu_button.clicked.connect(self.menu_Pressed)
def menu_Pressed(self):
print("Pressed!!!")
def Back(self):
if(self.__last == "Start" or self.__last == "Calibration"):
self.new_Content(self.__first_widget)
self.new_Menu(self.__first_menu)
else:
self.menu_Action(self.__last)
def new_Content(self, widget):
if(self.__actual_widget != None):
del self.__actual_widget
self.__actual_widget=widget
clear(self.__content_layout)
self.__content_layout.addWidget(self.__actual_widget)
def layout(self):
self.__main_layout = QtWidgets.QVBoxLayout(self)
self.__main_layout.setSpacing(0)
self.__main_layout.setContentsMargins(0,0,0,0)
self.__top_layout = QtWidgets.QHBoxLayout(self)
self.__top_layout.setSpacing(0)
self.__top_layout.setContentsMargins(0,0,0,0)
self.__content_layout = QtWidgets.QHBoxLayout()
self.__content_layout.addWidget(QtWidgets.QLabel(""))
self.__actual_widget.setLayout(self.__content_layout)
self.__first_widget = self.__actual_widget
self.__top_layout.addWidget(self.__menu_button, 0, QtCore.Qt.AlignTop)
self.__top_layout.addWidget(self.__top_bar)#, 0, QtCore.Qt.AlignTop)#, alignment=QtCore.Qt.AlignLeft)
self.__top_layout.setContentsMargins(0,0,0,0)
self.__top_layout.setSpacing(0)
# self.__top_layout.addStretch()
# self.__top_layout.
self.__main_layout.addLayout(self.__top_layout)
self.__main_layout.addWidget(self.__actual_widget)
self.setLayout(self.__main_layout)
def main():
app = QtWidgets.QApplication([])
volume = MainWindow()
volume.show()
app.exec_()
if __name__ == '__main__':
main()
martin | 2021-04-23 17:27:40 UTC | #2
Hey @Pier-Justin_Mora welcome to the forum!
To change how the resizing of the widgets works you need to use size policies -- they tell Qt how the widget should scale to fill space around it (it at all) in layouts.
Purchasing Power Parity
Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]What you're asking for on the layout is for it to expand horizontally, while remaining the same height vertically. That can be achieved with.
self.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed)
In contrast the button needs to retain a fixed position at all times. You can do this with
self.setSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed)
In both cases, you need to specify a base size for the widget for these instructions to make any sense. To do this you can implement a sizeHint
method on your classes, which returns the base size for the widgets, e.g.
def sizeHint(self):
return QtCore.QSize(80,80)
If you set this size on the button, that will be the exact fixed size of the button. If you set this size on the bar, the height will be the exact fixed height of the bar, while the width will be the minimum width (from which the bar will expand upwards).
There are a few other errors in the code, for example you are assigning to self.width
(& height) which overrides the Qt self.width()
method present on all widgets. You're also calling .resize()
in the paint method -- I don't know what the intent of that is, but it's a bad idea. If that call does resize the button, it will trigger a re-draw.
Making these changes, the code works (alignment needs some work)
The modified code of the two classes is copied below.
class TopBar(QtWidgets.QWidget):
back = QtCore.pyqtSignal()
new = QtCore.pyqtSignal()
clicked = QtCore.pyqtSignal(str)
def __init__(self, name, icon_path, width, height, parent=None, **kwargs):
# super().__init__()
super(TopBar, self).__init__(parent)
self.__color = QtGui.QColor("#215dde")
self.__name = name
self.__icon_path = icon_path
self.action_lst = []
self.__sig_lst = []
self.actual_action = None
self.__min_size = False
self.text_size = 30
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)
self.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed)
def sizeHint(self):
return QtCore.QSize(80,80)
def __del__(self):
print('Object deleted.')
def paintEvent(self, event):
width = event.rect().width()
painter = QtGui.QPainter(self)
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
fontText = QtGui.QFont(painter.font())
fontText.setFamily("Arial")
fontText.setPixelSize(self.text_size)
painter.setFont(fontText)
actual_width = 0
bar_rect = QtCore.QRect(0, 0, width, self.height())
painter.fillRect(bar_rect, QtGui.QBrush(QtGui.QColor(160, 166, 167, 255)))
image = QtGui.QPixmap(self.__icon_path)
text_height = self.height()
icon_rect = QtCore.QRect(text_height*0.1, text_height*0.1, text_height*0.9, text_height*0.9)
painter.drawPixmap(icon_rect,image)
actual_width = text_height*0.9
size_text_line = QtCore.QSize(painter.fontMetrics().size(QtCore.Qt.TextSingleLine, self.__name))
text_rect = QtCore.QRect(QtCore.QPoint(actual_width + text_height*1 , text_height-size_text_line.height()), size_text_line)
painter.drawText(text_rect, QtCore.Qt.AlignBottom, self.__name)
painter.end()
class Custom_Button(QtWidgets.QWidget):
clicked = QtCore.pyqtSignal(bool)
def __init__(self, icon_path, parent=None, **kwargs):
# super().__init__()
super(Custom_Button, self).__init__(parent)
self.__color = QtGui.QColor("#215dde")
self.setMouseTracking(True)
self.__mouse_checked = False
self.__mouse_over = False
self.actual_action = None
self.__pixmap = QtGui.QPixmap(icon_path)
self.number_lines = 3
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)
self.setMouseTracking(True)
self.setSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed)
def sizeHint(self):
return QtCore.QSize(80,80)
def setText(self, text):
self.__text = text
def __del__(self):
print('Button deleted.')
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(QtGui.QColor(160, 166, 167, 255))
painter.setPen(QtCore.Qt.NoPen)
rect = QtCore.QRect(0, 0, self.width(), self.height())
painter.drawRect(rect)
if(self.__mouse_checked):
painter.setBrush(QtGui.QColor("#c00000"))
painter.setPen(QtGui.QColor("#c00000"))
painter.drawRect(QtCore.QRect(0,0,self.width(), self.height()))
for i in range(self.number_lines):
actual_height = (i+1) * self.height() / ((self.number_lines - 1)*2)
line = QtCore.QRect(self.width() *0.1 , actual_height, self.width() *0.8, 5)##QRect(QPoint(self.painter.device().width()*0.3 , actual_height), size_text_line)###QRect because to color the whole line not only the text or the area of the text
painter.setPen(QtGui.QColor(71, 77, 78))
painter.setBrush(QtGui.QColor(71, 77, 78))
painter.drawRect(line)##drawLine(line)
painter.end()
def mousePressEvent(self, event):
self.__mouse_checked = True
self.update()
def mouseReleaseEvent(self, event):
self.__mouse_checked = False
self.clicked.emit(True)
self.update()
def mouseMoveEvent(self, event):
self.__mouse_over = True
def leaveEvent(self, event):
self.__mouse_over = False
Pier-Justin_Mora | 2021-04-23 17:28:40 UTC | #3
Hi @martin,
Thank you very much for your quick and detailed answer. You helped me a lot.
Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!