How to build a flat groupbox in PyQt5? - qt

I found the flat property of the QGroupbox Class in the Qt-docs, where it says:
A group box usually consists of a surrounding frame with a title at the top. If this property is enabled, only the top part of the frame is drawn in most styles; otherwise, the whole frame is drawn.
I'm trying to build such a flat QGroupBox, but it doesn't seem to work, as you can see in this picture. In the docs it also says:
Note: In some styles, flat and non-flat group boxes have similar representations and may not be as distinguishable as they are in other styles.
That leads me to the QStyle Class, which overstrains me a little, as I have no idea how to change the Style of the QGroupBox. I'm not even sure, if this is the real problem here. As you can see in my code below, I don't assign a specific style to the QGroupBox. So, it should use some sort of standard style, i guess...
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox,
QVBoxLayout, QLabel
class WidgetWithFlatGroupBox(QWidget):
def __init__(self):
QWidget.__init__(self, flags=Qt.Widget)
self.init_ui()
def init_ui(self):
self.setGeometry(300, 300, 300, 100)
self.setWindowTitle('PyQt5 Window')
layout = QVBoxLayout()
layout.addWidget(self.create_groupbox(), alignment=Qt.AlignCenter)
self.setLayout(layout)
def create_groupbox(self):
groupbox = QGroupBox('Flat Groupbox')
layout = QVBoxLayout()
label = QLabel('This Groupbox should be flat.')
layout.addWidget(label, alignment=Qt.AlignCenter)
groupbox.setLayout(layout)
groupbox.setFlat(True)
return groupbox
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = WidgetWithFlatGroupBox()
ex.show()
sys.exit(app.exec_())
I had a look at the internet, also here at stackoverflow of course, but were not able to find something useful. Is there anyone who knows how to use the flat property of the QGroupBox Class properly?

Sorry, but I do not understand how you need to do:
border: None;
border: 1px solid #76797C;
Try it:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QWidget, QGroupBox,
QVBoxLayout, QLabel, QPushButton)
class WidgetWithFlatGroupBox(QWidget):
def __init__(self):
QWidget.__init__(self, flags=Qt.Widget)
self.init_ui()
def init_ui(self):
self.setGeometry(300, 300, 300, 100)
self.setWindowTitle('PyQt5 Window')
layout = QVBoxLayout()
layout.addWidget(self.create_groupbox(), alignment=Qt.AlignCenter)
self.setLayout(layout)
def create_groupbox(self):
groupbox = QGroupBox('Flat Groupbox')
layout = QVBoxLayout()
label = QLabel('This Groupbox should be flat.')
button = QPushButton("This Button !!!")
button.setFlat(True)
layout.addWidget(label, alignment=Qt.AlignCenter)
layout.addWidget(button, alignment=Qt.AlignCenter)
groupbox.setLayout(layout)
# groupbox.setFlat(True)
return groupbox
style = '''
QGroupBox {
border: None; /*<----- None */
/* border: 1px solid #76797C; <----- 1px solid #76797C */
border-radius: 2px;
margin-top: 20px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top center;
padding-left: 10px;
padding-right: 10px;
padding-top: 10px;
}
'''
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style)
ex = WidgetWithFlatGroupBox()
ex.show()
sys.exit(app.exec_())

self.groupBox_33.setFlat(True)

Related

Border of a Frame is not shown

This is the relevant code of the frame
self.frame = QtWidgets.QFrame(self)
self.frame.setFrameShape(QtWidgets.QFrame.Box)
self.frame.setGeometry(QtCore.QRect(20, 710, 381, 121))
self.frame.setLineWidth(2)
self.frame.setMidLineWidth(0)
self.frame.setStyleSheet("border-color:white")
self.frame.setObjectName("frame")
Problem is that the border of the frame is not shown in the window
You should add it in one layout like QVBoxLayout, You created a frame but never add it to any layout, so it doesn't show.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget,QFrame,QVBoxLayout
from PySide6 import QtGui
from PySide6 import QtCore
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
self.setGeometry(0,0,600,400)
self.frame = QFrame(self)
self.frame.setFrameShape(QFrame.Box)
self.frame.setGeometry(QtCore.QRect(20, 710, 381, 121))
self.frame.setLineWidth(2)
self.frame.setMidLineWidth(0)
self.frame.setStyleSheet("border-color:white")
self.frame.setObjectName("frame")
layout=QVBoxLayout()
layout.addWidget(self.frame)
self.setLayout(layout)
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
output:
you can also use self.setCentralWidget(self.frame) if you use QMainwindow.
Setting the border color is insufficient, as you need to specify all border properties: width, style and color.
You should also always use selectors for widgets that can be containers.
The correct stylesheet would be:
QFrame#frame {
border: 2px solid white;
}
Note: you should always use layout managers, even in Designer.

