Why I do have strange extra space? - qt

I have a SingleTweetWidget to display a tweet.
If I put it into a QScrollArea, everything is working fine.
class TweetListWidget(QtGui.QWidget):
def __init__(self, client=None, parent=None):
super(TweetListWidget, self).__init__(parent)
self.setupUi()
def setupUi(self):
self.layout = QtGui.QVBoxLayout(self)
self.setLayout(self.layout)
def setModel(self, model):
self.model = model
self.model.rowsInserted.connect(self._rowsInserted)
def _rowsInserted(self, parent, start, end):
for index in range(start, end + 1):
item = self.model.get_item(index)
widget = SingleTweetWidget(self.client, item)
self.layout.insertWidget(index, widget)
But, if I put it into a dialog, there will be some extra space.
def setupUi(self, widget):
super(NewpostWindow, self).setupUi(widget)
tweet = SingleTweetWidget(self.client, self.tweet, self)
self.verticalLayout.insertWidget(0, tweet)
Please notice the space between the time (6s ago) and the blue separator line.
Where is it come from? I have no idea with it.
By the way, you can get the source code of SingleTweetWidget from https://github.com/WeCase/WeCase/blob/dev-0.06/src/TweetListWidget.py

QDialog has a layout which put a vertical space between widgets. It's because the default minimum height of QDialog is higher than the height of the two widgets. You can use self->setMinimumHeight(int) and self->setMaximumHeight(int) and the width variants or self->setFixedSize(w,h), etc...
You can set max/min width/height with every widget.
Read something about QLayout, QDialog and the Qt and see some examples. Qt have very good documentation. See
http://qt-project.org/doc/
http://qt-project.org/doc/qt-4.8/examples-layouts.html
http://qt-project.org/doc/qt-4.8/qwidget.html#setFixedSize

Related

How to avoid over-packing non-srolling Qt layouts?

A Qt packing layout, such as QVBoxLayout, can pack widgets inside it, such as buttons. In this case, they will be packed vertically as shown in image below:
When we pack too many widgets inside such a layout, and since scrolling is not added by default, the buttons will eventually get squeezed onto each other up to a point that they will overlap, as shown below:
My questions are:
How to tell Qt to not show/pack widgets beyond the available viewing space in the non-scrolling layout?
How to handle the case when the window is resized? I.e. Qt should add/remove widgets accordingly. E.g. if there is extra space available, then perhaps Qt should add some extra widgets that it couldn't add previously.
To be specific: "too many packed widgets" is when the widgets start invading spaces of other widgets, including their inter-widget spacings or margins.
Appendix
Images above are generated by this code below as run in a tile in i3, which is a modified version of this.
from PyQt5 import QtCore, QtWidgets
app = QtWidgets.QApplication([])
widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(widget)
for i in range(40):
layout.addWidget(QtWidgets.QPushButton(str(i + 1)))
widget.show()
app.exec_()
When too many widgets are packed:
If the window is tiled, you see them overcrowded as in in the image.
If the window is floating, the window will keep growing until it is no longer fully visible in the monitor.
None of these outcomes are acceptable in my case. My goal is to have Qt only pack as much as will be visible, and add/remove/hide/show dynamically as the window gets resized.
Try this code. It does not rely on QVBoxLayout but it basically does the same as this layout. It hides the child widgets which are outside of the area. There are no partially visible widgets.
from PyQt5 import QtWidgets
class Container(QtWidgets.QWidget):
_spacing = 5
def __init__(self, parent=None):
super().__init__(parent)
y = self._spacing
for i in range(40):
button = QtWidgets.QPushButton("Button" + str(i + 1), self)
button.move(self._spacing, y)
y += button.sizeHint().height() + self._spacing
def resizeEvent(self, event):
super().resizeEvent(event)
for child in self.children():
if isinstance(child, QtWidgets.QWidget):
child.resize(self.width() - 2 * self._spacing, child.height())
child.setVisible(child.geometry().bottom() < self.height())
app = QtWidgets.QApplication([])
w = Container()
w.resize(500, 500)
w.show()
app.exec_()
Note that is in fact does not add nor remove widgets dynamically, this would be much more code and it would probably be very depending on your specific use case. Moreover it feels as a premature optimization. Unless you really need it, do not do it.
UPDATE:
I experimented with the code above and proposed some improvements. I especially wanted to make it responsive to changes in child widgets. The problem is that if the child widget changes it size, the parent container must be re-layouted. The code above does not react in any way. To make it responsive, we need to react to LayoutRequest event. Note that in the code below, I have created three types of buttons - one add a line to itself, other increases font size, and yet another decreases font size.
from PyQt5 import QtCore, QtWidgets
def changeFontSize(increment):
font = QtWidgets.QApplication.font()
font.setPointSize(font.pointSize() + increment)
QtWidgets.QApplication.setFont(font)
class Container(QtWidgets.QWidget):
_spacing = 5
_children = [] # maintains the order of creation unlike children()
def __init__(self, parent=None):
super().__init__(parent)
for i in range(100):
child = QtWidgets.QPushButton(self)
child.installEventFilter(self)
# these are just to test various changes in child widget itself to force relayout
r = i % 3
if r == 0:
text = "New line"
onClicked = lambda state, w=child: w.setText(w.text() + "\nclicked")
elif r == 1:
text = "Bigger font"
onClicked = lambda: changeFontSize(1)
elif r == 2:
text = "Smaller font"
onClicked = lambda: changeFontSize(-1)
child.setText(text)
child.clicked.connect(onClicked)
self._children.append(child)
def resizeEvent(self, event):
super().resizeEvent(event)
self._relayout()
def event(self, event):
if event.type() == QtCore.QEvent.LayoutRequest:
self._relayout()
return super().event(event)
def _relayout(self):
y = self._spacing
for child in self._children:
h = child.sizeHint().height()
child.move(self._spacing, y)
child.resize(self.width() - 2 * self._spacing, h)
y += h + self._spacing
child.setVisible(y < self.height())
app = QtWidgets.QApplication([])
w = Container()
w.resize(500, 500)
w.show()
app.exec_()
This code is satisfactory, however it is not perfect. I have observed that when the container is being re-layouted and some of the child widgets will change its visibility state, re-layouting is called again. This is not needed but I have not discovered how to prevent it.
Maybe there is some better way...

