PyQt5 - Buttons and Label shall have the same height - python

it's a simple application. Just one label and three buttons. But the layout is making me trouble.
I want to have the childlayout for the label and the childlayout for the buttons to have the same height. But I can't get it to work. Neither with BoxLayouts nor with GridLayouts. I tried to addStretch, so both layouts have the same stretchfactor, and various different stuff. I'm sure the answer is easy but I can't figure it out.
I also tried it in QtDesigner, so I could inspect and compare the codeparts, but:
I designed this
and got this when running the code, again the buttons stick to the bottom and dont have the same height as the label
However, here's my code:
(I know that the Grid Layout is not necessary here, it was just some sort of trying to solve the problem. Anyways I don't think that the Grid Layout is the problem, as I had the same problems with BoxLayouts or just the label without a layout.)
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel, QVBoxLayout, QPushButton, QGroupBox, \
QGridLayout, QMainWindow, QSizePolicy
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 50, 400, 300)
self.setWindowTitle("Fretboard Note Quiz")
self.layout()
self.show()
def layout(self):
glay = QGridLayout()
self.setLayout(glay)
hbox1 = QHBoxLayout() #upper layout for label
hbox2 = QHBoxLayout() #bottom layout for buttons
btn1 = QPushButton("1")
btn2 = QPushButton("2")
btn3 = QPushButton("3")
#adding the buttons to the bottom layout
hbox2.addWidget(btn1)
hbox2.addWidget(btn2)
hbox2.addWidget(btn3)
label = QLabel("Text")
hbox1.addWidget(label)
glay.addLayout(hbox1, 0, 1)
glay.addLayout(hbox2, 1, 1)
glay.setRowStretch(0, 1)
glay.setRowStretch(1, 1)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())

In this case, the simplest thing is to use a QWidget as a container since by default they are stretched in the same way as the QLabel, so they will try to occupy the same space.
The problem with your initial code is that the stretch factors do not apply to layouts but to widgets.
Finally layout() is a method so do not hide it using it for other things, the names of the functions should describe the action of the method.
import sys
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 50, 400, 300)
self.setWindowTitle("Fretboard Note Quiz")
self.build_layout()
def build_layout(self):
btn1 = QPushButton("1")
btn2 = QPushButton("2")
btn3 = QPushButton("3")
label = QLabel("Text")
button_container = QWidget()
hlay = QHBoxLayout(button_container)
hlay.addWidget(btn1)
hlay.addWidget(btn2)
hlay.addWidget(btn3)
vlay = QVBoxLayout(self)
vlay.addWidget(label)
vlay.addWidget(button_container)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())

Maybe you could set the minimunHeight of your buttons like:
ui.pushButton.setMinimumHeight(100)

Related

How to auto-resize a PySide6 app to the minimum size when decreasing a `pixmap`?

I use the following code to set a PySide6 app to the minimal possible size. This works fine when increasing the size of the widgets (set - to + in line 37), but not when decreasing it - in effect, the size of the windows does decrease, but it seems to be one step late.
I found a few workarounds, most notably in Qt Layout, resize to minimum after widget size changes, but none of what I tried seems to be working (and I have met other issues with app.processEvents(), which should be avoided anyway).
Edit: In the new code example below, I think the problem is the width of the QPushButton, which is calculated too late.
Interestingly, this width of the QPushButton is solved by the layout.setSizeConstraint(QLayout.SetFixedSize) workaround, but the window width is not.
app.processEvents() works for this example, but I see bad side effects on other signals when using it.
New code example:
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QLayout, QMainWindow,
QPushButton, QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (effective, but discouraged)
self.adjustSize()
self.i += 1
app = QApplication()
win = Window()
app.exec()
Original code example:
import threading
import time
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QLayout,
QMainWindow,
QWidget,
)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel()
layout = QHBoxLayout()
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.show()
def run(self):
for i in range(10):
npix = 500 - 50 * i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (ineffective)
self.adjustSize()
time.sleep(1)
app = QApplication()
threading.Thread(target=Window().run).start()
app.exec()
#musicamante has posted very helpful comments, which I now turn into an answer.
Basically, this code works great:
from PySide6.QtCore import QMetaObject, Qt, QTimer, Slot
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton,
QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
# #Slot()
# def adjustSize(self):
# super().adjustSize()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
self.i += 1
# # As in https://stackoverflow.com/a/23954088/880783 - does not work
# QMetaObject.invokeMethod(self, "adjustSize")
# This works!
QTimer.singleShot(0, self.adjustSize)
app = QApplication()
win = Window()
app.exec()
As one can see, I have also tried the approach put forward https://stackoverflow.com/a/23954088/880783 - without success, however.

