python qt : automatically resizing main window to fit content - qt

I have a main window which contains a main widget, to which a vertical layout is set. To the layout is added a QTableWidget only (for the moment).
When I start the application and call show on the main_window, only part of the QTableWidget is shown. I can extend the window manually to see it all, but I would like the window to have its size nicely adapted to the size of the QTableWidget.
Googling the question found a lot of posts on how to use resize to an arbitrary size, and call to resize(int) works fine, but this is not quite what I am asking
Lots of other posts are not explicit enough, e.g "use sizePolicy" or "use frameGeometry" or "use geometry" or "use sizeHint". I am sure all of them may be right, but an example on how to would be awesome.

You can do something like this, from within your MainWindow after placing all the elements you need in the layout:
self.setFixedSize(self.layout.sizeHint())
This will set the size of the MainWindow to the size of the layout, which is calculated using the size of widgets that are arranged in the layout.

I think overriding sizeHint() on the QTableWidget is the key:
import sys
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget
class Table(QTableWidget):
def sizeHint(self):
horizontal = self.horizontalHeader()
vertical = self.verticalHeader()
frame = self.frameWidth() * 2
return QSize(horizontal.length() + vertical.width() + frame,
vertical.length() + horizontal.height() + frame)
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
top = Table(3, 5, self)
self.setCentralWidget(top)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())

You can use sizeHint() but not as stated in the other answers. sizeHint() returns a QSize object with a width and height. Let's say you have a main window mainWindow and a widget inside it called content. If your resizing involves content height to get bigger, you can fit the mainWindow to it like this:
mainWindow.resize(mainWindow.sizeHint().width,
mainWindow.size().height() + content.sizeHint().height());

Old but i experienced this a while back and seeing how the answers here didn't exactly work for me.
Here's what i did:
Please make sure you have the central widget for the 'mainwindow' set properly and the parent of the layout is the central widget,
Then set a sizepolicy for the mainwindow/widget as you wish.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class RandomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(RandomWidget, self).__init__(parent)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
self.ui()
self.layout.addWidget(self.table)
self.layout.addWidget(self.table2)
def ui(self):
self.table = QtWidgets.QTableWidget()
self.table.setMinimumSize(800,200)
self.table2 = QtWidgets.QTableWidget()
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
self.widget = None
super(Mainwindow, self).__init__()
self.setWindowTitle('test')
def ui(self):
self.setCentralWidget(self.widget)
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Window = Mainwindow()
Window.widget = RandomWidget(Window)
Window.ui()
sys.exit(app.exec_())

Related

Qt: Understanding QScrollArea::widgetResizable property