Subclassed QFrame not adjusting to it's layout

I'm trying to include a horizontal frame containing a label inside a vertical frame, but even though the label is displayed it's not in the right position and it's limited to a size of a standard QLabel
This is the main class:
class Launcher(QMainWindow):
def __init__(self):
super().__init__()
self.setFrame() #sets up window's geometry, works fine
self.setContent()
self.show()
def setContent(self):
layout = QBoxLayout(QBoxLayout.TopToBottom)
layout.addWidget(widgets.Logo(self), 0, Qt.AlignTop)
self.setLayout(layout)
And this is the imported class from a "widgets" module
class Logo(QFrame):
def __init__(self, parent):
super().__init__(parent)
layout = QBoxLayout(QBoxLayout.LeftToRight)
text = QLabel("PyTitle", self)
text.setAlignment(Qt.AlignCenter)
text.setFont(QFont("impact", 48))
layout.addWidget(text, 0, Qt.AlignCenter)
self.setLayout(layout)
self.show()
The result is this:
If I forcefully resize both QLabel AND QFrame, it's visible, but still in the top-left.
You must not set a layout on a QMainWindow, because it already has one built in (to handle dock-widgets, the menu-bar, status-bar, etc).
Instead, set a central-widget, and add all the widgets and layouts to that:
class Launcher(QMainWindow):
...
def setContent(self):
widget = widgets.Logo(self)
self.setCentralWidget(widget)
(PS: you only need to call show() on the top-level window - for all other child widgets, it's redundant).

python qt : automatically resizing main window to fit content

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_())

Centralize QTableView in window with vertical spacers

