How to align two widgets more closely? In my code, I want to align QLabel 1 and QLabel 2 more closely (i.e. QLabel 2 aligned just below the QLabel 1, with minimum spacing).
import sys
from PyQt5 import QtCore,QtGui,QtWidgets
class Layout_sample(QtWidgets.QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Layout Sample")
self.vbox = QtWidgets.QVBoxLayout()
self.lbl1 = QtWidgets.QLabel("F3")
self.lbl2 = QtWidgets.QLabel(u'\u2550'+u'\u2550')
self.vbox.addStretch()
self.vbox.addWidget(self.lbl1)
self.vbox.addWidget(self.lbl2)
self.vbox.addStretch()
self.vbox.setSpacing(0)
self.setLayout(self.vbox)
if __name__ =="__main__":
app = QtWidgets.QApplication(sys.argv)
mainwindow = Layout_sample()
mainwindow.show()
sys.exit(app.exec_())
I assume what you're trying to achieve is a double underline for the text in the first label. The problem with your example is that the unicode character ═ (U+2550) is centered vertically, so there will always be some fixed space above it. The unicode box-drawing characters don't include a top-aligned double-underline, so a different approach is needed.
One solution is to use html/css inside the label to draw a double border below the text. This has to be done using a table-cell, because Qt only supports a limited subset of html/css:
underline = """<td style="
border-bottom-style: double;
border-bottom-width: 3px;
">%s</td>"""
self.lbl1 = QtWidgets.QLabel(underline % 'F3')
self.vbox.addStretch()
self.vbox.addWidget(self.lbl1)
self.vbox.addStretch()
Related
How can i fix the QLabel to not clip the text when resizing? This is a widget that will be placed inside a QDialog eventually. So the resizing of the Dialog will happen if a user resizes the main dialog.
'''
Main Navigation bar
'''
################################################################################
# imports
################################################################################
import os
import sys
import inspect
from PySide2 import QtWidgets, QtCore, QtGui
################################################################################
# widgets
################################################################################
class Context(QtWidgets.QWidget):
def __init__(self):
super(Context, self).__init__()
# controls
self.uiThumbnail = QtWidgets.QLabel()
self.uiThumbnail.setMinimumSize(QtCore.QSize(100, 75))
self.uiThumbnail.setMaximumSize(QtCore.QSize(100, 75))
self.uiThumbnail.setScaledContents(True)
self.uiThumbnail.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.uiThumbnail.setObjectName('thumbnail')
self.uiDetailsText = QtWidgets.QLabel()
self.uiDetailsText.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.uiDetailsText.setWordWrap(True)
self.uiDetailsText.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
self.uiDetailsText.setOpenExternalLinks(True)
self.uiMenuButton = QtWidgets.QPushButton()
self.uiMenuButton.setFixedSize(QtCore.QSize(24, 24))
self.uiMenuButton.setFocusPolicy(QtCore.Qt.NoFocus)
# header layout
self.headerLayout = QtWidgets.QHBoxLayout()
self.headerLayout.setSpacing(6)
self.headerLayout.setContentsMargins(6, 6, 6, 6)
self.headerLayout.setAlignment(QtCore.Qt.AlignTop)
self.headerLayout.addWidget(self.uiThumbnail)
self.headerLayout.addWidget(self.uiDetailsText)
self.headerLayout.addWidget(self.uiMenuButton)
self.headerLayout.setAlignment(self.uiThumbnail, QtCore.Qt.AlignTop)
self.headerLayout.setAlignment(self.uiMenuButton, QtCore.Qt.AlignTop)
# frames
self.headerFrame = QtWidgets.QFrame()
self.headerFrame.setObjectName('panel')
self.headerFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.headerFrame.setFrameShadow(QtWidgets.QFrame.Raised)
self.headerFrame.setLayout(self.headerLayout)
# layout
self.mainLayout = QtWidgets.QHBoxLayout()
self.mainLayout.setSpacing(6)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.addWidget(self.headerFrame)
self.setLayout(self.mainLayout)
self.setStyleSheet('''
#thumbnail {
background-color: rgb(70,70,70);
}
#panel {
background-color: rgb(120,120,120);
border-radius:3px;
}
''')
self.updateContext()
# methods
def updateContext(self):
self.uiDetailsText.setText('''
<span style="font-size:14px;">
<b>A title goes here which can wrap</b>
</span>
<br>
<span style="font-size:11px;">
<b>Status:</b> Additional details go here
<br>
<b>User:</b>
User information goes here
<br>
<b>About:</b> Some more information
<br>
<b>Date:</b> 2021-07-03
<br>
</span>
''')
################################################################################
# main
################################################################################
if __name__ == '__main__':
pass
app = QtWidgets.QApplication(sys.argv)
ex = Context()
ex.resize(500,70)
ex.show()
sys.exit(app.exec_())
I tried adding this and it didn't help at all...
# methods
def resizeEvent(self, event):
newHeight = self.uiDetailsText.heightForWidth(self.uiDetailsText.width())
self.uiDetailsText.setMaximumHeight(newHeight)
event.accept()
The size policy of a QLabel is always Preferred (in both directions), and if word wrapping or rich text is used, the text layout engine will try to find an optimal width based on the contents. This unfortunately creates some issues in layout managers, as explained in the layout documentation:
The use of rich text in a label widget can introduce some problems to the layout of its parent widget. Problems occur due to the way rich text is handled by Qt's layout managers when the label is word wrapped.
Also consider the following line:
self.headerLayout.setAlignment(QtCore.Qt.AlignTop)
it will align the layout item (headerLayout) to the top of the headerFrame, which creates problems with the size hint of the label, since the size policy is Preferred. It's normally unnecessary (and often discouraged) to set the alignment of a layout if it's the top level layout of a widget.
Unfortunately, there's no easy solution for these situations, because:
to allow "free" resizing, the label cannot have any size constraints;
as soon as a widget is mapped, the size hint is just that: a hint; if the user resizes a window that contains wrapped text, the size choosen by the user is respected, even if that results in partially hiding the text;
the size hint considers the heightForWidth of children only when the layout is activated, after that the top level window will ignore any hint and will only honor the user choice, limited by the minimum size (or minimum size hint) of widgets;
heightForWidth() is called only for widgets that do not have a layout, otherwise the layout item's heightForWidth() will be called;
There are workarounds, but they are not always reliable.
A possible solution is to resize the top level window whenever the height doesn't respect the widget's heightForWidth(). Note that this is not completely reliable, and it's not 100% safe, as it requires to call a resize inside a resize event. If any of the parent has some functions that acts on delayed calls related to layouts (including changing the geometry), it might cause recursion problems.
class Context(QtWidgets.QWidget):
resizing = False
# ...
def resizeEvent(self, event):
super().resizeEvent(event)
if self.resizing:
return
diff = self.heightForWidth(event.size().width()) - self.height()
if diff > 0:
self.resizing = True
target = self
while not target.windowFlags() & (QtCore.Qt.Window | QtCore.Qt.SubWindow):
target = target.parent()
target.resize(target.width(), target.height() + diff)
self.resizing = False
Note that for this to work you still have to set the Expanding vertical size policy or remove the line for the layout alignment.
Most importantly, this can only be used only for a single widget in a window, so you should consider implementing it in the top level widget.
I want to change the color of selected item in QListItem, and I find qss may be a solution. And the code is:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
with open('mainStyle.qss', 'r', encoding='utf-8') as file:
self.setStyleSheet(file.read())
# self.setStyleSheet('*{font-size: 15px;background-color: rgb(150, 150, 150);}'
# 'QListWidget::item:selected{background: rgb(128,128,255);}')
self.setStyleSheet('QListWidget::item:selected{background: rgb(128,128,255);}')
layout = QVBoxLayout()
self.setLayout(layout)
listWidget = QListWidget()
layout.addWidget(listWidget)
w1 = QWidget()
w1Item = QListWidgetItem()
w1Item.setSizeHint(QSize(150, 150))
listWidget.insertItem(0, w1Item)
listWidget.setItemWidget(w1Item, w1)
w2 = QWidget()
w2Item = QListWidgetItem()
w2Item.setSizeHint(QSize(150, 150))
listWidget.insertItem(1, w2Item)
listWidget.setItemWidget(w2Item, w2)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
win.show()
app.exec_()
We can see that the color is changed to be blue when the item is selected.
However, I need to provide a general background color for other widgets. So I change the style from
self.setStyleSheet('QListWidget::item:selected{background: rgb(0,0,255);}')
to
self.setStyleSheet('*{font-size: 15px;background-color: rgb(150, 150, 150);}'
'QListWidget::item:selected{background: rgb(0,0,0);}')
Then, I find QListWidget::item:selected do not work. The color do not change when I select a item.
What's the wrong with my code?
The problem is that you're setting a QWidget for the item, and since you're using a universal selector (with the wildcard), the result is that all QWidget will have that background color, including those added as item widgets for the list view.
The color you used for the :selected pseudo is only valid for the item painted by the view, since the item widget has its own background set from the universal selector, that background won't be visible.
The solution is to use a proper selector combination ensuring that the rule only matches children of the list view that have a usable selector, and set a transparent color for those widgets.
A possibility is to set a custom property for the widgets that must be set before adding the widgets to the list (otherwise, you need to set the stylesheet after adding them, or request a style.unpolish()).
self.setStyleSheet('''
QWidget {
font-size: 15px;
background: rgb(150, 150, 150);
}
QListWidget::item:selected {
background: rgb(128,128,255);
}
QListWidget QWidget[widgetItem=true] {
background: transparent;
}
''')
# ...
w1 = QWidget()
w1.setProperty('widgetItem', True)
# ...
w2 = QWidget()
w2.setProperty('widgetItem', True)
# ...
Another way to do so is to use an "empty" subclass for widgets that are going to be added to the item view:
class CustomItemWidget(QWidget): pass
class Window(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
QWidget {
font-size: 15px;
background: rgb(150, 150, 150);
}
QListWidget::item:selected {
background: rgb(128,128,255);
}
CustomItemWidget {
background: transparent;
}
''')
# ...
w1 = CustomItemWidget()
# ...
w2 = CustomItemWidget()
# ...
Consider that using universal selectors for colors is usually not a good idea, as it can result in inconsistent behavior, especially for complex widgets: for instance, the scroll bars of scroll areas (like QListWidget) might not be correctly styled and can even become unusable.
If you plan to have a common background color for all widgets, it's better to set the Window role of the application palette:
app = QApplication(sys.argv)
palette = app.palette()
palette.setColor(palette.Window, QColor(150, 150, 150))
app.setPalette(palette)
# ...
In this way the current style will know exactly when to use that color as background, or as a component for other UI elements.
can you help me please with set of possiotion? I have a problem, because the labels don't move :(
Detail in screen.
label_result.move (X, Y) doesnť work.
from datetime import datetime as datetime, timedelta as timedelta
from PyQt5 import QtWidgets
app = QtWidgets.QApplication([])
# Hlavní okno
main = QtWidgets.QWidget()
main.setWindowTitle('Set tag')
main.setGeometry(60, 60, 300, 600)
# Layout 1
layout = QtWidgets.QHBoxLayout()
main.setLayout(layout)
# Nápis
label = QtWidgets.QLabel('Číslo sezení')
layout.addWidget(label)
#Input
input_session = QtWidgets.QLineEdit()
input_session.move(0,0)
layout.addWidget(input_session)
# Tlačítko
button = QtWidgets.QPushButton('Zaznamenat tag')
layout.addWidget(button)
#Sesion Result
label_result = QtWidgets.QLabel('Výsledky \n')
label_result.move (20, 20)
layout.addWidget(label_result)
button.clicked.connect(lambda: test(input_session.text()))
# Spuštění
main.show()
app.exec()
Thanks a lot.
Using layout managers means that all positioning and sizing of widgets is left to it, so any attempt to manually set the geometry of widget is ignored everytime the widget on which the layout is set is resized, which is what happens at least once as soon as the widget is mapped (shown) the first time.
If you want to position widgets in a different layouts, then you need to understand how layout(s) could be structured in order to achieve the desired result. In your case, it could be done in at least 3 slightly different ways:
using a main vertical boxed layout, then adding a horizontal layout for the first three widgets, another one for the last label, and finally a stretch (which is an empty layout item that tries to expand itself up to the maximum available extent);
again a main vertical layout, then a grid layout and again a stretch;
a grid layout with an expanding spacer at the bottom;
I'll show a possible implementation of the first, leaving the others up to you. Note that I will use a subclass of QWidget, which gives a better code/object structure and allows better implementation.
from PyQt5 import QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__():
super().__init__()
self.setWindowTitle('Set tag')
mainLayout = QtWidgets.QVBoxLayout(self)
topLayout = QtWidgets.QHBoxLayout()
mainLayout.addLayout(topLayout)
label = QtWidgets.QLabel('Číslo sezení')
topLayout.addWidget(label)
input_session = QtWidgets.QLineEdit()
topLayout.addWidget(input_session)
button = QtWidgets.QPushButton('Zaznamenat tag')
topLayout.addWidget(button)
midLayout = QtWidgets.QHBoxLayout()
mainLayout.addLayout(midLayout)
label_result = QtWidgets.QLabel('Výsledky')
midLayout.addWidget(label_result)
mainLayout.addStretch()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
myWidget = MyWidget()
myWidget.show()
sys.exit(app.exec_())
How do I get the font size and color of the text in the tooltip to change from that of the button? It keeps displaying as the size/font of the pushbutton instead of it's own. Also how do I get the text "leave the program" to actually fit in the tooltip?
I've tried this method and couldn't get it to work:
Setting the text colour of a tooltip in PyQt
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(100, 100)
self.setWindowTitle("Example")
self.leave = QPushButton("X", self)
self.leave.setStyleSheet("background-color : grey ; color : red ; font: 20pt")
self.leave.setToolTip("Leave the Program")
self.setStyleSheet("QToolTip{background-color : blue ; color: k ; font: 12pt}")
self.leave.move(38, 25)
self.leave.resize(24, 50)
self.leave.clicked.connect(self.exit)
def exit(self):
app.quit()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
The tool tip in question is a child of the button not of the main window so it will inherit the style sheet of the button (as well as those of the button's ancestors). Since you didn't specify a selector in the style sheet of the button, the style rules in this style sheet will be applied to all the button's children including the tool tip (unless they have a style sheet themselves). One way around this is to limit the button's style sheet to QPushButton objects only by doing something like
self.leave.setStyleSheet("QPushButton{background-color : grey ; color : red ; font: 20pt}")
Furthermore, to get the background color of the tool tip to change from default I needed to specify a style rule for its border, e.g.
self.setStyleSheet(" QToolTip{ border: 1px solid white; background-color: blue ; color: k ; font: 12pt}")
I am not sure if this is a bug or by design.
Screenshot:
I have an existing application that I am polishing off and I want to add some animation to a few of the widgets. Animating widgets with QPropertyAnimation outside of layouts is easy and fun, however when they are in a layout I am having various difficulties. The current one giving me a headache is that when I animate the size of a widget, the layout does not adjust to it's new size.
So lets say I have a QVBoxLayout with three widgets: a label which should expand to all available space, a treeview, and a button. When I click the button I want the tree to collapse and the label to take over it's space. Below is this example in code, and as you can see while the tree animates it's size nothing happens, and then when I hide it at the end of the animation the label pops to fill the now vacant space. So it seems that during the animation the layout does not "know" the tree is resizing. What I would like to happen is that AS the tree shrinks, the label expands to fill it.
Could this could be done not by absolute sizing of the label, but by calling a resize on the layout or something like that? I ask because I want to animate several widgets across my application and I want to find the best way to do this without having to make too many widgets interdependent upon each other.
Example code:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout()
self.setLayout(layout1)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
sefl.file_model.setRootPath("C:/")
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_size_anim = QtCore.QPropertyAnimation(self.browse_tree, "size")
self.tree_size_anim.setDuration(1000)
self.tree_size_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_pos_anim = QtCore.QPropertyAnimation(self.browse_tree, "pos")
self.tree_pos_anim.setDuration(1000)
self.tree_pos_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_anim_out = QtCore.QParallelAnimationGroup()
self.tree_anim_out.addAnimation(self.tree_size_anim)
self.tree_anim_out.addAnimation(self.tree_pos_anim)
def shrink_tree(self):
self.tree_size_anim.setStartValue(self.browse_tree.size())
self.tree_size_anim.setEndValue(QtCore.QSize(self.browse_tree.width(), 0))
tree_rect = self.browse_tree.geometry()
self.tree_pos_anim.setStartValue(tree_rect.topLeft())
self.tree_pos_anim.setEndValue(QtCore.QPoint(tree_rect.left(), tree_rect.bottom()))
self.tree_anim_out.start()
self.tree_anim_out.finished.connect(self.browse_tree.hide)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The layouts handle the geometry() of the widgets so that when wanting to change the pos property these are interfacing with their handles so it is very common that you get that type of behavior, a better option is to use a QVariantAnimation to establish a fixed height:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout(self)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
self.file_model.setRootPath(QtCore.QDir.rootPath())
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_anim = QtCore.QVariantAnimation(self)
self.tree_anim.setDuration(1000)
self.tree_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
def shrink_tree(self):
self.tree_anim.setStartValue(self.browse_tree.height())
self.tree_anim.setEndValue(0)
self.tree_anim.valueChanged.connect(self.on_valueChanged)
self.tree_anim.start()
def on_valueChanged(self, val):
h, isValid = val.toInt()
if isValid:
self.browse_tree.setFixedHeight(h)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()