Why the horizontal scroll bar does not show up in PYQT6 QtextEdit widget

In the example below I am using PyQt6 to create text with long lines that exceed the QtextEdit widget's viewing size vertically and horizontally. The vertical scroll bar shows up however, the horizontal scroll bar does not show up. Any help with this issue is appreciated.
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QPushButton,
QWidget, QHBoxLayout, QVBoxLayout, QTextEdit)
from PyQt6.QtCore import QSize
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Example")
self.setContentsMargins(20,20,20,20)
self.setFixedSize(QSize(800, 600))
self.setWindowTitle("Example")
layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
self.output_text = QTextEdit()
self.button_start = QPushButton("Start")
self.button_start.clicked.connect(self.start)
layout1.addLayout(layout2)
layout2.addWidget(self.output_text)
layout2.addWidget(self.button_start)
widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
def start(self):
for i in range (1, 30):
self.output_text.append("Line " + str(i) + ": This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. ")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
If you want a horizontal scrollbar, you will need to set the QTextEdit column width higher than the QTextEdit area. So let's say for example 1000 will work for your code.
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QPushButton,
QWidget, QHBoxLayout, QVBoxLayout, QTextEdit)
from PyQt6.QtCore import QSize, Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Example")
self.setContentsMargins(20,20,20,20)
self.setFixedSize(QSize(800, 600))
self.setWindowTitle("Example")
layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
self.output_text = QTextEdit()
self.output_text.setLineWrapColumnOrWidth(1000) #Here you set the width you want
self.output_text.setLineWrapMode(QTextEdit.LineWrapMode.FixedPixelWidth)
self.button_start = QPushButton("Start")
self.button_start.clicked.connect(self.start)
layout1.addLayout(layout2)
layout2.addWidget(self.output_text)
layout2.addWidget(self.button_start)
widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
def start(self):
for i in range (1, 30):
self.output_text.append("Line " + str(i) + ": This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. This is a long line. ")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
And here is the result:
Tell me if this wasn't what you were looking for or if it doesn't work for you.

Why my QToolButton which I try to make collapsible doesn't collapse properly?

