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.
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'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;
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;
}
There is something about Qt stylesheets that I don't seem to understand. I would simply like to set the background color of a widget to white. But for some reason the background color actually only appears in my widget's children.
I've tried to add self.setAutoFillBackground(True) to my code, but without success.
I've also tried to palette solution from https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget. It worked but only if I don't set a stylesheet, which is needed for the bottom border.
class TopLabelNewProject(qt.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = qt.QHBoxLayout(self)
layout.setContentsMargins(40, 0, 32, 0)
self.setLayout(layout)
self.setFixedHeight(80)
self.setStyleSheet("""
background-color: white;
border-bottom: 1px solid %s;
""" % colors.gray)
self.label = qt.QLabel("Dashboard")
self.label.setStyleSheet("""
QLabel {
font: medium Ubuntu;
font-size: 20px;
color: %s;
}""" % colors.gray_dark)
layout.addWidget(self.label, alignment=qt.Qt.AlignLeft)
self.newProjectButton = Buttons.DefaultButton("New project", self)
layout.addWidget(self.newProjectButton, alignment=qt.Qt.AlignRight)
Note: the Buttons.DefaultButton is just a QPushButton with a custom stylesheet.
This is what I would like to achieve, a white header bar with a label and a button:
But only the label gets a white background.
Try it:
import sys
from PyQt5 import Qt as qt
class TopLabelNewProject(qt.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = qt.QHBoxLayout(self)
layout.setContentsMargins(40, 0, 32, 0)
self.setLayout(layout)
self.setFixedHeight(80)
self.label = qt.QLabel("Dashboard")
layout.addWidget(self.label, alignment=qt.Qt.AlignLeft)
# self.newProjectButton = Buttons.DefaultButton("New project", self)
self.newProjectButton = qt.QPushButton("New project", self)
layout.addWidget(self.newProjectButton, alignment=qt.Qt.AlignRight)
style = '''
QWidget {
background-color: white;
}
QLabel {
font: medium Ubuntu;
font-size: 20px;
color: #006325;
}
QPushButton {
background-color: #006325;
color: white;
min-width: 70px;
max-width: 70px;
min-height: 70px;
max-height: 70px;
border-radius: 35px;
border-width: 1px;
border-color: #ae32a0;
border-style: solid;
}
QPushButton:hover {
background-color: #328930;
}
QPushButton:pressed {
background-color: #80c342;
}
'''
if __name__ == '__main__':
app = qt.QApplication(sys.argv)
app.setStyleSheet(style)
ex = TopLabelNewProject()
ex.show()
sys.exit(app.exec_())
First(change only one widget): Rightclick of the Widget, click "Change Stylesheet" and the Stylesheet window open, when you cange something inside the window you the widget will changed too.
Second(change all selected widget): Use the property window, mark all widget you want to change, click of the ... of the styleSheetrow. The styleSheet window open when you change now something all widget you selected will change.
Instert into Stylesheet:
QLabel <-- Elementname
{ ...... } <-- curly braces must be set around of the changes
background-color: Black; <--- set the Backgroundcolor
A full example:
QLabel{
border-style: outset;
border-width: 2px;
border-color: black;
Background-color: rgb(255,247,191);
color: black;
}
For more information about the Sylesheet window read https://doc.qt.io/Qt-5/stylesheet-syntax.html
Friendly wishes sniffi
Here i want to increase the row height of the headerlabel and font size of the cell items. In my code I am using self.table.setRowHeight() method, but its not working. So please tell me is their any method to increase the row height of the header labels and font size of cell items.
given bellow is my code:
import sys
from PyQt4 import QtGui, QtCore
ROUNDED_STYLE_SHEET1 = """QPushButton {
background-color: green;
color: white;
border-style: outset;
border-width: 4px;
border-radius: 15px;
border-color: none;
font: bold 12px;
min-width: 10em;
padding: 10px;
}
"""
OVAL = """QPushButton {
position: relative;
width: 50px;
height: 30px;
margin: 20px 0;
background: blue;
border-radius: 48% / 25%;
color: white;
font: bold 10px;
}
"""
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.label = QtGui.QLabel("Invoice Serial Number-39",self)
self.label.setStyleSheet("font: bold 20pt AGENTORANGE")
self.label.move(10,20)
self.line_edit = QtGui.QLineEdit(self)
self.line_edit.move(900,15)
self.line_edit.resize(300,30)
self.btn1 = QtGui.QPushButton("connect",self)
self.btn1.setStyleSheet(ROUNDED_STYLE_SHEET1)
self.btn1.move(1200,10)
self.btn2 =QtGui.QPushButton("back", self)
self.btn2.move(70,200)
self.btn3 = QtGui.QPushButton("Reset Form", self)
self.btn3.move(140,200)
self.btn3.setStyleSheet("background-color:brown")
self.btn4 = QtGui.QPushButton("pay", self)
self.btn4.setStyleSheet(OVAL)
self.btn4.move(1200,170)
self. table = QtGui.QTableWidget(self)
self.table.move(10,70)
self.table.resize(1350,100)
self.table_item = QtGui.QTableWidgetItem()
self.table.setRowCount(1)
self.table.setColumnCount(6)
self.tbutton = QtGui.QToolButton()
self.tbutton.setToolTip("delete")
self.tbutton.setIcon(QtGui.QIcon("trash1.png"))
self.tbutton.setCheckable(True)
self.qspinbox = QtGui.QSpinBox()
self.qspinbox.setMinimum(1)
self.qspinbox.setMaximum(50)
self.qspinbox.setRange(1,50)
self.qspinbox.setMinimum(1)
self.table.setHorizontalHeaderLabels(("S.no, Item Description,Qty,Rate(Rs:),Subtotal,"",").split(','))
self.table.setVerticalHeaderLabels(("1").split(','))
self.table.setItem(0,0,QtGui.QTableWidgetItem("1"))
self.table.setItem(0,1, QtGui.QTableWidgetItem("Acne-aid Wash Facial Cleansing"))
self.table.setItem(0,2,QtGui.QTableWidgetItem("1"))
self.table.setItem(0,3,QtGui.QTableWidgetItem("191.72"))
self.table.setItem(0,4,QtGui.QTableWidgetItem("191.72"))
self.table.setItem(0,5,QtGui.QTableWidgetItem(""))
self.table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.table.setCellWidget(0,5,self.tbutton)
self.table.setCellWidget(0,2,self.qspinbox)
self.table.horizontalHeader().setStyleSheet("QHeaderView { font-size: 18pt};")
self.table.horizontalHeader().setStyleSheet("::section {background-color : lightGray;font-size:10pt;}")
self.table.setRowHeight(0,100)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.setStyleSheet("QTableWidget{ width:100%; height: 100px; }")
self.table.setColumnWidth(0,100)
self.table.resizeRowsToContents()
self.table.setColumnWidth(1,250)
self.table.setColumnWidth(2,300)
self.table.setColumnWidth(3,250)
self.table.setColumnWidth(4,250)
self.table.setColumnWidth(5,200)
self.btn4.clicked.connect(self.on_pushButton_clicked)
# self.dialog = Example1()
self.setWindowTitle("business management")
self.setGeometry(200,300,900,600)
self.showMaximized()
def on_pushButton_clicked(self):
self.dialog.showMaximized()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
ex.setStyleSheet("background-color:white")
if __name__ == '__main__':
main()
If you want to increase the height of the horizontal header you must use setFixedHeight(), for example:
self.table.horizontalHeader().setFixedHeight(60)
If you want to increase the font of the cells you must set the setFont() method of the QTableWidget:
fnt = self.table.font()
fnt.setPointSize(40)
self.table.setFont(fnt)