Change highlight color for a specific item (QAction type) on a QMenuBar

I want to mimic the behavior of the window top-right corner actions when hovering over the minimize-maximize-close buttons.
So I have created a QMenuBar that contains 3 QActions.
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
minimize_action.setIcon(_MINIMIZE_ICON)
maximize_action = QAction(self)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setIcon(_CLOSE_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
And set the background color when hovering with stylesheet (it is applied to the entire QApplication):
I want the close button to have a different highlight color, so I tried setting a boolean property on the exit_action to change the color only for that specific item, but it does not do any effect (in fact, if I add the property in the stylesheet it doesn't even consider the stylesheet values)
Any idea of how to achieve this?
To extend the question, I would not only use this for the minimize-maximize-close actions, but also to understand how to apply different hover/selected colors on QActions in the QMenuBar (not in the QMenu, which I already found a solution for that).
This is an running example:
from PySide2.QtWidgets import QMenuBar, QAction, QStyle, QApplication, QMainWindow
from PySide2.QtCore import Qt
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize_action.setIcon(_MINIMIZE_ICON)
minimize_action.setProperty('exit_action', False)
maximize_action = QAction(self)
maximize_action.setProperty('exit_action', False)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setProperty('exit_action', True)
exit_action.setIcon(_EXIT_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
self.setStyleSheet(
'QMenuBar::item:selected {'
'background-color: grey;'
'}'
'QMenuBar::item[exit_action=true]:selected {'
' background-color: red;'
'}')
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setMenuBar(WindowMenuActions())
main.show()
sys.exit(app.exec_())
To anyone interested, I sorted by inheriting from a regular QWidget instead of a QMenuBar and using QToolButton instead of QAction.
Working example:
from PySide2.QtWidgets import QMenuBar, QStyle, QApplication, QMainWindow, QWidget, QHBoxLayout, QToolButton, QMenu
from PySide2.QtCore import Qt
class MenuBar(QMenuBar):
def __init__(self):
super(MenuBar, self).__init__()
self.addMenu(QMenu('File'))
self.addMenu(QMenu('Help'))
self.setCornerWidget(WindowMenuActions(self))
class WindowMenuActions(QWidget):
def __init__(self, parent=None):
super(WindowMenuActions, self).__init__(parent)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize = QToolButton(self)
minimize.setIcon(_MINIMIZE_ICON)
maximize = QToolButton(self)
maximize.setIcon(_MAXIMIZE_ICON)
exit_action = QToolButton(self)
exit_action.setProperty('exit_button', True)
exit_action.setIcon(_EXIT_ICON)
layout = QHBoxLayout()
layout.addWidget(minimize)
layout.addWidget(maximize)
layout.addWidget(exit_action)
self.setLayout(layout)
self.setStyleSheet(
'QToolButton:hover {'
' background: grey;'
'}'
'QToolButton[exit_button=true]:hover {'
' background: red;'
'}'
)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setWindowFlags(Qt.FramelessWindowHint)
main.setMenuBar(MenuBar())
main.show()
sys.exit(app.exec_())

PyQt5 QGroupBox - how to display title in line with the top outside line for the QGroupBox?

This example:
# test1.py
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QRadioButton, \
QGroupBox, QButtonGroup
from PyQt5.Qt import Qt
def main():
app = QApplication([])
mainForm = MainForm()
mainForm.show()
app.exec()
# end function
class MainForm(QWidget):
def __init__(self):
super().__init__()
self.rdoRed = QRadioButton('Red')
self.rdoGreen = QRadioButton('Green')
self.rdoBlue = QRadioButton('Blue')
self.vblColor = QVBoxLayout()
self.vblColor.addWidget(self.rdoRed)
self.vblColor.addWidget(self.rdoGreen)
self.vblColor.addWidget(self.rdoBlue)
self.gbColor = QGroupBox('Choose Color')
self.gbColor.setStyleSheet('QGroupBox:title {'
'subcontrol-origin: padding; }')
self.gbColor.setLayout(self.vblColor)
self.vboxLayout = QVBoxLayout()
self.vboxLayout.addWidget(self.gbColor)
self.setLayout(self.vboxLayout)
# end function
# end class
if __name__ == '__main__':
main()
Renders like this on Ubuntu 18.04:
I'd like the Choose Color text currently just above the top of the QGroupBox boundary line to instead be in line with the top of the QGroupBox boundary, like the Qt docs show:
Notice how Package selection is in line with the top boundary line of the QGroupBox, I'd like to do the same with the Choose Color title in the example I have above.
Is this an operating system limitation and what I'm asking isn't possible? I'm using Ubuntu 18.04.
Or is there a setStyleSheet parameter I can pass in to achieve this? As you can see in the example above I attempted to set some style sheet properties to achieve this but I was unable to find such a property. Or is there some other means with which this can be achieved?
Try it:
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QRadioButton, \
QGroupBox, QButtonGroup
from PyQt5.Qt import *
class MainForm(QWidget):
def __init__(self):
super().__init__()
self.rdoRed = QRadioButton('Red')
self.rdoGreen = QRadioButton('Green')
self.rdoBlue = QRadioButton('Blue')
self.vblColor = QVBoxLayout()
self.vblColor.addWidget(self.rdoRed)
self.vblColor.addWidget(self.rdoGreen)
self.vblColor.addWidget(self.rdoBlue)
self.gbColor = QGroupBox('Choose Color')
self.gbColor.setStyleSheet('''
QGroupBox {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E0E0E0, stop: 1 #FFFFFF);
border: 2px solid #999999;
border-radius: 5px;
margin-top: 2ex; /*leave space at the top for the title */
font-size: 13px;
color: black;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top center; /* position at the top center */
padding: 0 3px;
font-size: 13px;
color: black;
}
''')
self.gbColor.setLayout(self.vblColor)
self.vboxLayout = QVBoxLayout()
self.vboxLayout.addWidget(self.gbColor)
self.setLayout(self.vboxLayout)
def main():
app = QApplication([])
mainForm = MainForm()
mainForm.show()
app.exec()
if __name__ == '__main__':
main()

Qt text alignment in QCheckBox

I am looking for a way to align text in a QCheckBox to both the right and left side. I cannot seems to find any ways to modify just the alignment of the text and not the checkbox itself.
I don't know if you can access the label associated with the checkbox or not, but if you cannot, a hack would be to set the checkbox label with an empty string and use another QLabel where you can use setAlignment(Qt::AlignJustify) to adjust your text to both the right and left side.
But then I don't know if you consider this modify the checkbox itself and not just the alignement.
I solved it using a trick.
Create a QCheckBox without putting any text in it.
And create a QLable so that you can center the text and click it.
Then it's possible.
Below is the example code.
You can refer to it.
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyApp(QWidget):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.cb = QCheckBox(self)
cp_label = QLabel('Click\nHere Me!!', self)
self.cb.setStyleSheet(self.CP_DEFAULT_STYLE_SET_VALUE)
cp_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
cp_label.setGeometry(75+30, 50+15, 60, 30)
self.cb.move(75+90, 50+20)
self.cb.clicked.connect(self.cpClick)
cp_label.mousePressEvent = self.cpLabelClick
cp_label.leaveEvent = self.cpLabelLeave
cp_label.enterEvent = self.cpLabelEnter
self.setWindowTitle('QCheckBox')
self.setGeometry(300, 300, 300, 200)
self.show()
def cpClick(self):
if not self.cb.isChecked():
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE_SET_VALUE
)
else:
self.cb.setStyleSheet('')
def cpLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
def cpLabelLeave(self, _):
self.cb.setStyleSheet('')
def cpLabelEnter(self, _):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
or
If you use Qt Designer,
After creating one Qwidget, make QCheckBox and QLabel inside
Bring the QLabel to the front and make the QCheckBox deselected.
And if you write it like the code below, it works perfectly!
import os
import sys
from PyQt5.uic import loadUi
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
def resource_path(*relative_Path_AND_File):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = getattr(sys, '_MEIPASS', os.path.dirname(
os.path.abspath(__file__)))
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, '/'.join(relative_Path_AND_File))
class MyApp_Define:
cb: QCheckBox
cb_label: QLabel
class MyApp(QMainWindow, MyApp_Define):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
loadUi(resource_path("TEST.ui"), self)
self.cb_label.installEventFilter(self)
self.cb_label.leaveEvent = self.cbLabelLeave
self.cb_label.mousePressEvent = self.cbLabelClick
def eventFilter(self, source: QObject, event: QEvent):
# 자동 종료 체크박스 안에 들어갈시
if (
source is self.cb_label and
event.type() == QEvent.Type.MouseMove and
not self.cb.isChecked()
):
self.cbLabelEnter()
return super().eventFilter(source, event)
def cbLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
if not self.cb.isChecked():
self.cbLabelEnter()
def cbLabelLeave(self, _):
self.cb.setStyleSheet('')
def cbLabelEnter(self, _=None):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())
The image below is an example of QtDesigner.

