I've been trying to learn PyQt5 to reimplement an application I did with Tkinter, but with some design differences. Since it's not a complex application, I'd like to make it have a style similar to this small window from GitHub desktop (options on the left side of the window, and the rest in the remaining space):
I know my colors don't look great now, but I can take care of that later. However, I haven't found out how to draw lines/boxes similar to those, or at lesat in the divisions between my columns/rows.
Here's what I have so far:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QWidget, QFileDialog, QGridLayout, QFrame
from PyQt5 import QtGui, QtCore
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Some Window Title')
self.app = QApplication(sys.argv)
self.screen = self.app.primaryScreen()
self.screen_size = self.screen.size()
self.screen_width = self.screen_size.width()
self.screen_height = self.screen_size.height()
self.setGeometry(
self.screen_width * 0.1,
self.screen_height * 0.1,
self.screen_width * 0.8,
self.screen_height * 0.8
)
self.setStyleSheet('background: #020a19;')
self.grid = QGridLayout()
self.grid.setVerticalSpacing(0)
self.grid.setContentsMargins(0, 0, 0, 0)
self.option_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-color: #c5cad4;
border: 0px 0px 0px 0px;
border-style: outset;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
margin: 15px;
width: 2px;
}
QPushButton:hover {
background-color: #00384d;
}
'''
self.placeholder_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-top: none;
border-right: 1px;
border-left:none;
border-bottom: none;
border-color: #c5cad4;
border-style: outset;
padding: 10px;
margin: 0px;
width: 2px;
height: 100%;
}
QPushButton:hover {
background-color: #020a19;
}
'''
self.header_label = QLabel('Some Application')
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
self.header_label.setStyleSheet(
'''
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
height: 10%;
border-bottom: 1px solid #c5cad4;
'''
)
self.header_label.setFixedHeight(120)
self.grid.addWidget(self.header_label, 0, 0, 1, 2)
self.option_1_button.setStyleSheet(self.option_button_stylesheet)
self.option_1_button.setFixedWidth(200)
self.option_1_button.setFixedHeight(100)
self.grid.addWidget(self.option_1_button, 1, 0)
self.option_2_button.setStyleSheet(self.option_button_stylesheet)
self.option_2_button.setFixedWidth(200)
self.option_2_button.setFixedHeight(100)
self.grid.addWidget(self.option_2_button, 2, 0)
self.option_3_button.setStyleSheet(self.option_button_stylesheet)
self.option_3_button.setFixedWidth(200)
self.option_3_button.setFixedHeight(100)
self.grid.addWidget(self.option_3_button, 3, 0)
self.grid.setRowStretch(4, 1)
self.initUI()
self.setLayout(self.grid)
self.show()
def initUI(self):
self.greet_text = QLabel('Welcome')
self.greet_text.setAlignment(QtCore.Qt.AlignCenter)
self.greet_text.setStyleSheet(
"""
font-size: 35px;
color: #c5cad4;
"""
)
self.grid.addWidget(self.greet_text, 1, 1, 5, 1)
def run():
window = Window()
sys.exit(window.app.exec())
run()
As you can see, I'm using QWidget as my window element. I know I could use QMainWindow, but that changes the way widgets are placed and I'm finding it easier to use QWidget. I also don't need a toolbar or anything like that for this app.
How can I draw those lines?
Qt style sheets (QSS) don't provide such a feature, as it's only possible to style specific widgets without being able to consider their position within the layout. This is important in your case, as what you want to do is draw the "separation" between layout items.
It is theoretically possible to achieve this by setting a background for the container widget that will be the line color, have all its child widgets drawing their full contents with opaque colors, and ensure that the layout always has a spacing equal to the width of the line, but if the inner widgets don't respect their full size, they use an alpha channel, or some stretch or further spacing is added, the result would be ugly.
One possibility is to use a QWidget subclass, override its paintEvent() and draw those lines with QPainter.
The idea is that we cycle through all layout items, and draw lines that are placed in the middle between the "current" item and the previous.
In the following example I've created a basic QWidget subclass that implements the above concept, depending on the layout used.
Note that I had to make some changes and corrections to your original code:
as already noted in comments, an existing QApplication is mandatory to allow the creation of a QWidget, and while it's possible to make it an attribute of the object (before calling the super().__init__()), it is still conceptually wrong;
highly hierarchical structures in grid layouts should not use individual rows and columns for their direct child objects, but proper sub-layouts or child widgets should be added instead; in your case, the should be only two rows and columns: the header will have a 2-column-span in the first row, the menu will be on the second row (index 1) and first column, the right side in the second column, and the menu buttons will have their own layout;
setting generic style sheet properties for parent widgets is highly discouraged, as complex widgets (such as QComboBox, QScrollBar and scroll areas children) require that all properties are set to properly work; using setStyleSheet('background: ...') should always be avoided for parents or the application;
style sheets that are shared among many widgets should be set on the parent or the application, and proper selectors should always be used;
the QSS width property should be used with care, as it could make widgets partially invisible and unusable;
if you don't want any border, just use border: none;;
only absolute units are supported for style sheet sizes (see the Length property type), percent values are ignored;
setting fixed heights, paddings and margins can result in unexpected behavior; ensure that you carefully read the box model and do some testing to understand its behavior;
classes should not show themselves automatically during construction, so show() should not be called within the __init__() (this is not specifically "forbidden" or discouraged, but it's still good practice);
an if __name__ == '__main__': block should always be used, especially when dealing with programs or toolkits that rely on event loops (like all UI frameworks, as Qt is);
Here is a rewriting of your original code:
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
def paintEvent(self, event):
# QWidget subclasses *must* do this to properly use style sheets;
# (see doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget)
opt = QStyleOption()
opt.initFrom(self)
qp = QStylePainter(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
# end of default painting
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
lastGeo = layout.itemAt(0).geometry()
if isinstance(layout, QVBoxLayout):
for row in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
y = (lastGeo.bottom()
+ (newGeo.y() - lastGeo.bottom()) // 2)
qp.drawLine(0, y, self.width(), y)
lastGeo = newGeo
else:
for col in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
x = (lastGeo.right()
+ (newGeo.x() - lastGeo.right()) // 2)
qp.drawLine(x, 0, x, self.height())
lastGeo = newGeo
elif isinstance(layout, QGridLayout):
for i in range(layout.count()):
row, col, rowSpan, colSpan = layout.getItemPosition(i)
if not row and not col:
continue
cellRect = layout.cellRect(row, col)
if rowSpan:
cellRect |= layout.cellRect(row + rowSpan - 1, col)
if colSpan:
cellRect |= layout.cellRect(row, col + colSpan - 1)
if row:
aboveCell = layout.cellRect(row - 1, col)
y = (aboveCell.bottom()
+ (cellRect.y() - aboveCell.bottom()) // 2)
qp.drawLine(cellRect.x(), y, cellRect.right() + 1, y)
if col:
leftCell = layout.cellRect(row, col - 1)
x = (leftCell.right()
+ (cellRect.x() - leftCell.right()) // 2)
qp.drawLine(x, cellRect.y(), x, cellRect.bottom() + 1)
class Window(LayoutLineWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
Window {
background: #020a19;
}
QLabel#header {
qproperty-alignment: AlignCenter;
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
}
QWidget#content {
border: 1px solid #c5cad4;
border-radius: 5px;
}
QPushButton {
background: #020a19;
color: #c5cad4;
border: none;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
}
QPushButton:hover {
background-color: #00384d;
}
QWidget#menu > QPushButton {
width: 180px;
height: 80px;
}
''')
mainLayout = QGridLayout(self)
mainLayout.setContentsMargins(0, 0, 0, 0)
mainLayout.setSpacing(0)
self.header_label = QLabel('Some Application', objectName='header')
self.header_label.setMinimumHeight(120)
mainLayout.addWidget(self.header_label, 0, 0, 1, 2)
menuContainer = QWidget(objectName='menu')
mainLayout.addWidget(menuContainer)
menuLayout = QVBoxLayout(menuContainer)
menuLayout.setSpacing(15)
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
menuLayout.addWidget(self.option_1_button)
menuLayout.addWidget(self.option_2_button)
menuLayout.addWidget(self.option_3_button)
menuLayout.addStretch()
rightLayout = QVBoxLayout()
mainLayout.addLayout(rightLayout, 1, 1)
self.content = QStackedWidget()
self.content.setContentsMargins(40, 40, 40, 40)
rightLayout.addWidget(self.content)
rightLayout.addStretch(1)
self.firstPage = LayoutLineWidget(objectName='content')
self.content.addWidget(self.firstPage)
firstPageLayout = QVBoxLayout(self.firstPage)
spacing = sum(firstPageLayout.getContentsMargins()) // 2
firstPageLayout.setSpacing(spacing)
self.other_option_1_button = QPushButton('Other 1')
self.other_option_2_button = QPushButton('Other 2')
self.other_option_3_button = QPushButton('Other 3')
firstPageLayout.addWidget(self.other_option_1_button)
firstPageLayout.addWidget(self.other_option_2_button)
firstPageLayout.addWidget(self.other_option_3_button)
screen = QApplication.primaryScreen()
rect = QRect(QPoint(), screen.size() * .8)
rect.moveCenter(screen.geometry().center())
self.setGeometry(rect)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
This is the visual result:
Note that the above code will cause calling the paintEvent() and its instructions very often, so it's always a good idea to provide some caching. A possibility is to use QPicture, which is a sort of "QPainter recorder"; since it completely relies on the C++ implementation, this allows to optimize the painting by drawing the existing content until it's changed.
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
_paintCache = None
_redrawEvents = QEvent.LayoutRequest, QEvent.Resize
def event(self, event):
if event.type() in self._redrawEvents:
self._paintCache = None
self.update()
return super().event(event)
def paintEvent(self, event):
# QWidget subclasses *must* do the following to properly use style sheets;
# see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
qp = QStylePainter(self)
opt = QStyleOption()
opt.initFrom(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
try:
qp.drawPicture(0, 0, self._paintCache)
except TypeError:
self._rebuildPaintCache()
qp.drawPicture(0, 0, self._paintCache)
def _rebuildPaintCache(self):
layout = self.layout()
self._paintCache = QPicture()
qp = QPainter(self._paintCache)
# from this point, it's exactly the same as above
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
# ...
Further notes:
the above codes haven't been tested against widgets with different margin/padding settings and complex row/column spans for grid layouts; it might need further fixing;
one of the hidden features of QGridLayout is that it's possible to set more widgets for each grid "cell"; while this feature can be useful for complex layouts, it has an important drawback: whenever a child widget or layout uses spanning, the layout spacing is ignored, so child items might have inconsistent geometries and the above code might not work as expected;
don't underestimate the aspects related to the box model, especially when dealing with sizes;
font sizes should not be set in pixels, as screens can have different DPI settings; always prefer device based units: pt, em or ex;
Related
I have modelled my work with #S. Nick's answer in this post PyQt: How to create custom combined titlebar and menubar
I have added a lot of improvements with the titlebar. However, am now having an issue with adding toolbar to it's proper location.
custom titlebar
as you can see, the vertical sequence is:
titlebar
tab widget
toolbar
how do i make it show as:
titlebar
toolbar
tab widget
EDIT:
i updated my previous code with #A. Herlas solution [10/13/2022]
finally got to fix the issue with the mouse cursor not exactly centering on the
titlebar if window maximized and then moused dragged [10/25/2022]
Code:
import sys
from PySide6 import QtWidgets as qtw
from PySide6 import QtCore as qtc
from PySide6 import QtGui as qtg
class TitleBar(qtw.QWidget):
height = 35
def __init__(self, parent):
super(TitleBar, self).__init__(parent)
self.nav_maximize = """
QToolButton[accessibleName="btn_max"] {
image: url(./icons/nav_maximize.png);
background: #161a21;
border: nobutton_stylene;
padding-right: 3px;
}
QToolButton[accessibleName="btn_max"]:hover {
image: url(./icons/colored_maximize.png);
background: #161a21;
border: none;
}
"""
self.nav_normal = """
QToolButton[accessibleName="btn_max"]{
image: url(./icons/nav_normal.png);
background: #161a21;
border: none;
}
QToolButton[accessibleName="btn_max"]:hover{
image: url(./icons/colored_normal.png);
background: #161a21;
border: none;
}
"""
### for window movement ###
self.prevGeo = self.geometry()
self.pressing = False
self.maximizedWindow=False
### [ end ] ###
self.current_editor = self.parent().create_editor()
self.current_editor.setFocus()
self.text_editors = []
self.tabs = qtw.QTabWidget()
self.tabs.setTabsClosable(True)
self.tabs.tabBar().setMovable(True)
self.parent()._createActions()
self.parent()._connectActions()
self.layout = qtw.QHBoxLayout()
self.layout.setContentsMargins(0,0,10,0)
self.menubar = qtw.QMenuBar()
file_menu = self.menubar.addMenu('File')
file_menu.addAction(self.parent().new_action)
file_menu.addAction(self.parent().open_action)
file_menu.addAction(self.parent().save_action)
file_menu.addSeparator()
file_menu.addAction(self.parent().exit_action)
self.layout.addWidget(self.menubar)
self.window_title = qtw.QLabel("Visual Studio Code") # Window title
self.window_title.setAlignment(qtc.Qt.AlignCenter)
self.window_title.setAccessibleName("lbl_title")
self.window_title.setFixedHeight(self.height)
self.layout.addStretch(1) # this stretches the self.window_title qlabel to take-up all the remaining space
self.layout.addWidget(self.window_title)
self.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.Fixed)
self.maximizedWindow=False
self.closeButton = qtw.QToolButton()
self.closeButton.setAccessibleName("btn_close")
self.closeButton.clicked.connect(self.onClickClose)
self.maxButton = qtw.QToolButton()
self.maxButton.setAccessibleName("btn_max")
self.maxButton.setStyleSheet(self.nav_maximize)
self.maxButton.clicked.connect(self.showMaxRestore)
self.hideButton = qtw.QToolButton()
self.hideButton.setAccessibleName("btn_min")
self.hideButton.clicked.connect(self.onClickHide)
self.layout.addWidget(self.hideButton)
self.layout.addWidget(self.maxButton)
self.layout.addWidget(self.closeButton)
self.setLayout(self.layout)
#####################################################
## TITLE BAR MINIMIZE, MAXIMIZE, CLOSE METHODS
#####################################################
def onClickClose(self):
main.close()
def onClickHide(self):
main.showMinimized()
def showMaxRestore(self):
# QWidget.showNormal() # https://doc.qt.io/qt-6/qwidget.html#showNormal
#-- Restores the widget after it has been maximized or minimized.
if(self.maximizedWindow):
# self.prevGeo = self.geometry()
main.showNormal()
self.maximizedWindow = False
self.maxButton.setStyleSheet(self.nav_maximize)
else:
# QWidget.showMaximized() # https://doc.qt.io/qt-6/qwidget.html#showMaximized
#-- Shows the widget maximized.
self.prevGeo = self.geometry() # save current window geometry. this helps with centering the mouse cursor in the titlebar
main.showMaximized()
self.maximizedWindow = True
self.maxButton.setStyleSheet(self.nav_normal)
# EVENT FUNCTIONS
# window will maximize if mouse cursor is positioned at less then 10 pixels in y-coordinate
def mouseReleaseEvent(self, event):
if event.globalPosition().y() < 10:
self.showMaxRestore() # maximize window
def mousePressEvent(self, event):
# getting previous mouse x and y coordinates
self.prevMousePos = event.scenePosition()
self.pressing = True
if event.type() == qtc.QEvent.MouseButtonDblClick:
self.showMaxRestore()
def mouseMoveEvent(self, event): # this is responsible for the mouse drag on title bar
if(self.maximizedWindow):
# if the window is moved while maximized,
# it is automatically returned to its normal state upon mouse drag
main.showNormal()
self.maximizedWindow= False
self.maxButton.setStyleSheet(self.nav_maximize)
# mouse cursor re-positioning on the window
self.prevMousePos = qtc.QPointF((self.prevGeo.width()*.5), (self.prevGeo.height()*.5)) # setting the mouse position to be exactly at the center of the titlebar
if self.pressing: # this is for moving the window
# GLOBAL POSITION: https://stackoverflow.com/questions/67723421/deprecationwarning-function-when-moving-app-removed-titlebar-pyside6
mousePosition = event.globalPosition()
pos = mousePosition-self.prevMousePos
x = pos.x()
y = pos.y()
main.move(x,y)
#####################################################
## END
#####################################################
class MainWindow(qtw.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.statusbar = self.statusBar()
self.statusbar.showMessage("Ready")
self.current_editor = self.create_editor()
self.current_editor.setFocus()
self.text_editors = []
# WINDOW FLAGS: https://doc.qt.io/qtforpython/overviews/qtwidgets-widgets-windowflags-example.html?highlight=windowminimizebuttonhint
self.setMinimumSize(400,250)
self.resize(700,500)
self.setWindowFlags(qtc.Qt.FramelessWindowHint|
qtc.Qt.WindowMaximizeButtonHint|
qtc.Qt.WindowMinimizeButtonHint |
qtc.Qt.WindowStaysOnTopHint # make window on top of taskbar
)
self.title_bar = TitleBar(self)
self.tabs = qtw.QTabWidget()
self.tabs.setTabsClosable(True)
self.tabs.tabBar().setMovable(True)
self.tabs.tabCloseRequested.connect(self.remove_editor)
self.tabs.currentChanged.connect(self.change_text_editor)
self.tabs.tabBar().setMovable(True)
self._createToolBars()
# Cannot set QxxLayout directly on the QMainWindow
# Need to create a QWidget and set it as the central widget
widget = qtw.QWidget()
layout = qtw.QVBoxLayout()
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.title_bar,1)
layout.addWidget(self.file_toolbar,2)
layout.addWidget(self.tabs,3)
layout.setSpacing(0)
widget.setLayout(layout)
self.setCentralWidget(widget)
self.new_tab()
self.closeTab()
self._createActions()
self._connectActions()
def create_editor(self):
current_editor = qtw.QTextEdit()
# Set the tab stop width to around 33 pixels which is
# about 8 spaces
current_editor.setTabStopDistance(33)
return current_editor
def change_text_editor(self, index):
if index < len(self.text_editors):
self.current_editor = self.text_editors[index]
def remove_editor(self, index):
if self.tabs.count() < 2:
return True
self.tabs.removeTab(index)
if index < len(self.text_editors):
del self.text_editors[index]
def closeTab(self):
close_tab = qtg.QShortcut(qtg.QKeySequence("Ctrl+W"), self)
close_tab.activated.connect(lambda:self.remove_editor(self.tabs.currentIndex()))
def close(self): # close entire program
qtw.QApplication.quit()
def new_tab(self, checked = False, title = "Untitled.txt"):
self.widget = qtw.QMainWindow()
self.tabs.addTab(self.widget, title)
self.tabs.setCurrentWidget(self.current_editor) # set the current tab selected as current widget
self.current_editor = self.create_editor() # create a QTextEdit
self.text_editors.append(self.current_editor) # add current editor to the array list
self.widget.setCentralWidget(self.current_editor)
def open_document(self):
options = qtw.QFileDialog.Options()
self.filename, _ = qtw.QFileDialog.getOpenFileName(
self, 'Open File',".",
"(*.notes);;Text Files (*.txt);;Python Files (*.py)",
options=options
)
if self.filename:
with open(self.filename,"rt") as file:
content = file.read()
self.current_editor = self.create_editor()
currentIndex = self.tabs.addTab(self.current_editor, str(self.filename)) # use that widget as the new tab
self.current_editor.setText(content) # set the contents of the file as the text
self.tabs.setCurrentIndex(currentIndex) # make current opened tab be on focus
def _createToolBars(self):
# create toolbars
self.file_toolbar = self.addToolBar("File")
self.file_toolbar.setIconSize(qtc.QSize(22,22))
self.file_toolbar.addAction(self.new_action)
self.file_toolbar.addAction(self.open_action)
self.file_toolbar.addAction(self.save_action)
def _createActions(self):
# FILE MENU
self.new_action = qtg.QAction(qtg.QIcon("./icons/new_file.png"),"New", self)
self.open_action = qtg.QAction(qtg.QIcon("./icons/folder.png"),"Open", self)
self.save_action = qtg.QAction(qtg.QIcon("./icons/save.png"),"Save", self)
self.exit_action = qtg.QAction(qtg.QIcon("./icons/close.png"), "Exit", self)
self.new_action.setShortcut("Ctrl+N")
self.open_action.setShortcut("Ctrl+O")
self.save_action.setShortcut("Ctrl+S")
self.exit_action.setShortcut("Ctrl+Shift+Q")
self.new_action.setToolTip("New file")
self.open_action.setToolTip("Open a file")
self.save_action.setToolTip("Save a file")
self.exit_action.setToolTip("Exit Program")
def _connectActions(self):
# Connect File actions
self.new_action.triggered.connect(self.new_tab)
self.open_action.triggered.connect(self.open_document)
self.save_action.triggered.connect(self.save_document)
self.exit_action.triggered.connect(self.close)
def save_document (self):
if not self.current_editor.document().isModified():
self.statusBar().showMessage("There are no texts to be saved!")
else:
# Only open dialog if there is no filename yet
#PYQT5 Returns a tuple in PyQt5, we only need the filename
options = qtw.QFileDialog.Options()
file_filter = 'Notes_ file (*.notes);; Text file (*.txt);; Python file (*.py)'
if not self.filename:
self.filename = qtw.QFileDialog.getSaveFileName(self,caption='Save File',directory=".",filter=file_filter,initialFilter='Notes Files (*.notes)')[0] # zero index is required, otherwise it would throw an error if no selection was made
if self.filename:
# We just store the contents of the text file along with the
# format in html, which Qt does in a very nice way for us
with open(self.filename,"wt") as file:
file.write(self.current_editor.toHtml())
print(self.tabs.currentIndex())
print(str(self.filename))
self.tabs.setTabText(self.tabs.currentIndex(), str(self.filename)) # renames the current tabs with the filename
self.statusBar().showMessage(f"Saved to {self.filename}")
self.changesSaved = True
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
main = MainWindow()
main.setStyleSheet(
"""
QToolButton[accessibleName="btn_close"] {
image: url(./icons/nav_close.png);
background: #161a21;
border: none;
}
QToolButton[accessibleName="btn_close"]:hover {
image: url(./icons/colored_close.png);
background: #161a21;
border: none;
}
QToolButton[accessibleName="btn_min"] {
image: url(./icons/nav_minimize.png);
background: #161a21;
border: none;
padding-right: 3px;
}
QToolButton[accessibleName="btn_min"]:hover {
image: url(./icons/colored_minimize.png);
background: #161a21;
border: none;
padding-right: 3px;
}
"""
)
main.show()
sys.exit(app.exec())
thanks in advance for any feedbacks or suggestions.
remove this:
self._createToolBars()
from new_tab.
Add this:
file_toolbar = self.addToolBar("File")
file_toolbar.setIconSize(qtc.QSize(22,22))
file_toolbar.addAction(self.new_action)
file_toolbar.addAction(self.open_action)
before widget = qtw.QWidget() in init
Then change this:
layout.addWidget(self.title_bar,1)
layout.addWidget(self.tabs,2)
to this:
layout.addWidget(self.title_bar,1)
layout.addWidget(file_toolbar,2)
layout.addWidget(self.tabs,3)
Now you should have the desired order...
PS: I do not have the same icons as you.
complete PyQt6 version of #A. Herlas solution above:
EDIT:
finally got to fix the issue with the mouse cursor not exactly centering on the
titlebar if window maximized and then moused dragged [10/25/2022]
Code:
# TODO: PySide6/PyQt5 to PyQt6 conversion
# -> In order to call "Window" or "WindowText" in QPallete, you need to use the enum "ColorRole"
# like this: (QtGui.QPalette.ColorRole.Window) = read here: https://doc.qt.io/qt-6/qpalette.html
#
# -> "qtc.Qt.white" does not work anymore in PyQt6, use the enum "GlobalColor"
# like this: (QtCore.Qt.GlobalColor.black) = read here: https://doc.qt.io/qt-6/qt.html
#
# -> error: AttributeError: type object 'Qt' has no attribute 'FramelessWindowHint'. solution: use enum "WindowType"
# like this: (QtCore.Qt.WindowType.FramelessWindowHint) = read here: https://stackoverflow.com/questions/69747328/pyqt6-attributeerror-qt
#
# -> error: AttributeError: type object 'Qt' has no attribute 'AlignCenter'. solution: use enum "AlignmentFlag"
# like this: (QtCore.Qt.AlignmentFlag.AlignCenter) = read here: https://doc.qt.io/qt-6/qt.html#AlignmentFlag-enum
#
# -> error: AttributeError: type object 'QSizePolicy' has no attribute 'Expanding'. solution: use enum "Policy"
# like this: (QtWidgets.QSizePolicy.Policy.Expanding) = read here: https://doc.qt.io/qt-6/qsizepolicy.html
import sys
from PyQt6 import QtWidgets as qtw
from PyQt6 import QtCore as qtc
from PyQt6 import QtGui as qtg
from pathlib import Path
class TitleBar(qtw.QWidget):
height = 35
def __init__(self, parent):
super(TitleBar, self).__init__(parent)
self.nav_maximize = """
QToolButton[accessibleName="btn_max"] {
image: url(./icons/nav_maximize.png);
background: #1c2028;
border: nobutton_stylene;
padding-right: 3px;
}
QToolButton[accessibleName="btn_max"]:hover {
image: url(./icons/colored_maximize.png);
background: #1c2028;
border: none;
}
"""
self.nav_normal = """
QToolButton[accessibleName="btn_max"]{
image: url(./icons/nav_normal.png);
background: #1c2028;
border: none;
}
QToolButton[accessibleName="btn_max"]:hover{
image: url(./icons/colored_normal.png);
background: #1c2028;
border: none;
}
"""
### for window movement ###
self.prevGeo = self.geometry() # save window geometry: QtCore.QRect(int x, int y, int width, int height)
self.pressing = False
self.maximizedWindow=False
### [ end ] ###
self.current_editor = self.parent().create_editor()
self.current_editor.setFocus()
self.text_editors = []
self.tabs = qtw.QTabWidget()
self.tabs.setTabsClosable(True)
self.tabs.tabBar().setMovable(True)
self.parent()._createActions()
self.parent()._connectActions()
self.layout = qtw.QHBoxLayout()
self.layout.setContentsMargins(0,0,10,0)
self.menubar = qtw.QMenuBar()
file_menu = self.menubar.addMenu('File')
file_menu.addAction(self.parent().new_action)
file_menu.addAction(self.parent().open_action)
file_menu.addSeparator()
file_menu.addAction(self.parent().exit_action)
self.layout.addWidget(self.menubar)
self.window_title = qtw.QLabel("Visual Studio Code") # Notes
self.window_title.setAlignment(qtc.Qt.AlignmentFlag.AlignCenter)
self.window_title.setAccessibleName("lbl_title")
self.window_title.setFixedHeight(self.height)
self.layout.addStretch(1) # this stretches the self.window_title qlabel to take-up all the remaining space
self.layout.addWidget(self.window_title)
self.setSizePolicy(qtw.QSizePolicy.Policy.Expanding, qtw.QSizePolicy.Policy.Fixed)
self.maximizedWindow=False
self.closeButton = qtw.QToolButton()
self.closeButton.setAccessibleName("btn_close")
self.closeButton.clicked.connect(self.onClickClose)
self.maxButton = qtw.QToolButton()
self.maxButton.setAccessibleName("btn_max")
self.maxButton.setStyleSheet(self.nav_maximize)
self.maxButton.clicked.connect(self.showMaxRestore)
self.hideButton = qtw.QToolButton()
self.hideButton.setAccessibleName("btn_min")
self.hideButton.clicked.connect(self.onClickHide)
self.layout.addWidget(self.hideButton)
self.layout.addWidget(self.maxButton)
self.layout.addWidget(self.closeButton)
self.setLayout(self.layout)
#####################################################
## TITLE BAR MINIMIZE, MAXIMIZE, CLOSE METHODS
#####################################################
def onClickClose(self):
main.close()
def onClickHide(self):
main.showMinimized()
def showMaxRestore(self):
# QWidget.showNormal() # https://doc.qt.io/qt-6/qwidget.html#showNormal
#-- Restores the widget after it has been maximized or minimized.
if(self.maximizedWindow):
# self.prevGeo = self.geometry()
main.showNormal()
self.maximizedWindow = False
self.maxButton.setStyleSheet(self.nav_maximize)
else:
# QWidget.showMaximized() # https://doc.qt.io/qt-6/qwidget.html#showMaximized
#-- Shows the widget maximized.
self.prevGeo = self.geometry() # save current window geometry. this helps with centering the mouse cursor in the titlebar
main.showMaximized()
self.maximizedWindow = True
self.maxButton.setStyleSheet(self.nav_normal)
# EVENT FUNCTIONS
# window will maximize if mouse cursor is positioned at less then 10 pixels in y-coordinate
def mouseReleaseEvent(self, event):
if event.globalPosition().toPoint().y() < 10:
self.showMaxRestore() # maximize window
return
def mousePressEvent(self, event):
# getting previous mouse x and y coordinates
self.prevMousePos = event.scenePosition() # coordinates of prev mouse position
# print("previous mouse pos",self.prevMousePos)
self.pressing = True
if event.type() == qtc.QEvent.Type.MouseButtonDblClick:
self.showMaxRestore()
return
def mouseMoveEvent(self, event): # this is responsible for the mouse drag on title bar
if(self.maximizedWindow):
# if the window is moved while maximized,
# it is automatically returned to its normal state upon mouse drag
main.showNormal()
self.maximizedWindow= False
self.maxButton.setStyleSheet(self.nav_maximize)
# mouse cursor re-positioning on the window
self.prevMousePos = qtc.QPointF((self.prevGeo.width()*.5), (self.prevGeo.height()*.5)) # setting the mouse position to be exactly at the center of the titlebar
if self.pressing: # this is for moving the window
# GLOBAL POSITION: https://stackoverflow.com/questions/67723421/deprecationwarning-function-when-moving-app-removed-titlebar-pyside6
mousePosition = event.globalPosition()
print("mousePosition",mousePosition)
pos = mousePosition-self.prevMousePos
# "toPoint()" rounds the the float value of QPointF to the nearest integer
x = pos.toPoint().x()
y = pos.toPoint().y()
main.move(x,y) # .move() only accepts integer values that's why we use .toPoint()
#####################################################
## END
#####################################################
class MainWindow(qtw.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.statusbar = self.statusBar()
self.statusbar.showMessage("Ready")
self.current_editor = self.create_editor()
self.current_editor.setFocus()
self.text_editors = []
# WINDOW FLAGS: https://doc.qt.io/qtforpython/overviews/qtwidgets-widgets-windowflags-example.html?highlight=windowminimizebuttonhint
self.setMinimumSize(400,250)
self.resize(700,500)
self.setWindowFlags(qtc.Qt.WindowType.FramelessWindowHint|
qtc.Qt.WindowType.WindowMaximizeButtonHint|
qtc.Qt.WindowType.WindowMinimizeButtonHint |
qtc.Qt.WindowType.WindowStaysOnTopHint # make window on top of taskbar
)
self.title_bar = TitleBar(self)
self.tabs = qtw.QTabWidget()
self.tabs.setTabsClosable(True)
self.tabs.tabBar().setMovable(True)
self.tabs.tabCloseRequested.connect(self.remove_editor)
self.tabs.currentChanged.connect(self.change_text_editor)
self.tabs.tabBar().setMovable(True)
self._createToolBars()
# Cannot set QxxLayout directly on the QMainWindow
# Need to create a QWidget and set it as the central widget
widget = qtw.QWidget()
layout = qtw.QVBoxLayout()
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.title_bar,1)
layout.addWidget(self.file_toolbar,2)
layout.addWidget(self.tabs,3)
layout.setSpacing(0)
widget.setLayout(layout)
self.setCentralWidget(widget)
self.new_tab()
self.closeTab()
self._createActions()
self._connectActions()
def create_editor(self):
current_editor = qtw.QTextEdit()
# Set the tab stop width to around 33 pixels which is
# about 8 spaces
current_editor.setTabStopDistance(33)
return current_editor
def change_text_editor(self, index):
if index < len(self.text_editors):
self.current_editor = self.text_editors[index]
def remove_editor(self, index):
if self.tabs.count() < 2:
return True
self.tabs.removeTab(index)
if index < len(self.text_editors):
del self.text_editors[index]
def closeTab(self):
close_tab = qtg.QShortcut(qtg.QKeySequence("Ctrl+W"), self)
close_tab.activated.connect(lambda:self.remove_editor(self.tabs.currentIndex()))
def close(self): # close entire program
qtw.QApplication.quit()
def new_tab(self, checked = False, title = "Untitled.txt"):
self.widget = qtw.QMainWindow()
self.tabs.addTab(self.widget, title)
self.tabs.setCurrentWidget(self.current_editor) # set the current tab selected as current widget
self.current_editor = self.create_editor() # create a QTextEdit
self.text_editors.append(self.current_editor) # add current editor to the array list
self.widget.setCentralWidget(self.current_editor)
def open_document(self):
# guide on pyqt6: QFileDialog = read here: https://zetcode.com/pyqt6/dialogs/
home_dir = str(Path.home())
self.filename, _ = qtw.QFileDialog.getOpenFileName(
self, 'Open File',".",
"Text Files (*.txt);;Python Files (*.py)",
home_dir
)
if self.filename:
with open(self.filename,"rt") as file:
content = file.read()
self.current_editor = self.create_editor()
currentIndex = self.tabs.addTab(self.current_editor, str(self.filename)) # use that widget as the new tab
self.current_editor.setText(content) # set the contents of the file as the text
self.tabs.setCurrentIndex(currentIndex) # make current opened tab be on focus
def _createToolBars(self):
# create toolbars
self.file_toolbar = self.addToolBar("File")
self.file_toolbar.setIconSize(qtc.QSize(22,22))
self.file_toolbar.addAction(self.new_action)
self.file_toolbar.addAction(self.open_action)
def _createActions(self):
# FILE MENU
self.new_action = qtg.QAction(qtg.QIcon("./icons/new_file.png"),"New", self)
self.open_action = qtg.QAction(qtg.QIcon("./icons/folder.png"),"Open", self)
self.exit_action = qtg.QAction(qtg.QIcon("./icons/close.png"), "Exit", self)
self.new_action.setShortcut("Ctrl+N")
self.open_action.setShortcut("Ctrl+O")
self.exit_action.setShortcut("Ctrl+Shift+Q")
self.new_action.setToolTip("New file")
self.open_action.setToolTip("Open a file")
self.exit_action.setToolTip("Exit Program")
def _connectActions(self):
# Connect File actions
self.new_action.triggered.connect(self.new_tab)
self.open_action.triggered.connect(self.open_document)
self.exit_action.triggered.connect(self.close)
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
app.setStyle(qtw.QStyleFactory.create("Fusion")) # ['windowsvista', 'Windows', 'Fusion']
print(qtw.QStyleFactory.keys())
# DARKER COLOR OR LIGHTER: https://pinetools.com/darken-color
# COLOR READABILITY CHECKER: https://coolors.co/contrast-checker/dfdcd1-1c2028
# QPallete documentation: https://doc.qt.io/qt-6/qpalette.html
palette = qtg.QPalette()
palette.setColor(qtg.QPalette.ColorRole.Window, qtg.QColor("#1c2028")) # general background color
palette.setColor(qtg.QPalette.ColorRole.WindowText, qtg.QColor("#BFBDB6")) # for the window title
palette.setColor(qtg.QPalette.ColorRole.Button, qtg.QColor("#1c2028")) # overflow buttons color for the qtabbar
palette.setColor(qtg.QPalette.ColorRole.Window, qtg.QColor("#1c2028")) # menu border color
palette.setColor(qtg.QPalette.ColorRole.Text, qtg.QColor("#BFBDB6")) # menu unhighlited text color
palette.setColor(qtg.QPalette.ColorRole.Base, qtg.QColor("#1c2028")) # menu unhighlited bg color
palette.setColor(qtg.QPalette.ColorRole.Highlight, qtg.QColor("#0086b6")) # menu mouse hover highlight color
palette.setColor(qtg.QPalette.ColorRole.HighlightedText, qtg.QColor("#000000")) # menu highlighted text color
app.setPalette(palette)
main = MainWindow()
main.setStyleSheet(
"""
/* css styling properties: https://www.w3schools.com/cssref/pr_border-bottom_style.asp */
QMainWindow{ border-style: none;}
QStatusBar { color: #BFBDB6; background: #1c2028; }
QMenuBar::item:pressed { color: #BFBDB6; background: #1c2028; }
QMenuBar::item { color: #BFBDB6; background: #1c2028; }
/* styling Qmenu: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu */
QTextEdit QMenu::item {color: #BFBDB6; font-weight: normal;} /* for context menu> right click -> textedit*/
QTextEdit QMenu::item:selected { /* when user selects item using mouse or keyboard */
background-color: #0086b6;
color: #000;
}
QTabWidget::pane { border: none; }
QTabBar::tab { border: none; }
QTabBar::tab:top:selected { background: #161a21;}
QTabBar::tab:top:!selected { background: #1c2028; }
QTabBar::close-button { image: url(./icons/close_default.png); margin: 2px}
QTabBar::close-button:hover { image: url(./icons/close_active.png); margin: 2px}
QTabBar::tab:selected {
color: #ffb454;
background: #1c2028;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
QTabBar::tab:!selected {
color: #BFBDB6;
background: #1c2028;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
QTabBar::tab:top, QTabBar::tab:bottom {
min-width: 8ex;
margin-right: -1px;
padding: 5px 10px 5px 10px;
}
QTextEdit
{
border: none;
font: "Consolas";
color: #BFBDB6;
background: #161a21;
selection-background-color: #ffb454;
selection-color: #000000;
}
QMenuBar
{
color: #BFBDB6;
background: #1c2028;
border: none;
border-style: none;
}
QMenuBar::item:selected
{
color: #BFBDB6;
background: #1c2028;
}
QToolBar
{
background: #1c2028;
border: none;
border-style: none;
}
/* -----------------------------//
- The css below affects the QToolbar buttons (or any QToolButton)
-----------------------------//
*/
QToolButton::hover{
background-color: #1c2028;
}
/* ---------- [end] ------------*/
QToolButton[accessibleName="btn_max"]{
image: url(./icons/nav_normal.png);
background: #1c2028;
border: none;
}
QToolButton[accessibleName="btn_max"]:hover{
image: url(./icons/colored_normal.png);
background: #1c2028;
border: none;
}
QToolButton[accessibleName="btn_max"] {
image: url(./icons/nav_maximize.png);
background: #1c2028;
border: nobutton_stylene;
padding-right: 3px;
}
QToolButton[accessibleName="btn_max"]:hover {
image: url(./icons/colored_maximize.png);
background: #1c2028;
border: none;
}
QMenuBar{
color: #fff;
font: "Consolas";
font-size: 14px;
padding: 3px;
}
QLabel[accessibleName="lbl_title"]{
background-color: #1c2028;
font-size: 13px;
font: "Consolas";
padding-right: 425px;
}
QToolButton[accessibleName="btn_close"] {
image: url(./icons/nav_close.png);
background: #1c2028;
border: none;
}
QToolButton[accessibleName="btn_close"]:hover {
image: url(./icons/colored_close.png);
background: #1c2028;
border: none;
}
QToolButton[accessibleName="btn_min"] {
image: url(./icons/nav_minimize.png);
background: #1c2028;
border: none;
padding-right: 3px;
}
QToolButton[accessibleName="btn_min"]:hover {
image: url(./icons/colored_minimize.png);
background: #1c2028;
border: none;
padding-right: 3px;
}
QScrollBar:vertical {
border: none;
width: 14px;
margin: 0px 0 0px 0;
background-color: #1c2028;
border-radius: 0px;
}
QScrollBar:handle:vertical {
background-color: #292c35;
}
QScrollBar:handle:vertical:hover {
background-color: #4c4a4a;
}
QScrollBar:handle:vertical:pressed {
background-color: #5c5b5b;
}
QScrollBar:horizontal {
border: none;
height: 14px;
margin: 0px 0 0 0;
background-color: #1c2028;
border-radius: 0px;
}
QScrollBar:handle:horizontal {
background-color: #292c35;
}
QScrollBar:handle:horizontal:hover {
background-color: #4c4a4a;
}
QScrollBar:handle:horizontal:pressed {
background-color: #5c5b5b;
}
/* -----------------------------//
- The css below removes the QScrollBar's Arrow keys both aesthetically AND functionally
-----------------------------//
*/
QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal,
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{
border: none;
background-color: none;
color: none;
width:0px;
height:0px;
}
QScrollBar::sub-line, QScrollBar::add-line{
border: none;
background-color: none;
width:0px;
height:0px;
}
/* ---------- [end] ---------- */
"""
)
main.show()
sys.exit(app.exec())
I'm making a GUI using PyQt5 and I made the background of my widgets change color when you hover your mouse over them. However, when I hover over a widget, the widget beneath it overlaps and the background is cut off at the bottom.
Its because of the way I made the background, I had to space the widgets very close together and increase the border size in order to achieve that background effect, but it messes with the hover like I said before. Is there any way to ignore this overlapping, or perhaps a different method I could use to draw the background without having to overlap the widgets?
Here is a screenshot of what I want in case my explanation was bad (The bottom widget works fine because there is nothing beneath it to overlap)
I will also attach my code here so you can see what I did
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_())
The problem is due to the stacking of new widgets, and the simplest solution would be to set a transparent background for those widgets if you're sure that they won't have a different background when not hovered:
self.checkbox_style='''
QCheckBox
{
background-color : rgb(0, 0, 0, 0);
...
Unfortunately, this will not really solve your problem, as there are serious logical issues in your implementation.
First of all, setting hardcoded content margins of a layout based on the overall layout is a serious problem for many reasons:
it forces you to change those hardcoded margins based on the menu contents;
it is based on your assumption about the current available fonts, which could potentially make the UI elements partially (or completely) hidden;
it doesn't allow you to properly add more objects to the layout;
After seriously considering the above matters (which are very important and should never be underestimated), the problem is that, while Qt allows to lay out items "outside" their possible position through QSS paddings, you need to consider the widget z-level, which by default stacks a new widget above any other previously created sibling widget (widgets that have a common ancestor), and that's why you see partially hidden check boxes.
The base solution would be to install an event filter, check if it's a HoverMove event type and then call raise_() to ensure that it's put above any other sibling:
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)
Again, this will only partially solve your problem, as you'll soon find out that your hardcoded sizes and positions will not work well (remember, what you see on your screen is never what others see on theirs).
A possible solution would be to create a separate QWidget subclass for the menu, add checkboxes in there, and always compute the maximum height considering the padding. Then, to ensure that the widget box is properly put where it should you should add a addStretch() or at least a "main widget" (which is what is normally used for a "main 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()
Note that in the code above I still used an event filter while still using explicit opaque background colors, if you are fine with the parent background, you can avoid installing and using the event filter and only set the default background to the transparent rgb(0, 0, 0, 0).
I am trying to figure out how to add a populated QVBoxLayout into a QFrame.
In the image you see 4 sliders, each with two labels, and a pushbutton. I've been messing around with QIcon and stylesheets which is why they look a bit odd at the moment. Each of these objects is created in my own custom class that produces all 4 objects together in a QVBoxLayout.
I would like to put a border around each QVBoxLayout. I've reeled through pages and pages of guides and other peoples code but i just can't work out what i'm doing wrong here. I've managed to create a QFrame with just a black border that is appearing behind the QVBoxLayout but its not tied to it in anyway, so its off-center. I could probably bodge something together using the geometery but that seems like something i'll regret later on.
You can see in the second image what i'm trying to get at, but everything i'm doing just isn't working. I've only just started learning to code, so i'm a bit out of my depth at the moment - i've read and read and read the QT documentation and hundreds of message board questions similar to this but none of it makes sense and none of it works.
Its going to be something very simple i know, but right now i'm ready to throw the computer out the window.
Here is my code - apologies as it is a bit messy as i've been experimenting with a lot of different things:
#!/usr/bin/env python3
import sys
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
ON_BTN = 0
ON_PRESS_BTN = 1
OFF_BTN = 2
OFF_PRESS_BTN = 3
MUTE_ICONS = {
ON_BTN: "./images/onbtn.png",
ON_PRESS_BTN: "./images/onpressbtn",
OFF_BTN: "./images/offbtn.png",
OFF_PRESS_BTN: "./images/offpressbtn.png"
}
slider_enabled = """
QSlider::groove:vertical {
height: 300px;
width: 7px;
border-radius: 3px;
}
QSlider::handle:vertical {
background: #8d8d8d;
border: 2px solid #444;
height: 30px;
margin: 0 -30px; /* expand outside the groove */
border-radius: 10px;
}
QSlider::add-page:vertical {
background: #31b0c3;
border-radius: 3px;
}
QSlider::sub-page:vertical {
background: black;
border-radius: 3px;
}
QSlider::handle:vertical:pressed {
background: #565656;
}"""
slider_disabled = """
QSlider::groove:vertical {
height: 300px;
width: 7px;
border-radius: 3px;
}
QSlider::handle:vertical {
background: #8d8d8d;
border: 2px solid #444;
height: 30px;
margin: 0 -30px; /* expand outside the groove */
border-radius: 10px;
}
QSlider::add-page:vertical {
background: #3d6166;
border-radius: 3px;
}
QSlider::sub-page:vertical {
background: black;
border-radius: 3px;
}
QSlider::handle:vertical:pressed {
background: #565656;
}"""
class StyleSheets():
def __init__(self, style):
self.style = style
def sliderstyle(self):
self.style.setStyleSheet
class MySlider(QWidget):
def __init__(self, number):
#Defining various items that make up the object
QWidget.__init__(self)
self.number = number #Channel number
#Slider creation and functions that set its value called
self.slider = QSlider(Qt.Vertical)
self.slider.setMaximum(100)
self.send_slider_startup_values()
self.slider.valueChanged.connect(self.slider_value_change)
self.slider.setFixedHeight(300)
self.slider.setFixedWidth(80)
self.setStyleSheet("background: white;")
self.MyStylesheet = """
QSlider::groove:vertical {
height: 300px;
width: 7px;
border-radius: 3px;
}
QSlider::handle:vertical {
background: #8d8d8d;
border: 2px solid #444;
height: 30px;
margin: 0 -30px; /* expand outside the groove */
border-radius: 10px;
}
QSlider::add-page:vertical {
background: #00ddff;
border-radius: 3px;
}
QSlider::sub-page:vertical {
background: black;
border-radius: 3px;
}
QSlider::handle:vertical:pressed {
background: #565656;
}
"""
self.slider.setStyleSheet(self.MyStylesheet)
#Defining the channel number label & volume level label
self.numberlabel = QLabel(str(number))
self.numberlabel.setAlignment(Qt.AlignCenter)
self.volumelabel = QLabel("0")
self.volumelabel.setAlignment(Qt.AlignCenter)
self.volumelabel.setStyleSheet("border-style: solid; border-color: rgba(0,50,100,255);")
#Creating the mute button
self.mutebutton = QPushButton()
#self.mutebutton.setAlignment(Qt.AlignCenter)
self.mutebutton.setFixedSize(QSize(int (77), int (45)))
self.mutebutton.setIconSize(QSize( int(80), int(48)))
self.mutebutton.setFlat(True)
self.mutebutton.pressed.connect(self.button_pressed)
self.mutebutton.released.connect(self.button_released)
self.update_status(OFF_BTN)
#Making the Layout and adding each item
self.sliderframe = QFrame(self)
self.sliderframe.setFixedHeight(450)
self.sliderframe.setFixedWidth(90)
self.sliderframe.setStyleSheet("border: 2px solid black; background: 0%;")
self.setGeometry(50, 50, 450, 90)
#sliderflayout = QVBoxLayout()
#sliderflayout.setAlignment(Qt.AlignCenter)
sliderlayout = QVBoxLayout()
sliderlayout.setAlignment(Qt.AlignCenter)
sliderlayout.addWidget(self.slider, 0, Qt.AlignCenter)
sliderlayout.addWidget(self.numberlabel, Qt.AlignCenter)
sliderlayout.addWidget(self.volumelabel, Qt.AlignCenter)
sliderlayout.addWidget(self.mutebutton, Qt.AlignCenter)
#sliderlayout.addWidget(self.sliderframe, Qt.AlignCenter)
#self.sliderframe = QFrame(sliderlayout)
#self.sliderframe.setFixedHeight(450)
#self.sliderframe.setFixedWidth(100)
#self.sliderframe.setStyleSheet("border: 5px solid black;")
#self.setGeometry(50, 50, 50, 50)
# self.sliderframe.setVisible(False)
self.setLayout(sliderlayout)
#self.setAutoFillBackground(True)
def slider_value_change(self):
value = int(self.slider.value() / 10)
self.volumelabel.setText(str(value))
def send_slider_startup_values(self):
self.slider.setValue(0)
def update_status(self, status):
self.status = status
self.mutebutton.setIcon(QIcon(MUTE_ICONS[self.status]))
def button_pressed(self):
if self.status == ON_BTN:
self.update_status(ON_PRESS_BTN)
elif self.status == OFF_BTN:
self.update_status(OFF_PRESS_BTN)
def button_released(self):
if self.status == ON_PRESS_BTN:
self.send_mute()
self.slider.setStyleSheet(slider_disabled)
self.update_status(OFF_BTN)
elif self.status == OFF_PRESS_BTN:
self.send_mute()
self.slider.setStyleSheet(slider_enabled)
self.update_status(ON_BTN)
def send_mute(self):
pass
class Window2(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setWindowTitle("Hello")
self.setMinimumHeight(480)
self.setMinimumWidth(800)
self.setAutoFillBackground(True)
self.setStyleSheet("""QWidget{background-color: grey;}""")
self.labeltest = QLabel("test")
label = QLabel("Test 1")
label2 = QLabel("Test 2")
label3 = QLabel("Test 3")
label4 = QLabel("Test 4")
label5 = QLabel("Test 5")
radiobutton = QRadioButton("MUTE")
radiobutton.setText("hello")
#self.label5 = QLabel("HELLO!!!!!")
#frame = QFrame()
#frame.setLayout(QVBoxLayout)
self.channel1 = MySlider(1)
self.channel2 = MySlider(2)
self.channel3 = MySlider(3)
self.channel4 = MySlider(4)
#self.sliderframe = QFrame(self.channel1)
#self.sliderframe.setFixedHeight(500)
#self.sliderframe.setFixedWidth(150)
#self.sliderframe.setStyleSheet("border: 2px solid black; background: 0%;")
self.channel2.setDisabled(True)
layout = QHBoxLayout()
layout.setAlignment(Qt.AlignLeft)
layout.addWidget(self.channel1)
layout.addWidget(self.channel2)
layout.addWidget(self.channel3)
layout.addWidget(self.channel4)
self.setLayout(layout)
def password_timeout(self):
pass
#def frame_object(self, channel):
#frame = QFrame()
#frame.setStyleSheet("""
#QFrame::active: {
# background: white;
#}""")
#channelstrip = MySlider(channel)
#frame.setLayout(channelstrip)
#return frame
class PasswordWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setWindowTitle("Password Test")
self.showFullScreen()
layout = QGridLayout()
self.setLayout(layout)
label = QLabel("Please enter password: ")
layout.addWidget(label, 0, 0)
button = QPushButton("Press me!")
button.clicked.connect(self.button_pushed)
layout.addWidget(button, 1, 0)
def button_pushed(self):
self.close()
app = QApplication(sys.argv)
mainwindow = Window2()
mainwindow.show()
sys.exit(app.exec_())
EDIT: Slight update, i've managed to frame the object by creating a seperate class that inherits Qframe and has the MySlider object passed into it. I need to find a way to get it tighter around the objects but its progress somewhat.
I've figured it out. I'm writing a response in case someone else comes across the same problem (as it seems pretty common). Layouts come automatically with a margin and padding. The command to set both the padding and margin to what you want it to be is:
layoutname.setContentsMargins(0, 0, 0, 0)
layoutname.setSpacing(0)
So i thought i was doing this with QFrames, but i didn't and accidently made it work anyway. You can do this by creating a class of widget that you pass a custom made object into, and using a stylesheet to border the entire thing. You also get the benefit of the stylesheet not affecting objects passed into it, as its only concerned with the class its written in. So in my case, i made an object class called "MySlider" that produces 4 objects - a QSlider, 2 QLabels, and a QPushbutton. This is considered a single object as it was made in a class. I then created a second class that requires an argument. I pass that newly made object into the second class as that argument and add that to another QVBoxLayout. Using self.setStyleSheet i can then edit the entire widgets elements. The widget will automatically size to the smallest it can go without cropping its contents. Here is the code, its pretty simple but man did it twist my melon:
class SliderFrame(QFrame):
def __init__(self, myslider):
QFrame.__init__(self)
self.setStyleSheet("border: 1px solid black; margin: 0px; padding: 0px;")
self.layout = QVBoxLayout()
self.layout.addWidget(myslider)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
And when creating the object on your main window:
self.channel1 = SliderFrame(MySlider(1))
You can see that i pass an argument into SliderFrame, and in turn pass an argument into MySlider.
In PyQt5 I'm getting some unexpected behavior from QTabWidget, the background seems to be white instead of the default form color (roughly light gray). Here is an example:
# QTabWidget2.py
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QTabWidget, \
QGraphicsView, QFrame, QGridLayout
from PyQt5.QtGui import QPalette
from PyQt5.Qt import Qt
def main():
app = QApplication([])
mainForm = MainForm()
mainForm.show()
app.exec()
# end main
class MainForm(QWidget):
def __init__(self):
super().__init__()
# set default form size and location
self.setGeometry(300, 300, 800, 600)
# declare a graphics view
self.bigLabel = QLabel('Big Label')
self.setFontSize(self.bigLabel, 18)
self.bigLabel.setAlignment(Qt.AlignCenter)
self.bigLabel.setFrameStyle(QFrame.Panel)
# declare a small label and a button
self.label = QLabel('Label')
self.setFontSize(self.label, 12)
self.label.setAlignment(Qt.AlignCenter)
self.button = QPushButton('Button')
self.setFontSize(self.button, 12)
self.vboxLayout = QVBoxLayout()
self.vboxLayout.addWidget(self.label)
self.vboxLayout.addWidget(self.button)
self.vboxLayout.addStretch(1)
self.hboxLayout = QHBoxLayout()
self.hboxLayout.addWidget(self.bigLabel, 10)
self.hboxLayout.addLayout(self.vboxLayout, 1)
self.containerWidget = QWidget()
self.containerWidget.setLayout(self.hboxLayout)
self.tabWidget = QTabWidget()
self.tabWidget.addTab(self.containerWidget, 'My Tab')
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.tabWidget)
self.setLayout(self.gridLayout)
# end function
def setFontSize(self, widget, fontSize):
font = widget.font()
font.setPointSize(fontSize)
widget.setFont(font)
# end function
# end class
if __name__ == '__main__':
main()
Here is what it looks like on Ubuntu 18.04:
My question is, how can I make the QTabWidget background the same color as the form (in this case a QWidget) background?
Some things I have tried:
Many widgets have a function like this:
someWidget.setBackgroundBrush(self.palette().brush(QPalette.Window))
But QTabWidget does not seem to have setBackgroundBrush or an equivalent I can find.
I've found some posts that suggest to use style sheets to achieve this, but I'm not sure how to set this up. Would I need to sub-class QTabWidget to achieve this? Also, how could I get the default background form color? I could use simple guess and check to get close, but then it may change slightly across different platforms so this is not especially desirable.
--- Edit ---
Arrrrrrrggggggg !!! Qt can be really frustrating sometimes. If I add this just after declaring the QTabWidget:
widgetColor = self.palette().color(QPalette.Background)
widgetColorRgba = widgetColor.red(), widgetColor.green(), widgetColor.blue(), widgetColor.alpha()
print('widgetColorRgb = ' + str(widgetColorRgba))
styleSheetString = 'background-color: rgba(' + str(widgetColorRgba[0]) + ', ' + \
str(widgetColorRgba[1]) + ', ' + str(widgetColorRgba[2]) + ', ' + str(widgetColorRgba[3]) + ');'
print('styleSheetString = ' + str(styleSheetString))
# this line works
self.tabWidget.setStyleSheet(styleSheetString)
# this line does not work !!!
self.tabWidget.tabBar().setStyleSheet(styleSheetString)
It correctly changes the body of the QTabWidget to the default form background color, but it does not change the color of the tab!!
There are two possible solutions for this problem.
The main reason for this behavior can be found in the style that is being used.
Each Qt style decides how the palette colors are used to paint a widget. This means that even if you set a specific color for (let's say) the background, it is not guaranteed that the widget will have that specific background color.
This concept is better explained if you think about buttons: they often have a "shade" gradient, that is based on the Button color role, but the background is not exactly that color. In the following image, I'm showing a button for which I set a plain red (#ff0000) color for the Button role, but, as you can see, it's not red:
The palette colors are, in fact, a reference.
Some widgets don't have a specific role for their painting behavior, and it's up to the style to decide which role use and how, and that's the case of QTabWidget.
On Linux the default style is usually "Fusion", which uses the Button role to paint the background of the tab widget, but when that background is painted, a gradient based on that color is used instead. Again, a full red color applied for the Button role (aka, the tab widget background), that is not an actual red:
There are two possible solutions for this.
1. Use another style
The "Oxygen" style seem to be more coherent with the parent background when dealing with QTabWidget.
def main():
app = QApplication([])
app.setStyle(QStyleFactory.create('oxygen'))
To know what styles are installed on the system, just call QStyleFactory.keys(). Note that not all styles are available for every system. Usually, "Fusion" and "Windows" are available, which means that if you try to access the "Oxygen" style and it's not installed, the fallback style will be used instead.
2. Use a comprehensive stylesheet for the tab bar too
The reason for your tab not using the background set by the stylesheet is due to the fact that the style (fusion) is still using the aforementioned gradient applied to the background color, and that's because the tab can have a different color whether it's selected or not (specifically, lighter than the background if selected, darker if not).
To avoid that, you need to set the stylesheet for all tabbar pseudo states.
bgd = 'rgba({}, {}, {}, {})'.format(*self.palette().color(QPalette.Window).getRgb())
# get the default margins for layouts and use them for text padding of
# the tab; obviously, you can use your own default padding instead
margins = []
for side in (QStyle.PM_LayoutTopMargin, QStyle.PM_LayoutRightMargin, QStyle.PM_LayoutBottomMargin, QStyle.PM_LayoutLeftMargin):
margin = self.style().pixelMetric(side, None, None)
if side in (QStyle.PM_LayoutTopMargin, QStyle.PM_LayoutBottomMargin):
margin //= 2
margins.append('{}px'.format(margin))
padding = ' '.join(margins)
self.tabWidget.setStyleSheet('''
/* this selector applies the background color only to QWidgets that
are direct children of QTabWidget */
QTabWidget > QWidget {{
background-color: {bgd};
}}
QTabBar::tab {{
padding: {padding};
border-left: 1px solid palette(mid);
border-right: 1px solid palette(mid);
border-top: 1px solid palette(mid);
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom: 1px solid palette(mid);
margin: 0;
}}
QTabBar::tab:selected {{
border-color: palette(dark);
border-bottom: none;
}}
QTabBar::tab:!selected {{
margin-top: 2px;
margin-bottom: -2px;
}}
'''.format(bgd=bgd, padding=padding))
And that's the result:
I like the answer from #musicamente. But it's worth pointing out that the background color of the QTabWidget can be controlled in the stylesheet property of the QTabWidget.
According to the docs, a QTabWidget is mostly composed of a QTabBar and a QStackedWidget. You use a QTabBar key in the stylesheet to control the tab colors and a QStackedWidget key to control each individual page.
The default light gray color of Qt windows is #f0f0f0, so you can use this as a color. Here is a string you can enter into Designer/Creator (alternatively you can do it programmatically with setStyleSheet('string')):
QTabBar::tab {
padding: 8px;
background-color: #f0f0f0;
border: 1px solid palette(mid);
border-radius: 2px;
margin: 0;
}
QTabBar::tab:selected {
border-bottom: none;}
QTabBar::tab:!selected {
margin-top: 2px;
margin-bottom: -2px;
border-bottom: none;}
QStackedWidget {
background-color: #f0f0f0;
}
which gives
However, I prefer the selected tab to be a different color, so I just accept defaults for QTabBar.
QStackedWidget {
background-color: #f0f0f0;
}
which gives
Recently, I have been looking to make my own title bar using PyQt4. While searching for a method, I ran into this awesome post. The problem is, when I ran it, the window options (Close, Minimize, and Maximize) were not there. Only when I hovered over the place where the buttons were supposed to be, did it show up. Here is the code for quick reference:
#########################################################
## customize Title bar
## dotpy.ir
## iraj.jelo#gmail.com
#########################################################
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
class TitleBar(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowFlags(Qt.FramelessWindowHint);
css = """
QWidget{
Background: #AA00AA;
color:white;
font:12px bold;
font-weight:bold;
border-radius: 1px;
height: 11px;
}
QDialog{
Background-image:url('img/titlebar bg.png');
font-size:12px;
color: black;
}
QToolButton{
Background:#AA00AA;
font-size:11px;
}
QToolButton:hover{
Background: #FF00FF;
font-size:11px;
}
"""
self.setAutoFillBackground(True)
self.setBackgroundRole(QtGui.QPalette.Highlight)
self.setStyleSheet(css)
self.minimize=QtGui.QToolButton(self);
self.minimize.setIcon(QtGui.QIcon('img/min.png'));
self.maximize=QtGui.QToolButton(self);
self.maximize.setIcon(QtGui.QIcon('img/max.png'));
close=QtGui.QToolButton(self);
close.setIcon(QtGui.QIcon('img/close.png'));
self.minimize.setMinimumHeight(10);
close.setMinimumHeight(10);
self.maximize.setMinimumHeight(10);
label=QtGui.QLabel(self);
label.setText("Window Title");
self.setWindowTitle("Window Title");
hbox=QtGui.QHBoxLayout(self);
hbox.addWidget(label);
hbox.addWidget(self.minimize);
hbox.addWidget(self.maximize);
hbox.addWidget(close);
hbox.insertStretch(1,500);
hbox.setSpacing(0);
self.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Fixed);
self.maxNormal=False;
close.clicked.connect(self.close);
self.minimize.clicked.connect(self.showSmall);
self.maximize.clicked.connect(self.showMaxRestore);
def showSmall(self):
box.showMinimized();
def showMaxRestore(self):
if(self.maxNormal):
box.showNormal();
self.maxNormal= False;
self.maximize.setIcon(QtGui.QIcon('img/max.png'));
print '1'
else:
box.showMaximized();
self.maxNormal= True;
print '2'
self.maximize.setIcon(QtGui.QIcon('img/max2.png'));
def close(self):
box.close()
def mousePressEvent(self,event):
if event.button() == Qt.LeftButton:
box.moving = True; box.offset = event.pos()
def mouseMoveEvent(self,event):
if box.moving: box.move(event.globalPos()-box.offset)
class Frame(QtGui.QFrame):
def __init__(self, parent=None):
QtGui.QFrame.__init__(self, parent)
self.m_mouse_down= False;
self.setFrameShape(QtGui.QFrame.StyledPanel)
css = """
QFrame{
Background: #D700D7;
color:white;
font:13px ;
font-weight:bold;
}
"""
self.setStyleSheet(css)
self.setWindowFlags(Qt.FramelessWindowHint);
self.setMouseTracking(True);
self.m_titleBar= TitleBar(self);
self.m_content= QtGui.QWidget(self);
vbox=QtGui.QVBoxLayout(self);
vbox.addWidget(self.m_titleBar);
vbox.setMargin(0);
vbox.setSpacing(0);
layout=QtGui.QVBoxLayout(self);
layout.addWidget(self.m_content);
layout.setMargin(5);
layout.setSpacing(0);
vbox.addLayout(layout);
# Allows you to access the content area of the frame
# where widgets and layouts can be added
def contentWidget(self):
return self.m_content
def titleBar(self):
return self.m_titleBar
def mousePressEvent(self,event):
self.m_old_pos = event.pos();
self.m_mouse_down = event.button()== Qt.LeftButton;
def mouseMoveEvent(self,event):
x=event.x();
y=event.y();
def mouseReleaseEvent(self,event):
m_mouse_down=False;
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv);
box = Frame()
box.move(60,60);
l=QtGui.QVBoxLayout(box.contentWidget());
l.setMargin(0);
edit=QtGui.QLabel("""I would've did anything for you to show you how much I adored you
But it's over now, it's too late to save our loveJust promise me you'll think of me
Every time you look up in the sky and see a star 'cuz I'm your star.""");
l.addWidget(edit)
box.show()
app.exec_()
Here is my output:
Changing the CSS properties of QToolButton and QToolButton:hover should be able to let you control the style of the window options (Close, Minimize, and Maximize). For example to change the background color to white with rounded corners:
QToolButton {
background-color: white;
border: 1px solid #32414B;
border-radius: 4px;
margin: 0px;
padding: 2px;
}
QToolButton:hover {
border: 1px solid #148CD2;
}