I am experimenting with Qt 5 QScrollArea (in Python and PyQt, but I believe the question applies just as well in C++ Qt).
The Qt documentation for QScrollArea::widgetResizable says that "If this property is set to false (the default), the scroll area honors the size of its widget." By "its widget", I assume it means the widget being viewed in the scroll area.
However, in the program below I show an image label inside the scroll area, but the scroll area does not seem to "honor the size of its widget", because the image is partly hidden from the start.
The documentation also says "Regardless of this property, you can programmatically resize the widget using widget()->resize(), and the scroll area will automatically adjust itself to the new size." However, I do invoke resize for the viewed widget, but nothing happens.
The documentation also says "If this property is set to true, the scroll area will automatically resize the widget in order to avoid scroll bars where they can be avoided, or to take advantage of extra space." However, I don't see any resizing, even though if the widget were resized then it would be possible to avoid the scroll bars.
This is what I see whether I set the property to True or False, and whether I invoke widget().resize() or not:
Clearly I must be missing something quite fundamental here; what is it?
Edit: the main purpose of the question is understanding how widgetResizable works and what it does. Fitting the image into the window is a secondary goal.
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QImage, QPalette, QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QScrollArea
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
image = QImage("happyguy.png")
imageLabel = QLabel()
imageLabel.setPixmap(QPixmap.fromImage(image))
scrollArea = QScrollArea()
scrollArea.setBackgroundRole(QPalette.Dark)
scrollArea.setWidget(imageLabel)
scrollArea.setWidgetResizable(True)
scrollArea.widget().resize(QSize(10, 10))
self.setCentralWidget(scrollArea)
app = QApplication([])
w = MainWindow()
w.show()
app.exec_()
And here's the happyguy.pgn file:
scrollArea.setWidgetResizable(True) give the resize control of imageLabel to scrollArea. So the next line scrollArea.widget().resize(QSize(10, 10)) will be overrode by system.
A solution worked on windows (resize main window to fit image size).
from PyQt5.QtGui import QImage, QPalette, QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QScrollArea, QFrame
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
image = QImage("happyguy.png")
imageLabel = QLabel()
imageLabel.setPixmap(QPixmap.fromImage(image))
scrollArea = QScrollArea()
scrollArea.setFrameShape(QFrame.NoFrame)
scrollArea.setBackgroundRole(QPalette.Dark)
scrollArea.setWidget(imageLabel)
self.setCentralWidget(scrollArea)
self.resize(image.size())
app = QApplication([])
w = MainWindow()
w.show()
app.exec_()
Or use QScrollArea.setMinimumSize
from PyQt5.QtGui import QImage, QPalette, QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QScrollArea, QFrame
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
image = QImage("happyguy.png")
imageLabel = QLabel()
imageLabel.setPixmap(QPixmap.fromImage(image))
scrollArea = QScrollArea()
scrollArea.setFrameShape(QFrame.NoFrame)
scrollArea.setBackgroundRole(QPalette.Dark)
scrollArea.setWidget(imageLabel)
scrollArea.setMinimumSize(image.size())
self.setCentralWidget(scrollArea)
app = QApplication([])
w = MainWindow()
w.show()
app.exec_()
Resizable IS NOT Scrollable ...

Set editor width in QTreeWidget to fill cell