guys.
Asking for your help to troubleshoot my test script.
I am practicing to make collapsible button with widgets inside.
Script was mainly taken from another question in stackoverflow about collapsible buttons.
So I am trying to put under QTabWidget my class CollpsibleBox(QWidget). Problem is that my CollapsibleBox is acting very weird - buttons are jumping , sometimes it doesn't open/close properly.
I was wondering if it's some mistake in placing correctly my widget under QTabWidget or is there some problem with animation?
import random
from PySide2.QtGui import QPixmap, QBrush, QColor, QIcon, QPainterPath, QPolygonF, QPen, QTransform
from PySide2.QtCore import QSize, Qt, Signal, QPointF, QRect, QPoint, QParallelAnimationGroup, QPropertyAnimation, QAbstractAnimation
from PySide2.QtWidgets import QMainWindow, QDialog, QVBoxLayout, QHBoxLayout, QGraphicsView, QGraphicsScene, QFrame, \
QSizePolicy, QGraphicsPixmapItem, QApplication, QRubberBand, QMenu, QMenuBar, QTabWidget, QWidget, QPushButton, \
QSlider, QGraphicsPolygonItem, QToolButton, QScrollArea, QLabel
extraDict = {'buttonSetA': ['test'], 'buttonSetB': ['test']}
tabList = ['Main', 'Extra']
_ui = dict()
class MainWindow(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent=parent)
self.create()
def create(self, **kwargs):
_ui['mainLayout'] = QVBoxLayout()
_ui['tabWidget'] = QTabWidget()
_ui['mainLayout'].addWidget(_ui['tabWidget'])
for tab in tabList:
_ui['tab' + tab] = QWidget()
_ui['tabWidget'].addTab(_ui['tab' + tab], tab)
_ui['tabExtra'].layout = QVBoxLayout()
_ui['tabExtra'].setLayout(_ui['tabExtra'].layout)
_ui['content'] = QWidget()
_ui['tabExtra'].layout.addWidget(_ui['content'])
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box)
lay = QVBoxLayout()
for j in range(8):
label = QLabel("{}".format(j))
color = QColor(*[random.randint(0, 255) for _ in range(3)])
label.setStyleSheet(
"background-color: {}; color : white;".format(color.name())
)
label.setAlignment(Qt.AlignCenter)
lay.addWidget(label)
box.setContentLayout(lay)
self.setLayout(_ui['mainLayout'])
class CollapsibleBox(QWidget):
def __init__(self, name):
super(CollapsibleBox, self).__init__()
self.toggle_button = QToolButton(text=name, checkable=True, checked=False)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setArrowType(Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.toggle_animation = QParallelAnimationGroup(self)
self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0)
self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.content_area.setFrameShape(QFrame.NoFrame)
lay = QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self.content_area, b"maximumHeight"))
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(Qt.DownArrow if not checked else Qt.RightArrow)
self.toggle_animation.setDirection(QAbstractAnimation.Forward
if not checked
else QAbstractAnimation.Backward
)
self.toggle_animation.start()
def setContentLayout(self, layout):
lay = self.content_area.layout()
del lay
self.content_area.setLayout(layout)
collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight())
content_height = layout.sizeHint().height()
for i in range(self.toggle_animation.animationCount()):
animation = self.toggle_animation.animationAt(i)
animation.setDuration(500)
animation.setStartValue(collapsed_height)
animation.setEndValue(collapsed_height + content_height)
content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1)
content_animation.setDuration(500)
content_animation.setStartValue(0)
content_animation.setEndValue(content_height)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.setGeometry(500, 100, 500, 500)
window.show()
sys.exit(app.exec_())
The problem is that you are only adding two widgets to the full layout, and the layout will try to place them as better as possible (tipically at the center of the area that is available for each widget, based on its size hints).
You could either set the alignment of the widget for the layout (placing the buttons on top of their available space):
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box, alignment=Qt.AlignTop)
Or add a stretch to the bottom of the layout:
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
# ...
vlay.addStretch(1)
which will position all buttons on top of the layout.
As a side note, I'd suggest you to avoid the dictionary logic for the ui, as it might become very confusing and prone to errors. If you really need to do that for some (I hope, very good) reason that's ok, but please avoid it when asking questions: it makes really hard to read your code, and people might end up just ignoring your question at all.

How to control dimensions of layouts PyQt5

I'm trying to setup a layout similar to this question. Create Qt layout with fixed height. I'm trying to use the solution posted there but I cant seem to recreate it in python. How do I create a widget with a horizontal box layout and set the dimensions of the new widget? When I tried recreating the code in python my widget ended up as a layout instead of a widget. Below is the code I'm trying to rewrite in python, thanks in advance:
// first create the four widgets at the top left,
// and use QWidget::setFixedWidth() on each of them.
// then set up the top widget (composed of the four smaller widgets):
QWidget *topWidget = new QWidget;
QHBoxLayout *topWidgetLayout = new QHBoxLayout(topWidget);
topWidgetLayout->addWidget(widget1);
topWidgetLayout->addWidget(widget2);
topWidgetLayout->addWidget(widget3);
topWidgetLayout->addWidget(widget4);
topWidgetLayout->addStretch(1); // add the stretch
topWidget->setFixedHeight(50);
// now put the bottom (centered) widget into its own QHBoxLayout
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addStretch(1);
hLayout->addWidget(bottomWidget);
hLayout->addStretch(1);
bottomWidget->setFixedSize(QSize(50, 50));
// now use a QVBoxLayout to lay everything out
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(topWidget);
mainLayout->addStretch(1);
mainLayout->addLayout(hLayout);
mainLayout->addStretch(1);
The translation of C++ to Python does not have so much science since you only have to understand the logic:
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QApplication, QComboBox, QHBoxLayout, QLineEdit, QPushButton, QSpinBox, QToolButton, QVBoxLayout, QWidget
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
widget1 = QPushButton("widget1")
widget2 = QSpinBox()
widget3 = QComboBox()
widget4 = QLineEdit()
bottomWidget = QToolButton(text="botton")
# first create the four widgets at the top left,
# and use QWidget::setFixedWidth() on each of them.
# then set up the top widget (composed of the four smaller widgets):
topWidget = QWidget()
topWidgetLayout = QHBoxLayout(topWidget)
topWidgetLayout.addWidget(widget1)
topWidgetLayout.addWidget(widget2)
topWidgetLayout.addWidget(widget3)
topWidgetLayout.addWidget(widget4)
topWidgetLayout.addStretch(1)
topWidget.setFixedHeight(50)
# now put the bottom (centered) widget into its own QHBoxLayout
hLayout = QHBoxLayout()
hLayout.addStretch(1)
hLayout.addWidget(bottomWidget)
hLayout.addStretch(1)
bottomWidget.setFixedSize(QSize(50, 50))
# now use a QVBoxLayout to lay everything out
mainLayout = QVBoxLayout()
mainLayout.addWidget(topWidget)
mainLayout.addStretch(1)
mainLayout.addLayout(hLayout)
mainLayout.addStretch(1)
self.setLayout(mainLayout)
self.resize(640, 480)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