I am trying to create a main window (fixed size) that contains a QTableView, with QSpacerItems above and below, in order to centralise the table (vertically).
(Sorry, can't post an image, apparently).
I have a QVBoxLayout, into which I have a vertical spacer, the QTableView, and another vertical spacer. I've played with all combinations of QSizePolicy for all three widgets, but I cannot get the table to be displayed without scrollbars. (I cannot use Qt.ScrollBarAlwaysOff because they will be needed if the number of items exceeds the main window's size). So the vertical scrollbars on the QTableView are displayed, even though the vertical spacers are absorbing plenty of space between the view and the main window.
I want the vertical spacers to take up the minimum space required above and below the table widget in order to centralise the rows, and the table widget to display as many rows as possible, without scrollbars.
You can subclass QTableView, use QSizePolicy::Fixed in the vertical direction and override sizeHint() to return your preferred vertical height.
Here's a working example (You didn't specify language, so I am going to assume it is Python :-) :
import sys
from PySide import QtCore, QtGui
class MyTableView(QtGui.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
#assume expanding in horizontal direction and fixed in vertica direction
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
def sizeHint(self):
return QtCore.QSize(400, 500) #I allow you to edit that!
class MyApplication(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtGui.QVBoxLayout()
table_view = MyTableView()
layout.addWidget(table_view)
self.model = QtGui.QStringListModel() #use a string list model for simplicity
table_view.setModel(self.model)
self.strings = ['1', '2', '3']
self.model.setStringList(self.strings) #initialize the model
self.counter = 4
button = QtGui.QPushButton('Add Cell') #this button updates the model and adds cells
button.clicked.connect(self.addCell)
layout.addWidget(button)
self.setLayout(layout)
def addCell(self):
self.strings.append(str(self.counter))
self.counter += 1
self.model.setStringList(self.strings)
app = QtGui.QApplication(sys.argv)
main = MyApplication()
main.show()
sys.exit(app.exec_())

QVBoxLayout: How to vertically align widgets to the top instead of the center

In Qt, When I add widgets to my layout, they are vertically centered by default. Is there a way to "List" the widgets from top to bottom instead of centering them vertically?
If you have a QVBoxLayout and want your fixed size widgets to be stacked at the top, you can simply append a vertical stretch at the end:
layout.addStretch()
If you have multiple stretchers or other stretch items, you can specify an integer stretch factor argument that defines their size ratio.
See also addStretch and addSpacerItem.
Add two layout.addStretch() before and after adding the widgets to center them vertically:
layout.addStretch()
layout.addWidget(self.message)
layout.addWidget(self.userid_field)
layout.addWidget(self.password_field)
layout.addWidget(self.loginButton)
layout.addStretch()
Not sure whether this answers your original question, but it is the answer to the one that I had when googling and being led to this page - so it might be useful for others too.
use void QLayout::setAlignment ( Qt::Alignment alignment ) method to set alignment according to your choice.
I find this a little more complicated than just using layout.setAlignment(). It kept not working for me until just now, when I figured out that if you have expanding widgets that you set a maximum height for, then that widget will not be aligned the way you want.
Here is example code that does not top align the QTextBrowser() widget even though I call layout.setAlignment(Qt.AlignTop). Sorry that it is in Python, but it is pretty easy to translate to C++ (I have gone the other way many times).
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWidget(QWidget):
"""
Create a widget that aligns its contents to the top.
"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
layout = QVBoxLayout()
label = QLabel('label:')
layout.addWidget(label)
info = QTextBrowser(self)
info.setMinimumHeight(100)
info.setMaximumHeight(200)
layout.addWidget(info)
# Uncomment the next line to get this to align top.
# layout.setAlignment(info, Qt.AlignTop)
# Create a progress bar layout.
button = QPushButton('Button 1')
layout.addWidget(button)
# This will align all the widgets to the top except
# for the QTextBrowser() since it has a maximum size set.
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
widget.resize(QSize(900, 400))
app.exec_()
The following explicitly calls layout.setAlignment(info, Qt.AlignTop) to get the expanding text widget to work.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWidget(QWidget):
"""
Create a widget that aligns its contents to the top.
"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
layout = QVBoxLayout()
label = QLabel('label:')
layout.addWidget(label)
info = QTextBrowser(self)
info.setMinimumHeight(100)
info.setMaximumHeight(200)
layout.addWidget(info)
# Uncomment the next line to get this to align top.
layout.setAlignment(info, Qt.AlignTop)
# Create a progress bar layout.
button = QPushButton('Button 1')
layout.addWidget(button)
# This will align all the widgets to the top except
# for the QTextBrowser() since it has a maximum size set.
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
widget.resize(QSize(900, 400))
app.exec_()
After comparison between the two solutions, it seems that :
myLayout.setAlignment(Qt::AlignTop)
works for several widget alignement but :
myLayout.setAlignment(myWidget, Qt::AlignTop)
works only for the first widget you add to the layout.
After all, the solution depends also to the QSizePolicy of yours widgets.
If you are using QT creator, you just add a "Vertical Spacers" at the bottom of your widget.
In pyQt (and PySide) we have Qt.AlignCenter (align on main direction), Qt.AlignHCenter (align on horizontal direction), Qt.AlignVCenter (align on vertical direction), use one of it when you need it.

Resources