I created a small program with PyQt in which I put a QComboBox, but this one contains only a list of 2 letters. The program window being small, to save space, I would like to reduce the width of the QComboBox.
This is what it looks like now. The width is too large.
I searched the internet, but after a lot of searching time, I still haven't found anything. Thank you in advance if you have an idea.
There are several methods to resize a widget's size. Lets say the QComboBox is defined as this:
combo = QComboBox(self)
One way is to use QWidget.resize(width, height)
combo.resize(200,100)
To obtain a proper size automatically, you can use QWidget.sizeHint() or sizePolicy()
combo.resize(combo.sizeHint())
If you want to set fixed size, you can use setFixedSize(width, height), setFixedWidth(width), or setFixedHeight(height)
combo.setFixedSize(400,100)
combo.setFixedWidth(400)
combo.setFixedHeight(100)
Here's an example:
from PyQt5.QtWidgets import (QWidget, QLabel, QComboBox, QApplication)
import sys
class ComboboxExample(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("Ubuntu", self)
self.combo = QComboBox(self)
self.combo.resize(200,25)
# self.combo.resize(self.combo.sizeHint())
# self.combo.setFixedWidth(400)
# self.combo.setFixedHeight(100)
# self.combo.setFixedSize(400,100)
self.combo.addItem("Ubuntu")
self.combo.addItem("Mandriva")
self.combo.addItem("Fedora")
self.combo.addItem("Arch")
self.combo.addItem("Gentoo")
self.combo.move(25, 25)
self.label.move(25, 75)
self.combo.activated[str].connect(self.onActivated)
# self.setGeometry(0, 0, 500, 125)
self.setWindowTitle('QComboBox Example')
self.show()
def onActivated(self, text):
self.label.setText(text)
self.label.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ComboboxExample()
sys.exit(app.exec_())
Related
I am trying to find solution by some time but nothing works so far..
I made a simple QTableWidget in PyQt5 and while casual QTableWidgetItems and QLabel resizes well using the option .resizeColumnsToContents(), the QPushButton somehow keeps its minimum width which makes the column too wide.
I tried using setMinimumWidth(1) on the button but it didn't work.
Here is the code I made:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTableWidget, QApplication, QTableWidgetItem, QPushButton, QLabel
class Table(QTableWidget):
def __init__(self, *args):
super().__init__(*args)
button = QPushButton("X")
button.setMinimumWidth(1)
self.setHorizontalHeaderLabels(["1", "2", "3", "4"])
self.setItem(0, 0, Cell("Val1"))
self.setCellWidget(0, 1, button)
self.setCellWidget(0, 2, QLabel("Y"))
self.setItem(0, 3, Cell("Val3"))
self.resizeColumnsToContents()
self.resizeRowsToContents()
class Cell(QTableWidgetItem):
def __init__(self, *args):
super().__init__(*args)
self.setTextAlignment(Qt.AlignCenter)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Table(1, 4)
widget.show()
sys.exit(app.exec_())
When using widgets in item views, the header auto resizing uses the widget's size hint, not the minimum size.
A QPushButton normally has a wider size hint (even if it doesn't have text), so you have two options:
use a QToolButton instead (which is normally smaller by default);
create a subclass of QPushButton and override its sizeHint();
In the second case, you could just return a minimum arbitrary size:
class PushButton(QPushButton):
def sizeHint(self):
return QSize(10, 10)
# ...
button = PushButton("X")
self.setCellWidget(0, 1, button)
But that wouldn't be appropriate, because you'd need to try to show the full text of the button.
A proper solution should use the style, which has a function that can compute the proper sizes of widgets and other elements: sizeFromContents().
Note that in this case you should use a QStyleOptionToolButton and the CT_ToolButton type (instead of QStyleOptionButton and CT_PushButton), otherwise you'll end up with a larger button anyway:
class PushButton(QPushButton):
def sizeHint(self):
self.ensurePolished()
opt = QStyleOptionToolButton()
opt.initFrom(self)
fm = self.fontMetrics()
textSize = fm.size(Qt.TextShowMnemonic, self.text())
textSize.setWidth(textSize.width() +
fm.horizontalAdvance(' ') * 2)
opt.rect.setSize(textSize)
return self.style().sizeFromContents(
QStyle.CT_ToolButton, opt, textSize)
I'm looking for a solution, to animate this arc from 0 - 360°. I'm relative new to Pyside/Pyqt and I don't find such a simple solution (only beginner "unfriedly"). I tried it with while loops aswell, but it doesn't works. At the moment I don't understand this animation system, but I want to work on it.
import sys
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import Qt
from PySide6.QtGui import QBrush, QPen, QPainter
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("AnimateArc")
self.setGeometry(100, 100, 600, 600)
def paintEvent(self, event):
self.anim = QtCore.QPropertyAnimation(self, b"width", duration=1000) #<- is there a documentation for b"width", b"geometry"?
self.anim.setStartValue(0)
start = 0
painter = QPainter(self)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawArc(100, 100, 400, 400, 90 * 16, start * 16) # I want to make the change dynamicly
self.anim.setEndValue(360)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
QPropertyAnimation is used to animate Qt properties of any QObject. If you refer to self (the current instance of QMainWindow), then you can animate all properties of a QMainWindow and all inherited properties (QMainWindow inherits from QWidget, so you can animate all the QWidget properties as well).
In your case, you're trying to animate the width property of the window, and that's certainly not what you want to do.
Since what you want to change is a value that is not a property of the window, you cannot use QPropertyAnimation (unless you create a Qt property using the #Property decorator), and you should use a QVariantAnimation instead.
Then, a paintEvent is called by Qt every time the widget is going to be drawn (which can happen very often), so you cannot create the animation there, otherwise you could end up with a recursion: since the animation would require a repaint, you would create a new animation everytime the previous requires an update.
Finally, consider that painting on a QMainWindow is normally discouraged, as a Qt main window is a special kind of QWidget intended for advanced features (menus, status bar, etc) and uses a central widget to show the actual contents.
The correct approach is to create and set a central widget, and implement the painting on that widget instead.
Here is a revised and working version of your code:
class ArcWidget(QWidget):
def __init__(self):
super().__init__()
self.anim = QtCore.QVariantAnimation(self, duration=1000)
self.anim.setStartValue(0)
self.anim.setEndValue(360)
self.anim.valueChanged.connect(self.update)
self.anim.start()
def paintEvent(self, event):
painter = QPainter(self)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawArc(
100, 100, 400, 400, 90 * 16, self.anim.currentValue() * 16)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("AnimateArc")
self.setGeometry(100, 100, 600, 600)
self.arcWidget = ArcWidget()
self.setCentralWidget(self.arcWidget)
The valueChanged connection ensures that everytime the value changes the widget schedules an update (thus calling a paintEvent as soon as the event queue allows it), then you can use the current value of the animation to draw the actual arc.
Thanks #musicamante for the solution regarding animating arc using QAnimationProperty
Modified #musicmante code to create a loading effect guess it will help developers and might save their time who are trying to make loading effect using Qt
Source
#!/usr/bin/env python3.10
import sys
import string
import random
from PySide6.QtWidgets import (QMainWindow, QPushButton, QVBoxLayout,
QApplication, QWidget)
from PySide6.QtCore import (Qt, QVariantAnimation)
from PySide6.QtGui import (QPen, QPainter, QColor)
class Arc:
colors = list(string.ascii_lowercase[0:6]+string.digits)
shades_of_blue = ["#7CB9E8","#00308F","#72A0C1", "#F0F8FF",
"#007FFF", "#6CB4EE", "#002D62", "#5072A7",
"#002244", "#B2FFFF", "#6F00FF", "#7DF9FF","#007791",
"#ADD8E6", "#E0FFFF", "#005f69", "#76ABDF",
"#6A5ACD", "#008080", "#1da1f2", "#1a1f71", "#0C2340"]
shades_of_green = ['#32CD32', '#CAE00D', '#9EFD38', '#568203', '#93C572',
'#8DB600', '#708238', '#556B2F', '#014421', '#98FB98', '#7CFC00',
'#4F7942', '#009E60', '#00FF7F', '#00FA9A', '#177245', '#2E8B57',
'#3CB371', '#A7F432', '#123524', '#5E8C31', '#90EE90', '#03C03C',
'#66FF00', '#006600', '#D9E650']
def __init__(self):
self.diameter = random.randint(100, 600)
#cols = list(Arc.colors)
#random.shuffle(cols)
#_col = "#"+''.join(cols[:6])
#print(f"{_col=}")
#self.color = QColor(_col)
#self.color = QColor(Arc.shades_of_blue[random.randint(0, len(Arc.shades_of_blue)-1)])
self.color = QColor(Arc.shades_of_green[random.randint(0, len(Arc.shades_of_green)-1)])
#print(f"{self.color=}")
self.span = random.randint(40, 150)
self.direction = 1 if random.randint(10, 15)%2 == 0 else -1
self.startAngle = random.randint(40, 200)
self.step = random.randint(100, 300)
class ArcWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.arcs = [Arc() for i in range(random.randint(10, 20))]
self.startAnime()
def initUI(self):
#self.setAutoFillBackground(True)
self.setAttribute(Qt.WA_StyledBackground, True)
self.setStyleSheet("background-color:black;")
def startAnime(self):
self.anim = QVariantAnimation(self, duration = 2000)
self.anim.setStartValue(0)
self.anim.setEndValue(360)
self.anim.valueChanged.connect(self.update)
self.anim.start()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
#painter.setPen(QPen(QColor("#b6faec"), 5, Qt.SolidLine))
#painter.drawArc(
# 100, 100, 400, 400, 90*16,
# self.anim.currentValue() * 16)
#width = 400
#height = 400
#painter.drawArc(self.width()/2 -width/2, self.height()/2 - height/2, 400, 400, self.anim.currentValue()*16, 45*16)
for arc in self.arcs:
painter.setPen(QPen(arc.color, 6, Qt.SolidLine))
painter.drawArc(self.width()/2 - arc.diameter/2,
self.height()/2 - arc.diameter/2, arc.diameter,
arc.diameter, self.anim.currentValue()*16*arc.direction+arc.startAngle*100, arc.span*16)
#print(f"currentValue : {self.anim.currentValue()}")
#arc.startAngle = random.randint(50, 200)
if self.anim.currentValue() == 360:
#print("360")
self.startAnime()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Animate Arc")
self.setGeometry(100, 100, 600, 600)
self.arcWidget = ArcWidget()
self.setCentralWidget(self.arcWidget)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec()
output:
$ ./arc_widget.py
I am trying to create a dynamic GUI with multiple Groupbox objects in a QVBoxLayout. As there are a lot of them, I will be needing a scroll area to make them available to the end user.
So I tried to change to top widget of this tab from a QWidget to a QScrollArea.
Before the change:
This is the kind of result I want but with a scroll bar because the window is too high.
After the change to QScrollArea:
My GroupBoxs are now "collapsed" and there is not scrollbar. I tried setting their size but it is not adequate because they are not fixed. I searched the documentation and tried to use WidgetResizable or I tried to set a fixed height or the sizehint but nothing worked as I wanted.
After creating the the Groupbox, the sizeHint for my QScrollArea is already very low (around 150px of height) so I think I'm missing a parameter.
It would be complicated to provide code as it is intricate. If necessary I could recreate the problem in a simpler way.
How to reproduce:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import *
import sys
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
v_layout = QVBoxLayout()
scroll_area = QScrollArea()
self.layout().addWidget(scroll_area)
scroll_area.setLayout(v_layout)
# v_layout.setSizeConstraint(QLayout.SetMinimumSize)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Uncommenting # v_layout.setSizeConstraint(QLayout.SetMinimumSize) allows the content of the group boxes to deploy and fixes the first part of the issue. But there is still not scroll bar.
You have 2 errors:
A widget should not be added to the layout of a QMainWindow, but the setCentralWidget method should be used.
You should not add the layout to the QScrollArea but use a widget as a container for the other widgets, also if you use layouts then you have to activate the widgetResizable property.
Considering the above, the solution is:
def initUI(self):
scroll_area = QScrollArea(widgetResizable=True)
self.setCentralWidget(scroll_area)
container = QWidget()
scroll_area.setWidget(container)
v_layout = QVBoxLayout(container)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()
So, I'm working on an application and I'm using QTableWidget to generate a table. I want to put the table at the center of the window but I can't find a way to do this, it takes way too much space and its stucked at the top left of the window. I'm putting the table and a button in a QVBoxLayout, there's some blank space after the table (at the bottom and the right) and then the button, far away from the table.
Thats how its looking like
And thats how i want it to be
Right now my code is like this:
from PyQt5.QtWidgets import QHeaderView, QPushButton, QMainWindow, QApplication, QMenuBar, QAction, QFileDialog, QWidget, QTableView, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5 import QtGui
import sys
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.createWindow()
self.show()
def createWindow(self):
self.setWindowTitle('Pós Graduação')
self.setWindowIcon(QtGui.QIcon('icon.ico'))
self.setGeometry(300, 100, 700, 600)
self.table_widget = TableWidget(self)
self.setCentralWidget(self.table_widget)
class TableWidget(QWidget):
def __init__(self, parent):
super(TableWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.creatingTable(parent)
#self.setLayout(self.layout)
def creatingTable(self, parent):
tableWidget = QTableWidget()
tableWidget.setRowCount(6)
tableWidget.setColumnCount(4)
tableWidget.horizontalHeader().setVisible(False)
tableWidget.verticalHeader().setVisible(False)
header = tableWidget.horizontalHeader()
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
self.layout.addWidget(tableWidget)
self.button1 = QPushButton("Button 1")
self.layout.addWidget(self.button1)
self.setLayout(self.layout)
if __name__ == '__main__':
App = QApplication(sys.argv)
App.setStyle('Fusion')
window = MyApp()
sys.exit(App.exec())
An item view has a default size "hint" for Qt, which is the size that the widget suggests as the best to show its contents and make it as usable as possible. Also, each widget has a sizePolicy property, which tells how the widget behaves when it's part of a layout (should it shrink, grow, have a fixed size, etc).
Item views like QTableWidget (and its ancestor, QTableView) don't explicitly expose their contents, and that's for obvious reasons: a table could have thousands of rows and columns, and the layout shouldn't really care about them.
To be able to resize the table to its contents, the table has to cycle through all of them, and to do so it's better to do it whenever the contents change their sizes. The best approach is probably to connect to the signals the model and header provide, and set a fixed size each time they are fired, which is something that is better done with subclassing.
class TableWidgetSubclass(QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# update the size each time columns or rows are changed
self.model().columnsInserted.connect(self.updateSize)
self.model().columnsRemoved.connect(self.updateSize)
self.model().rowsInserted.connect(self.updateSize)
self.model().rowsRemoved.connect(self.updateSize)
# the same, when a section is resized; note that Qt requires some "time"
# to do so, so the call to the update function has to be delayed
self.horizontalHeader().sectionResized.connect(lambda: QTimer.singleShot(0, self.updateSize))
self.verticalHeader().sectionResized.connect(lambda: QTimer.singleShot(0, self.updateSize))
# ensure that the widget uses only the maximum required size
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
# and disable the scrollbars
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def updateSize(self):
width = 0
header = self.horizontalHeader()
# go through each column and add its size
for s in range(self.model().columnCount()):
width += header.sectionSize(s)
height = 0
header = self.verticalHeader()
# the same for rows
for s in range(self.model().rowCount()):
height += header.sectionSize(s)
size = QSize(width, height)
# since we've connected a lot of signals and the model could still
# be empty when calling this slot, ensure that the size is valid
if size.isValid():
self.setFixedSize(size)
Finally, when adding a widget to a layout, it usually uses as much space as possible. If it doesn't, it's aligned according to the default top-left. To avoid that, add the widget by specifying the alignment:
class TableWidget(QWidget):
def __init__(self, parent):
super(TableWidget, self).__init__(parent)
QVBoxLayout(self)
self.creatingTable(parent)
def creatingTable(self, parent):
tableWidget = TableWidgetSubclass()
# ...
self.layout().addWidget(tableWidget, alignment=Qt.AlignCenter)
self.button1 = QPushButton("Button 1")
self.layout().addWidget(self.button1, alignment=Qt.AlignCenter)
Note: as you can see, I didn't use self.layout =, and then I used self.layout(). That's for two reasons:
you should never overwrite basic class attributes (layout is a property of every QWidget)
when adding the target attribute to a layout constructor, that layout is automatically applied to the widget, and there's no need to use setLayout again, since it has already implicitly called.
I am trying to find a way to have one of my widgets maintain a width based on its height. I have other widgets working fine reimplementing the heightForWidth method. That was easy because that method is standard. I know there is no included widthForHeight method so I have tried many options the internet has suggested but have not gotten anything to work all the way. What I currently have almost gets me there.
I first reimplement my widget's sizeHint to get its width to be a ratio of its parent's (QHBoxLayout) height.
The sizeHint makes MyCustomLabel show with the right size at first show but did not update during times the user resized the window. I don't know if this the best way but to fix that I am reimplementing resizeEvent and calling adjustSize to force the sizeHint recalculation.
With those two reimplemented methods MyCustomLabel shows with the right size. I placed this custom widget in a QHBoxLayout with a few other standard widgets. The problem is the other widgets in the layout don't respect the new size of my MyCustomLabel when the user resizes the window. What I end up with is the other widgets in the layout either overlapping or being placed too far from MyCustomLabel. I kind of get it, I am brute forcing my widget to a size and not letting the layout do the work. However I thought updating the sizeHint would inform the layout of MyCustomLabel's new size and adjust everything accordingly. How do I fix this layout problem or am I going about this widthForHeight problem all the wrong way?
Edit:
I tried #AlexanderVX suggestion of setting the SizePolicy to Minimum and while it does prevent the other widgets from overlapping it also locked MyCustomLabel to a fixed size. I need the widget to expand and shrink with the layout. I also tried Preferred, Expanding, MinimumExpanding policies just to see if they would do anything but with no luck.
from __future__ import division
from PySide import QtCore
from PySide import QtGui
import sys
class MyCustomLabel(QtGui.QLabel):
clicked = QtCore.Signal(int)
dblClicked = QtCore.Signal(int)
def __init__(self, leadSide='height', parent=None):
super(MyCustomLabel, self).__init__()
self.leadSide = leadSide
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
# QtGui.QSizePolicy.Preferred)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
# QtGui.QSizePolicy.Expanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
# QtGui.QSizePolicy.MinimumExpanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
# QtGui.QSizePolicy.Minimum)
# self.setSizePolicy(sizePolicy)
def resizeEvent(self, event):
super(MyCustomLabel, self).resizeEvent(event)
self.adjustSize()
def sizeHint(self):
super(MyCustomLabel, self).sizeHint()
parentSize = self.parent().size().toTuple()
if self.leadSide.lower() == 'height':
new_size = QtCore.QSize(parentSize[1] * (16 / 9), parentSize[1]) * .9
if self.leadSide.lower() == 'width':
new_size = QtCore.QSize(parentSize[0], parentSize[0] / (16 / 9)) * .9
return new_size
class __Test__(QtGui.QWidget):
def __init__(self):
super(__Test__, self).__init__()
self.initUI()
def initUI(self):
customLabel = MyCustomLabel(leadSide='height')
# customLabel.setScaledContents(True)
customLabel.setStyleSheet('background-color: blue;'
'border:2px solid red;')
btn01 = QtGui.QPushButton('button')
btn01.setFixedHeight(80)
textEdit = QtGui.QTextEdit()
textEdit.setFixedSize(150, 150)
layout01 = QtGui.QHBoxLayout()
layout01.setContentsMargins(0,0,0,0)
layout01.setSpacing(0)
layout01.addWidget(customLabel)
layout01.addWidget(btn01)
layout01.addWidget(textEdit)
self.setLayout(layout01)
self.setGeometry(300, 300, 600, 300)
self.setWindowTitle('Testing')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = __Test__()
sys.exit(app.exec_())
if __name__ == '__main__':
main()