How to make QTabWidget look transparent?

I want to make a simple GUI desktop application using python.
I've made a simple window with a background image, and I added a tab-widget on the right corner. It worked properly. But the tab-bar and tab contents area are white.
What I want is that the tab-widget's background shows it's parent window's background image (which means it's transparent). But I don't know how to do it.
Here is my working environment, code and screen shot:
Working environment:
Windows 7
Python 3.4
PyQt5 5.5
Source code:
# coding: utf-8
# There are some unnecessary module.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QGridLayout
from PyQt5.QtGui import QPixmap, QPalette, QBrush, QColor
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
tab1 = QWidget()
tab2 = QWidget()
vbox1 = self.makeTab1()
vbox2 = self.makeTab2()
tab1.setLayout(vbox1)
tab2.setLayout(vbox2)
tabs = QTabWidget()
tabs.addTab(tab1, "firstTab")
tabs.addTab(tab2, "secondTab")
picLabel = QLabel(self)
picFile = 'background_img.jpg'
pixmap = QPixmap(picFile)
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(pixmap))
hboxEX = QHBoxLayout()
hboxEX.addStretch(2)
hboxEX.addWidget(tabs)
hboxEX.setStretchFactor(tabs, 1)
vboxEX = QVBoxLayout()
vboxEX.addStretch(1)
vboxEX.addLayout(hboxEX)
vboxEX.setStretchFactor(hboxEX, 1)
self.setLayout(vboxEX)
self.setPalette(palette)
self.resize(pixmap.width(), pixmap.height())
self.show()
def makeTab1(self):
lbl1 = QLabel(self)
lbl2 = QLabel(self)
lbl3 = QLabel(self)
lbl1.setText("Google")
lbl2.setText("WikiPedia")
lbl3.setText("StackOverflow")
lbl1.setOpenExternalLinks(True)
lbl2.setOpenExternalLinks(True)
lbl3.setOpenExternalLinks(True)
vbox1 = QVBoxLayout()
vbox1.addWidget(lbl1)
vbox1.addWidget(lbl2)
vbox1.addWidget(lbl3)
return vbox1
def makeTab2(self):
lbl4 = QLabel(self)
lbl5 = QLabel(self)
lbl6 = QLabel(self)
lbl4.setText("Python")
lbl5.setText("CentOS")
lbl6.setText("MariaDB")
lbl4.setOpenExternalLinks(True)
lbl5.setOpenExternalLinks(True)
lbl6.setOpenExternalLinks(True)
vbox2 = QVBoxLayout()
vbox2.addWidget(lbl4)
vbox2.addWidget(lbl5)
vbox2.addWidget(lbl6)
return vbox2
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Screen shot:
What I tried:
I added these codes and it changed the tab's color. But it didn't make the tab area look transparent:
tabPalette = QPalette()
tabPalette.setColor(QPalette.Background, QColor("cyan"))
tab1.setAutoFillBackground(True)
tab1.setPalette(tabPalette)
I think it's a Windows problem. You might want to try to switch your application to a different style.
See this page of PyQt reference http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html
some sample code:
def changeStyle(self, styleName):
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create(styleName))
self.changePalette()
call it with:
changeStyle("plastique")

Categories

Resources