I'm trying to create a custom window (namely a custom bar with close buttons, etc.) and I'm using the following code for this:
from PySide6.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout
from PySide6.QtCore import Qt, QSize, QPoint, QRect
from PySide6.QtGui import QIcon
class CustomBar(QWidget):
def init(self, main_window):
super().init()
self.main_window = main_window
self.layout = QHBoxLayout()
self.buttons_layout = QHBoxLayout()
self.buttons_layout.setContentsMargins(0, 0, 0, 0)
self.buttons_layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setStyleSheet("background-color: rgb(60, 63, 65);")
# ------- title -------
self.title = QLabel("MyApp")
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title.setFixedWidth(200)
# ------- minimize button -------
self.minimize_button = QPushButton()
self.minimize_button.setIcon(QIcon("media\\ui\\icons\\enabled_button.png"))
self.minimize_button.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
self.minimize_button.setIconSize(QSize(16, 16))
self.minimize_button.setFixedSize(QSize(16, 16))
self.minimize_button.clicked.connect(self.minimize_window)
self.buttons_layout.addWidget(self.minimize_button)
# ------- close button -------
self.close_button = QPushButton()
self.close_button.setIcon(QIcon("media\\ui\\icons\\disabled_button.png"))
self.close_button.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
self.close_button.setIconSize(QSize(16, 16))
self.close_button.setFixedSize(QSize(16, 16))
self.close_button.clicked.connect(self.close_window)
self.buttons_layout.addWidget(self.close_button)
self.layout.addWidget(self.title)
self.layout.addLayout(self.buttons_layout)
self.setLayout(self.layout)
self.current_mouse_position = QPoint(0, 0)
def minimize_window(self):
self.main_window.showMinimized()
def close_window(self):
self.main_window.close()
def mousePressEvent(self, event):
self.current_mouse_position = self.mapToGlobal(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
new_mouse_position = self.mapToGlobal(event.pos())
mouse_movement = new_mouse_position - self.current_mouse_position
self.current_mouse_position = new_mouse_position
window_pos = QPoint(self.main_window.geometry().x(), self.main_window.geometry().y())
new_window_pos = window_pos + mouse_movement
self.main_window.setGeometry(QRect(new_window_pos, self.main_window.size()))
And this is the result of this:
For clarity, I will add a stroke to each bar element.
I also added the Qt.WA_TranslucentBackground attribute to the main window to make it transparent and then rounded the corners.
The problem is this: as you can see in the screenshots, the close buttons and the title have different sizes and if I set them to gray too, it will be a jagged line, moreover, the buttons have a transparent background, while the title has a gray one.
I need to leave it like this.
Is it possible to create a placeholder widget, set styles (color, rounded corners) for it, and place the title and buttons on it? Approximately like this:
I am trying to make a programmable sequencer where I can configure objects that I've programmed and create a list of them to run. That requires me to instantiate that object by defining it's properties and I'd like to make a tab control on the blue widget shown below that has an inputs and outputs tab.
Anytime I try to implement this, it replaces all of the other widgets:
Surely that has to be a way to use a standard tab control in a widget right?
Code below:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QGridLayout, QLabel, QMainWindow, QWidget, QTabWidget, QVBoxLayout
from PyQt5.QtGui import QColor, QPalette
# Placeholder widget for setting up layout
class Color(QWidget):
def __init__(self, color):
super().__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
# Creating tab widgets
class MyTabWidget(QTabWidget):
def __init__(self):
super().__init__()
self.setTabPosition(QTabWidget.North)
for n, color in enumerate(['blue', 'purple']):
self.addTab(Color(color), color)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
layout = QGridLayout()
# 1
layout.addWidget(Color("red"), 0, 0, 2, 1)
# 2
layout.addWidget(Color("yellow"), 2, 0, 3, 1)
# 3
layout.addWidget(Color("green"), 0, 1, 5, 4)
# 4
# layout.addWidget(Color("blue"), 0, 5, 5, 1)
tabs = MyTabWidget()
layout.addWidget(tabs, 0, 5, 5, 1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The size policy of the QTabWidget is set to stretch and since the stretch factors of the QGridLayout items is 0 then it will cause the items to be compressed. The solution is to set the stretch factors to 1:
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
for column in range(layout.columnCount()):
layout.setColumnStretch(column, 1)
for row in range(layout.rowCount()):
layout.setRowStretch(row, 1)
I want to do now is find a way to increment the QLineEdit box (Step_Box) with the corresponding value of one of the buttons (0.01, 1, 10...). Each time I press on one of the buttons, the entry on QLineEdit must increment by that value. How should I do this?
# -*- coding: utf8 -*-
import sys
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QGridLayout,
QPushButton,
QLineEdit,
QLabel,
QComboBox,
)
class XY_Buttons(QPushButton):
def __init__(self, name):
super(XY_Buttons, self).__init__()
self.clicked.connect(self.is_clicked)
self.setText(str(name))
def is_clicked(self):
print("A")
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("Suruga Controller")
OuterLayout = QGridLayout() # Parent layout
ArrowsLayout = QGridLayout() # Arrows layout -- in here are the XY controller
UpDownLayout = QGridLayout() # Up and Down layout -- z controller
XYLayout = QGridLayout() # XY options layout -- step size and others
ZLayout = QGridLayout() # Z options layout -- step size and others
# Adding Widgets for ArrowsLayout
ArrowsLayout.addWidget(QPushButton("Up"), 0, 1)
ArrowsLayout.addWidget(QPushButton("Down"), 2, 1)
ArrowsLayout.addWidget(QPushButton("Left"), 1, 0)
ArrowsLayout.addWidget(QPushButton("Right"), 1, 2)
# Adding Widgets for XYLayout
xstep=0.0
Step_Box = QLineEdit(str(xstep))
Step_Box.setReadOnly(True)
Units_Box = QComboBox()
Units_Box.addItems([u"10E-6 (µm) ", "10E-3 (cm)"])
XYLayout.addWidget(XY_Buttons(0.01), 1, 0)
XYLayout.addWidget(XY_Buttons(0.1), 1, 1)
XYLayout.addWidget(XY_Buttons(1), 1, 2)
XYLayout.addWidget(XY_Buttons(10), 2, 0)
XYLayout.addWidget(XY_Buttons(100), 2, 1)
XYLayout.addWidget(XY_Buttons(1000), 2, 2)
# XYLayout.addWidget(Clear_Button(), 0, 2, 1, 2)
XYLayout.addWidget(QLabel("Step is:"), 0, 0, 1, 2)
XYLayout.addWidget(Step_Box, 0, 1)
XYLayout.addWidget(Units_Box, 3, 1)
# Nesting all layouts
OuterLayout.addLayout(ArrowsLayout, 0, 0)
OuterLayout.addLayout(XYLayout, 0, 1)
OuterLayout.addLayout(UpDownLayout, 1, 0)
OuterLayout.addLayout(ZLayout, 1, 1)
widget = QWidget()
widget.setLayout(OuterLayout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
You need some reference to the instance of QLineEdit i.e. Step_Box in order to change their values from the button click, since you have a custom button class, you can just pass the instance of the line edit and store it there, then you can just increment the value from the slot to which you have connected the clicked signal of the buttons.
import sys
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QGridLayout,
QPushButton,
QLineEdit,
QLabel,
QComboBox,
)
class XY_Buttons(QPushButton):
def __init__(self, name, lineEdit:QLineEdit=None):
super(XY_Buttons, self).__init__()
self.clicked.connect(self.is_clicked)
self.setText(str(name))
self.lineEdit = lineEdit
def is_clicked(self):
print("A")
self.lineEdit.setText(str(float(self.lineEdit.text())+ float(self.text())))
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("Suruga Controller")
OuterLayout = QGridLayout() # Parent layout
ArrowsLayout = QGridLayout() # Arrows layout -- in here are the XY controller
UpDownLayout = QGridLayout() # Up and Down layout -- z controller
XYLayout = QGridLayout() # XY options layout -- step size and others
ZLayout = QGridLayout() # Z options layout -- step size and others
# Adding Widgets for ArrowsLayout
ArrowsLayout.addWidget(QPushButton("Up"), 0, 1)
ArrowsLayout.addWidget(QPushButton("Down"), 2, 1)
ArrowsLayout.addWidget(QPushButton("Left"), 1, 0)
ArrowsLayout.addWidget(QPushButton("Right"), 1, 2)
# Adding Widgets for XYLayout
xstep=0.0
Step_Box = QLineEdit(str(xstep))
Step_Box.setReadOnly(True)
Units_Box = QComboBox()
Units_Box.addItems([u"10E-6 (µm) ", "10E-3 (cm)"])
XYLayout.addWidget(XY_Buttons(0.01, Step_Box), 1, 0)
XYLayout.addWidget(XY_Buttons(0.1, Step_Box), 1, 1)
XYLayout.addWidget(XY_Buttons(1, Step_Box), 1, 2)
XYLayout.addWidget(XY_Buttons(10, Step_Box), 2, 0)
XYLayout.addWidget(XY_Buttons(100, Step_Box), 2, 1)
XYLayout.addWidget(XY_Buttons(1000, Step_Box), 2, 2)
# XYLayout.addWidget(Clear_Button(), 0, 2, 1, 2)
XYLayout.addWidget(QLabel("Step is:"), 0, 0, 1, 2)
XYLayout.addWidget(Step_Box, 0, 1)
XYLayout.addWidget(Units_Box, 3, 1)
# Nesting all layouts
OuterLayout.addLayout(ArrowsLayout, 0, 0)
OuterLayout.addLayout(XYLayout, 0, 1)
OuterLayout.addLayout(UpDownLayout, 1, 0)
OuterLayout.addLayout(ZLayout, 1, 1)
widget = QWidget()
widget.setLayout(OuterLayout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
But generally, passing reference like this is not considered a good practice, so it'd have been better if you connected it from within the class where the instance was created.
I would advise to introduce a clear separation of the data, i.e. the currently selected step, from the GUI. A common approach is Model-View-Controller (MVC).
I think it is necessary for any GUI application, as it otherwise leads to spaghetti code of widgets talking to each other, patching out bugs.
There are several View and Model components already available inside Qt5 (QStringListModel + QListView for example), but it's trivial to make our own.
Here I introduce the FooModel and the corresponding view, along with buttons (Controller) that attempts to modify the model.
# -*- coding: utf8 -*-
import sys
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QGridLayout,
QPushButton,
QLineEdit,
QLabel,
QComboBox,
)
class FooChanger(QPushButton):
# Note: This is the controller. It doesn't try to be clever and modify the view.
# It just modifies the model. If the model wishes to signal to any view component, that's the models responsibility.
def __init__(self, foo_model, value):
super().__init__(str(value))
self.clicked.connect(self.is_clicked)
self.model = foo_model
self.value = value
def is_clicked(self):
self.model.set_value(self.value)
class FooView(QLabel):
# This is the view. It does try to be clever either. It just updates information when the model signals.
def __init__(self, model):
super().__init__("hejsan")
self.model = model
self.model.data_changed.connect(self.refresh)
self.refresh() # initial refresh
def refresh(self):
self.setText(str(self.model.value))
class FooModel(QObject):
# This is our simple model. It only has a signal, and uses it to indicate when the data has been modified.
# Make sure to use the set_value function and don't poke at self.value directly!
data_changed = pyqtSignal()
def __init__(self, initial_value):
super().__init__()
# We are currently just storing one piece of information. The infamous Foo value!
self.value = initial_value
def set_value(self, new_value):
self.value = new_value
self.data_changed.emit()
class MainWindow(QMainWindow):
def __init__(self, model):
super().__init__()
self.setWindowTitle("Suruga Controller")
OuterLayout = QGridLayout() # Parent layout
ArrowsLayout = QGridLayout() # Arrows layout -- in here are the XY controller
UpDownLayout = QGridLayout() # Up and Down layout -- z controller
XYLayout = QGridLayout() # XY options layout -- step size and others
ZLayout = QGridLayout() # Z options layout -- step size and others
# Adding Widgets for ArrowsLayout
ArrowsLayout.addWidget(QPushButton("Up"), 0, 1)
ArrowsLayout.addWidget(QPushButton("Down"), 2, 1)
ArrowsLayout.addWidget(QPushButton("Left"), 1, 0)
ArrowsLayout.addWidget(QPushButton("Right"), 1, 2)
# Adding Widgets for XYLayout
step_box = FooView(model)
unix_box = QComboBox()
unix_box.addItems([u"10E-6 (µm) ", "10E-3 (cm)"])
XYLayout.addWidget(FooChanger(model, 0.01), 1, 0)
XYLayout.addWidget(FooChanger(model, 0.1), 1, 1)
XYLayout.addWidget(FooChanger(model, 1), 1, 2)
XYLayout.addWidget(FooChanger(model, 10), 2, 0)
XYLayout.addWidget(FooChanger(model, 100), 2, 1)
XYLayout.addWidget(FooChanger(model, 1000), 2, 2)
XYLayout.addWidget(QLabel("Step is:"), 0, 0, 1, 2)
XYLayout.addWidget(step_box, 0, 1)
XYLayout.addWidget(unix_box, 3, 1)
# Nesting all layouts
OuterLayout.addLayout(ArrowsLayout, 0, 0)
OuterLayout.addLayout(XYLayout, 0, 1)
OuterLayout.addLayout(UpDownLayout, 1, 0)
OuterLayout.addLayout(ZLayout, 1, 1)
widget = QWidget()
widget.setLayout(OuterLayout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
foo = FooModel(3.1415)
window = MainWindow(foo) # The view acts on our foo.
window.show()
app.exec()
There is no need for cross-talk between widgets here. It's just a view that only displays current data (it doesn't even care how the data got modified, it just always works), a controller that just informs the model that the user requested a change, and a model that is blissfully ignorant of the existence of either widgets or buttons.
When I use inherited QWidget, the margins and spacing have NO background color unlike using QWidget directly (the code is basically the same).
Pure QWidget:
class App(QWidget):
def __init__(self):
super().__init__()
self.start()
def start(self):
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.setGeometry(10, 10, 500, 100)
grid_layout = QGridLayout()
grid_layout.setSpacing(10)
widget = QWidget()
widget.setLayout(grid_layout)
widget.setStyleSheet('background: green')
grid_layout.addWidget(QLabel("first"), 0, 0)
grid_layout.addWidget(QLabel("second"), 0, 1)
grid_layout.addWidget(QLabel("third"), 0, 2)
self.layout.addWidget(widget)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = App()
#ex.start_card_holder()
sys.exit(app.exec_())
Inherited QWidget:
class App(QWidget):
....
class MainWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
def start(self):
...
widget = App.MainWidget()
...
Can anyone tell what I did wrong?
The following concepts must be clear to understand the behavior:
By default the classes that inherit from QWidget do not implement the painting based on the Qt QStyleSheet as indicated by the following questions:
Qt Stylesheet for custom widget
CSS Stylesheet is not applied to custom QWidget
Set background colour for a custom QWidget
By setting the "background: green" you are indicating that the widget takes that color if it is enabled and its children.
In your case the "widget" if it is a QWidget then it will be painted but if it is a MainWidget no.
To check what I indicate, it is only necessary to enable the background color using one of the answers to the questions indicated above:
# ...
widget = App.MainWidget()
widget.setAttribute(Qt.WA_StyledBackground, True)
# ...
I am trying to make a QLabel appear over a QGridLayout, but I cannot figure how to do it.
This is a sample code:
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QFrame, QLabel
import sys
class Foo(QWidget):
def __init__(self):
super().__init__()
grid_layout = QGridLayout()
rect1 = QLabel('RECT1')
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 1)
rect2 = QLabel('RECT2')
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
self.setLayout(grid_layout)
self.show()
app = QApplication(sys.argv)
foo = Foo()
sys.exit(app.exec_())
which produces the following output:
For instance, I want to create another QLabel in red, and display it over them, in the center of the image:
red_label = QLabel('red')
red_labe.setStyleSheet("font-size:20pt; color: red;");
Something like this:
How can I achieve that?
A possible solution is to make red_label son of the QWidget for it you must pass the self parameter when the object is created. In addition to this the QLabel must change size when the window does, emulating the layout task for it will create a signal that will be emited in the resizeEvent event:
import sys
from PyQt5 import QtCore, QtWidgets
class Foo(QtWidgets.QWidget):
sizeChanged = QtCore.pyqtSignal(QtCore.QSize)
def __init__(self):
super().__init__()
grid_layout = QtWidgets.QGridLayout(self)
rect1 = QtWidgets.QLabel("RECT1")
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 1)
rect2 = QtWidgets.QLabel("RECT2")
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
red_label = QtWidgets.QLabel("red", self)
red_label.setAlignment(QtCore.Qt.AlignCenter)
red_label.setStyleSheet("font-size: 20pt; color: red;")
self.sizeChanged.connect(red_label.resize)
def resizeEvent(self, event):
self.sizeChanged.emit(event.size())
super().resizeEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
foo = Foo()
foo.show()
sys.exit(app.exec_())
This may be a little less elegant of a solution, but it does keep the red labeled centered as the window is resized.
class Foo(QWidget):
def __init__(self):
super().__init__()
grid_layout = QGridLayout()
rect1 = QLabel('RECT1')
rect1.setStyleSheet("color: green;")
grid_layout.addWidget(rect1, 0, 0)
rect2 = QLabel('RECT2')
rect2.setStyleSheet("color: blue;")
grid_layout.addWidget(rect2, 0, 2)
grid_layout_two = QGridLayout()
blank_label = QLabel()
red_label = QLabel('red')
red_label.setStyleSheet("font-size:20pt; color: red;")
grid_layout_two.addWidget(blank_label, 0, 0)
grid_layout_two.addWidget(red_label, 0, 1)
grid_layout_two.addWidget(blank_label, 0, 2)
grid_layout_three = QGridLayout()
grid_layout_three.addItem(grid_layout, 0, 0)
grid_layout_three.addItem(grid_layout_two, 0, 0)
self.setLayout(grid_layout_three)
self.show()
app = QApplication(sys.argv)
foo = Foo()
sys.exit(app.exec_())
Basically making three grid layouts, positioning the elements so that the red label is in the center of the other two labels but in a grid layout that is in front of the other labels layout.