Consider the following example,
import sys
from PyQt5 import QtCore, QtWidgets
class scrolldemo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(scrolldemo, self).__init__(parent)
widget = QtWidgets.QWidget()
widget.setFixedSize(QtCore.QSize(500, 600))
scroll = QtWidgets.QScrollArea()
scroll.setWidget(widget)
self.setCentralWidget(scroll)
self.setWindowTitle("Scroll demo")
def main():
app = QtWidgets.QApplication(sys.argv)
ex = scrolldemo()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The widget has a size that is slightly too large vertically, which adds a vertical scroll bar. However, the vertical scroll bar takes up space that was taken up by the widget itself. This requires a horizontal scroll bar too, making the window look like this:
I'm okay with the vertical scroll bar, but horizontal scroll bars are ungainly. Observe that the extra space needed for the horizontal scroll bar is exactly the width of the vertical bar.
This can be worked around by overriding the sizeHint method to include the vertical scroll bar's width,
class ScrollAreaWithVerticalBar(QtWidgets.QScrollArea):
def sizeHint(self):
hint = super().sizeHint()
bar_width = self.verticalScrollBar().sizeHint().width()
return QtCore.QSize(hint.width() + bar_width, hint.height())
This produces a slightly wider QScrollArea that no longer needs the ungainly horizontal scroll bar. I got this idea by looking at a closed Qt bug report related to this problem. However, that bug report was closed as "out of scope", which makes me think that this isn't a bug at all and I'm using QScrollArea incorrectly, and my incorrect use also is requiring a workaround.
So what should I be doing instead?
Related
I am attempting to do a HTML style grid layout in Qt using this example here: https://gist.github.com/Cysu/7461066. It nearly does what I want, however when I put it into a scroll area it seems to collapse down to a minimum size and not want to expand to the full size of the scroll area. I did see that in the documentation you need to set the sizeConstraint in the layout to SetMinAndMaxSize, however this did not seem to fix it.
How can I get the layout to occupy the maximum size inside of the scroll area?
Here's a basic example, importing the class from the link above:
class App(QtWidgets.QDialog):
def __init__():
super().__init__()
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
grid = QtWidgets.QWidget()
grid_layout = FlowLayout()
grid_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
grid.setLayout(grid_layout)
scroll_area = QtWidgets.QScrollArea()
scroll_area.setWidget(grid)
layout.addWidget(scroll_area)
# layout.addWidget(grid) # adding the widget the the parent layout works just fine...
for i in range(25):
grid_layout.addWidget(QtWidgets.QLabel(f"Label {i}"))
I have a line editor that inherits from QTextEdit, and I am using it to edit view items that show rich text. The second parameter for QTextEdit.setAlignment is `QtAligntment' and the docs say:
Valid alignments are Qt.AlignLeft, Qt.AlignRight, Qt.AlignJustify and
Qt.AlignCenter (which centers horizontally).
That is, there is no native support for vertical alignment. Is there an indirect way to vertically center the text in QTextEdit?
Related link
Center the Text of QTextEdit horizontally and vertically : Unfortunately, the accepted answer uses QLineEdit which won't work for me.
A clue?
At the following I found a clue about how to do it in C++/Qt. I am almost able to follow it, but not quite, as it is for c++:
http://www.qtcentre.org/threads/26003-Vertical-centering-of-a-QTextEdit
I will hack at it on my own for a couple of days and try to answer it myself, but wanted to post this now in case someone has already cracked it already or done it in a different/better way.
For a single-line edit centred vertically, you just need to calculate a correct fixed height.
Using the example delegate from your previous question, it can be achieved like this:
class RichTextLineEdit(QtGui.QTextEdit):
def __init__(self, parent=None):
...
margin = 1
self.document().setDocumentMargin(margin)
fontMetrics = QtGui.QFontMetrics(self.font())
height = fontMetrics.height() + (margin + self.frameWidth()) * 2
self.setFixedHeight(height)
(NB: the reimplemented sizeHint and minimumSizeHint methods are probably redundant in the original example).
While the accepted answer works for default font size, it breaks when I change the font size or vertical margins (see comments). The text line edit class below centers the text vertically, for all font sizes and vertical margins that I've tested.
It sets up the editor using QTextDocument which is then assigned to the QTextEdit instance. QTextDocuments provide the back-end containers for QTextEdits anyway, and have built-in functionality for handling font sizes and margins, and give an additional layer of control over the editor.
In practice, I found using QTextDocument let me solve the problem in a more intuitive way without having, you don't have to delve into the nitty-gritty mechanics of frame widths, font metrics, and all that, which we did when working solely using native QTextEdit methods.
Note it uses setViewportMargins() instead of setContentMargins() (which is what you might expect it to use) because the latter is for setting margins for something that is inserted into a layout. The following editor is a standalone widget, not put into any layout, so setContentMargins() won't do anything.
import sys
from PySide import QtGui, QtCore
class TextLineEdit(QtGui.QTextEdit):
topMarginCorrection = -4 #not sure why needed
returnPressed = QtCore.Signal()
def __init__(self, fontSize = 10, verticalMargin = 2, parent = None):
QtGui.QTextEdit.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFontPointSize(fontSize)
self.setViewportMargins(-verticalMargin, self.topMarginCorrection , 0, 0) #left, top, right, bottom
#Set up document with appropriate margins and font
document = QtGui.QTextDocument()
currentFont = self.currentFont()
currentFont.setPointSize(fontSize)
document.setDefaultFont(currentFont)
document.setDocumentMargin(verticalMargin)
self.setFixedHeight(document.size().height())
self.setDocument(document)
def keyPressEvent(self, event):
'''stops retun from returning newline'''
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.returnPressed.emit()
event.accept()
else:
QtGui.QTextEdit.keyPressEvent(self, event)
def main():
app = QtGui.QApplication(sys.argv)
myLine = TextLineEdit(fontSize = 15, verticalMargin = 8)
myLine.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
It seems most people are asking how to make their QMainWindow resize to its contents - I have the opposite problem, my MainWindow does resize and I don't know why.
When I set my QLabel to a longer text, my mainwindow suddenly gets bigger, and I can't find out why that happens.
The following example code basically contains:
A QMainWindow
A QWidget as central widget
A QVBoxLayout as a child of that
A LabelBar inside that.
The LabelBar is a QWidget which in turn contains:
A QHBoxLayout
Two QLabels in that.
After a second, a QTimer sets the label to a longer text to demonstrate the issue.
PyQt example code:
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QLabel, QWidget,
QMainWindow, QVBoxLayout, QSizePolicy)
from PyQt5.QtCore import QTimer
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
cwidget = QWidget(self)
self.setCentralWidget(cwidget)
self.resize(100, 100)
vbox = QVBoxLayout(cwidget)
vbox.addWidget(QWidget())
self.bar = LabelBar(self)
vbox.addWidget(self.bar)
timer = QTimer(self)
timer.timeout.connect(lambda: self.bar.lbl2.setText("a" * 100))
timer.start(1000)
class LabelBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
hbox = QHBoxLayout(self)
self.lbl1 = QLabel(text="eggs")
hbox.addWidget(self.lbl1)
self.lbl2 = QLabel(text="spam")
hbox.addWidget(self.lbl2)
if __name__ == '__main__':
app = QApplication([])
main = MainWindow()
main.show()
app.exec_()
Main window grows because it's the goal of using layouts. Layouts make size requirements for their widgets to ensure that all content is displayed correctly. Requirements depend on child widgets. For example, QLabel by default grows horizontally and require space to fit its content. There are many ways to prevent window growing, and the resulting behavior varies:
You can put QLabel in a QScrollArea. When label's text is too long, scrollbars will appear.
You can enable word wrap using self.lbl2.setWordWrap(True). As long as you set text with some spaces, QLabel will display it in several lines, and window will grow vertically a bit instead of growing horizontally.
You can ignore QLabel's size hint using self.lbl2.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed). QLabel's content will not affect its layout or parent widget size. Too large text will be truncated.
How do you make a QGraphicsScene with a specified size and a QGraphicsView to monitor that scene with the same size?
This sounds like a stupid question but consider the following test code [1]
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.scene = QtGui.QGraphicsScene(0, 0, 200, 200, self)
self.view = QtGui.QGraphicsView(self.scene, self)
#self.view.setMaximumSize(200, 200)
self.setCentralWidget(self.view)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
When I run this I get a little window with the QGraphicsView and no scroll bars. If I shrink the window scroll bars appear because I've made the view smaller than the specified size of the scene. If I enlarge the window the bars go away and the view resizes with the window. All of this makes sense.
Now I run the same code again but with the commented line (preceded by #) un-commented. The Window appears with the view inside, but the view has scroll bars. When I shrink the window the view also shrinks, but when I enlarge the window the view only enlarges to a certain size. This is not surprising. What is surprising is that with the view at it's maximum size, the scroll bars are still there. I don't understand this because the maximum size of the view is explicitly matched to the size of the scene in the code.
Why does this happen?
I am aware that other questions have explained how to force the scene and view to fit together but I want to understand what size view and scene I have actually made, hence my attempt to use explicitly specified sizes.
[1] I'm using PyQt, C++ users just read "self" as "this" and "import" as "#include"
EDIT: The accepted answer below is absolutely correct. I would just like to add for others who read this that if your view is in a layout you have to account for the layout margins as well. These can be explicitly set in the QtDesigner etc.
Ok, I worked it out for you!
The QGraphicsView (which subclasses QFrame) has a border. If you add the below line to your init method:
self.view.setFrameShape(QtGui.QFrame.NoFrame)
then you remove the 1px frame and it works as you expect!
I have a QScrollArea Widget, which starts empty;
It has a vertical layout, with a QGridLayout, and a vertical spacer to keep it at the top, and prevent it from stretching over the whole scroll area;
Elsewhere in the program, there is a QTextEdit, which when changed, has its contents scanned for "species" elements, and then they are added to the QGridLayout. Any species elements which have been removed are removed too. This bit works;
I have turned the vertical scrollbar on all the time, so that when it appears it does not sit on top of the other stuff in there. Note that the scroll bar is larger than the scroll box already though, despite not needing to be.
This is the problem. The scroll area seems to be preset, and i cannot change it. If i add more rows to the QGridLayout, the scroll area doesn't increase in size.
Instead, it stays the same size, and squeezes the QGridLayout, making it look ugly (at first);
And then after adding even more it becomes unusable;
Note that again, the scroll bar is still the same size as in previous images. The first two images are from Qt Designer, the subsequent 3 are from the program running.
If I resize the window so that the QScrollArea grows, then I see this:
Indicating that there's some layout inside the scroll area that is not resizing properly.
My question is; what do I need to do to make the scrollable area of the widget resize dynamically as I add and remove from the QGridLayout?
If you're coming here from Google and not having luck with the accepted answer, that's because you're missing the other secret invocation: QScrollArea::setWidget. You must create and explicitly identify a single widget which is to be scrolled. It's not enough to just add the item as a child! Adding multiple items directly to the ScrollArea will also not work.
This script demonstrates a simple working example of QScrollArea:
from PySide.QtGui import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
inner.layout().addWidget(b)
scroll.show()
app.exec_()
The documentation provide an answer :
widgetResizable : bool
This property holds whether the scroll area should resize the view widget.
If this property is set to false (the default), the scroll area honors the size of its widget.
Set it to true.
Why don't you use a QListView for your rows, it will manage all the issues for you? Just make sure that after you add it you click on the Class (top right window of designer) and assign a layout or it wont expand properly.
I use a QLIstWidget inside a QScrollArea to make a scrollable image list
Try this for adding other objects to the list, this is how I add an image to the list.
QImage& qim = myclass.getQTImage();
QImage iconImage = copyImageToSquareRegion(qim, ui->display_image->palette().color(QWidget::backgroundRole()));
QListWidgetItem* pItem = new QListWidgetItem(QIcon(QPixmap::fromImage(iconImage)), NULL);
pItem->setData(Qt::UserRole, "thumb" + QString::number(ui->ImageThumbList->count())); // probably not necessary for you
QString strTooltip = "a tooltip"
pItem->setToolTip(strTooltip);
ui->ImageThumbList->addItem(pItem);
Update on Artfunkel's answer:
Here's a PySide6 demo that uses a "Populate" button to run the for loop adding items to the scroll area. Each button will also delete itself when clicked.
from PySide6.QtWidgets import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
def on_remove_widget(button):
button.deleteLater()
def populate():
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
b.clicked.connect(b.deleteLater)
inner.layout().addWidget(b)
b = QPushButton(inner)
b.setText("Populate")
b.clicked.connect(populate)
inner.layout().addWidget(b)
scroll.show()
app.exec()