Scaling of QMovie content not working while paused

I am looking into using QMovie for some animated icons in my application. The code below demonstrates some basic usage of this class which I would like to also add as functionality to my application. The problem that I'm facing is that whenever QMovie is paused, scaling the content doesn't seem to work (see screenshots after code). The behaviour is observed in both ways - upscaling (resulting from increase of size of the window) and downscaling (resulting from the decrease of size of the window).
Any idea how to fix that?
from sys import exit, argv
from PyQt4.QtGui import QApplication, QWidget, QCheckBox, QPushButton, QLabel, QGridLayout, QIcon, QSizePolicy, QMovie
from PyQt4.QtCore import Qt, QSize, pyqtSlot, pyqtSignal, QPoint, QRect
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.toggled = False
self.initUI()
def initUI(self):
self.layout = QGridLayout()
self.label = QLabel()
self.label.setMinimumWidth(100)
self.label.setMinimumHeight(100)
self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.label_animation = QMovie('circles.gif')
self.label_animation.setScaledSize(QSize(100, 100))
self.label.setMovie(self.label_animation)
self.label_animation.start()
self.label_animation.setPaused(True)
# self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.layout.addWidget(self.label, 0, 0)
self.button = QPushButton()
self.button.setObjectName('button')
# self.button.setIcon(QIcon('icon.png'))
self.button.setStyleSheet('QPushButton#button{border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-image: url("icon.png"); background: transparent;}')
self.button.setMinimumWidth(100)
self.button.setMinimumHeight(100)
self.button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.button.clicked.connect(self.toggle)
self.layout.addWidget(self.button, 0, 1)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle('Icons, Images && Video with PyQt')
self.setLayout(self.layout)
self.resize(self.layout.sizeHint())
def resizeEvent(self, event):
self.label_animation.setScaledSize(QSize(self.label.width(), self.label.height()))
#pyqtSlot()
def toggle(self):
style = 'QPushButton#button{border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-image: url("icon.png"); '
if not self.toggled:
style = style + 'background-color: rgb(255, 0, 0);}'
else:
style = style + 'background: transparent;}'
self.button.setStyleSheet(style)
self.label_animation.setPaused(self.toggled)
self.toggled = not self.toggled
def main():
app = QApplication(argv)
ex = Example()
ex.show()
exit(app.exec_())
if __name__ == '__main__':
main()
Screenshots:
Resizing window (paused QMovie) - QPushButton on the right with its border image is resized properly however on the left the QMovie's content inside the QLabel (label itself IS resized!). The screenshot below demonstrates failure to upscale the animation.
Resizing window (unpaused QMovie) - content of QMovie gets scaled properly the moment the animation is unpaused.

Resources