By default if a cell is edited in a QTreeWidget, the editor changes its width based on length of text.
Is it possible to set the editorĀ“s width to fill the cell?
Here is the code to reproduce the screenshot:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Example(QTreeWidget):
def __init__(self):
super().__init__()
self.resize(600, 400)
self.setHeaderLabels(['Col1', 'Col2', 'Col3', 'Col4'])
self.setRootIsDecorated(False)
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QAbstractItemView.SelectItems)
# self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setStyleSheet('QTreeView { show-decoration-selected: 1;}')
for i in range(5):
item = QTreeWidgetItem(['hello', 'bello'])
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.addTopLevelItem(item)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can create a simple QStyledItemDelegate and override its updateEditorGeometry() in order to always resize it to the index rectangle:
class FullSizedDelegate(QStyledItemDelegate):
def updateEditorGeometry(self, editor, opt, index):
editor.setGeometry(opt.rect)
class Example(QTreeWidget):
def __init__(self):
# ...
self.setItemDelegate(FullSizedDelegate(self))
** UPDATE **
The default text editor for all item views is an auto expanding QLineEdit, which tries to expand itself to the maximum available width (the right edge of the viewport) if the text is longer than the visual rectangle of the item. In order to avoid this behavior and always use the item rect, you have to return a standard QLineEdit. In this case the updateGeometry override is usually not necessary anymore (but I'd keep it anyway, as some styles might still prevent that):
class FullSizedDelegate(QStyledItemDelegate):
def createEditor(self, parent, opt, index):
if index.data() is None or isinstance(index.data(), str):
return QLineEdit(parent)
return super().createEditor(parent, opt, index)

QMainWindow and child widgets size don't match

Probably a noob question, but I'm still learning PySide. So I'm trying to use QMainWindow which has a QFrame and the QFrame has two labels. I'm using QBoxLayouts on QMainWindow and QFrame. The problem is that when I set the QFrame to something like 200x200 then QMainWindow does not resize, it remains too small to display both labels. Correct me if I'm wrong but shouldn't QMainWindow automatically have the right size when using layouts? Additionaly when I output frame.sizeHint() then it outputs PySide.QtCore.QSize(97, 50) but I would expect it to be 200, 200.
The code below will reproduce the problem:
import sys
from PySide import QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
#-------
#CREATE WIDGETS
#-------
frame = QtGui.QFrame()
frame.setStyleSheet("QFrame {background-color: yellow}")
frame.setGeometry(0, 0, 200, 200)
someLabel = QtGui.QLabel("SomeLabel")
someOtherLabel = QtGui.QLabel("SomeOtherLabel")
self.setCentralWidget(frame)
#--------
#CREATE LAYOUT
#--------
frameLayout = QtGui.QVBoxLayout()
frameLayout.addWidget(someLabel)
frameLayout.addWidget(someOtherLabel)
frame.setLayout(frameLayout)
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(frame)
self.setLayout(mainLayout)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is what happens after code is run:
A QMainWindow already has a top-level layout, so you should never set one yourself. All you need to do is set the central-widget, and then add a layout and widgets to that.
Your example can therefore be fixed like this:
frame.setLayout(frameLayout)
# get rid of these three lines
# mainLayout = QtGui.QVBoxLayout()
# mainLayout.addWidget(frame)
# self.setLayout(mainLayout)
self.show()
It's worth noting that there is possibly a bug/misfeature in PySide regarding this, because in PyQt your original script would print a useful error message:
QWidget::setLayout: Attempting to set QLayout "" on MainWindow "", which already has a layout

Semi-resizable widgets in PyQt

I try to create a gui with two main widgets. The window should be resizable. When resized horizontally only one of them widgets should expand. When resized vertically both should expand. Furthermore it should be possible readjust the resize this split horizontally. I illustrated this to make it more clear:
With tkinter this was easily achievable with the properties expand and fill. In Qt I could use the resize event but I hope that I don't have to do this manually, since this should after all be a common task. I tried toying around with QHBoxLayout but without success unfortunately.
Any help is greatly appreciated. Thanks.
You need to use the setStretchFactor method on your QSplitter.
An example (modified from the QSplitter example here):
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
left = QtGui.QFrame(self)
left.setFrameShape(QtGui.QFrame.StyledPanel)
right = QtGui.QFrame(self)
right.setFrameShape(QtGui.QFrame.StyledPanel)
splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter.addWidget(left)
splitter.addWidget(right)
splitter.setStretchFactor(1, 1)
splitter.setSizes([125, 150])
hbox.addWidget(splitter)
self.setLayout(hbox)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QSplitter')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This produces an initial UI that looks like this:
When the image is expanded horizontally, you can see that the left widget stays the same size:
When expanded vertically, both widgets expand:
Finally, the splitter is resizeable:
If you adjust the window size after adjusting the splitter, the left widget will retain it's size and the right will expand/collapse to fill the remainder of the window.

How to set background color to entire widget with stylesheet in PySide

I am trying to set a background color of a widget, but it only applies to widget's children. The code below is a simple representation of the real app structure. I'd like testWidget to be entirely red, which is 100x100 pixel rectangle due to it's size, but for some reason only the button is red.
from PySide import QtGui
class Widget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
mainLayout = QtGui.QVBoxLayout(self)
testWidget = QtGui.QWidget()
testWidget.setFixedSize(100,100)
testWidget.setStyleSheet('background-color: red;')
testLayout = QtGui.QVBoxLayout()
testWidget.setLayout(testLayout)
but = QtGui.QPushButton('TEST')
but.setFixedSize(20,20)
testLayout.addWidget(but)
mainLayout.addWidget(testWidget)
w = Widget()
w.show()
By default, a QWidget does not fill its background. You can either use a QFrame instead or setting the WA_StyledBackground attribute of the QWidget to True as said here : PySide: QWidget does not draw background color.
To apply the style sheet only to the container, and not to its children, the container widget can be named and the style sheet can specifically be applied to it by referring to its name.
Below is a MWE, derived from your code, that shows how it can be done using a QFrame instead of a QWidget :
from PySide import QtGui
import sys
class Widget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
mainLayout = QtGui.QVBoxLayout(self)
testWidget = QtGui.QFrame()
testWidget.setFixedSize(100,100)
testWidget.setObjectName("myWidget")
testWidget.setStyleSheet("#myWidget {background-color:red;}")
testLayout = QtGui.QVBoxLayout()
testWidget.setLayout(testLayout)
but = QtGui.QPushButton('TEST')
testLayout.addWidget(but)
mainLayout.addWidget(testWidget)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
instance_1 = Widget()
instance_1.show()
sys.exit(app.exec_())
which results in:

Resources