Qt: HBoxLayout - stop MainWindow from resizing to contents - qt

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.

Related

How to hide unexpected Python "splash" window that displays before my MainWindow in pyside6

Before my QMainWindow appears on screen, a smaller window titled "Python" opens up for about half a second.
I tried adding self.hide() at the top of the MainWindow init (just after super().init()), but that had no effect. I also tried covering it up with a QSplashScreen, but the window appears on top of the splash screen. Is there any way to prevent this unexpected "Python" window from appearing? Unexpected "Python" window screenshot
Here's my main file splashtest.py:
import sys
from modules.gui.main_window import *
def main():
app = QApplication(sys.argv)
#pixmap = QPixmap('./breadware-splash.png')
#splash = QSplashScreen(pixmap)
#splash.show()
window = Window()
#splash.finish(window)
app.exec()
if __name__ == "__main__":
main()
Here is main_window.py:
from PySide6.QtWidgets import QApplication, QMainWindow, QTabWidget, QSplashScreen
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt, QSize, QThread, QObject, Signal
from modules.gui.test_tab import *
class Window(QMainWindow):
def __init__(self):
parent = super().__init__()
self.resize(800, 600)
self.setWindowTitle('Breadware Test')
# Create a tab widget
self.tw = QTabWidget()
self.testtab = self.tw.addTab(TestTab(self), "Test Device")
self.tw.show()
self.setCentralWidget(self.tw)
self.show()
And here is test_tab.py:
from PySide6.QtWidgets import QWidget, QTextEdit, QGridLayout
class TestTab(QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
grid = QGridLayout()
self.infoarea = QTextEdit(self)
self.infoarea.setReadOnly(True)
self.infoarea.setStyleSheet("background-color: black; border: 0;")
grid.addWidget(self.infoarea, 5, 1, 1, 2)
self.setLayout(grid)
self.show()
self.infoarea.insertHtml("<span style=\"font-size:12pt; font-weight:800; color:white;\" >Hello There</span><br>")
tl;dr
Remove self.tw.show() from Window and self.show() in TestTab.
Explanation
As a rule of thumb, self.show() should never be called in the __init__() of a widget, especially for widgets that are going to be "reparented" (it becomes a child of another widget).
In your case, you are doing that mistake twice:
the first time, implicitly, in the __init__() of TestTab as written above;
then, again, in the __init__() of Window, before setting it as a central widget, which has the same result, because you are showing it before reparenting it;
Note: calling show() (just like setVisible(True)) more than once within the same function call does not change the result; with your current code, even using just one of the calls above would have created the issue, as you experience showed, based on your comment); such calls are not immediate, the create an extended queue of events that eventually result in what the user can see and interact with on the screen. If the widget is already visible, any further call is a no-op, unless another change in the widget status has happened in the meantime, such as changing the window flags.
The reason is simple: if a widget is created without a parent, it is always considered a top level one (a window). If a widget that has no parent calls self.show(), it will show itself as a separate window.
Your code would not have created the issue if:
you did not call self.tw.show() before self.setCentralWidget();
most importantly, you properly used the parent argument of __init__() by calling super().__init__(parent) (because you used TestTab(self));
Calling show() on a child widget will (obviously) not show it if the parent is not shown, it will only "mark" it as visible and make it as such as soon as the parent is shown. This is extremely important for child widgets created after the parent is being shown: in that case, calling show() is mandatory (unless the widget is added to the layout manager of the parent, see below).
This means that the only cases for which calling self.show() in the init is considerable acceptable is:
when you are completely sure that the widget will always be a top level window;
when the parent argument is properly used in the super call and the parent is actually valid:
class ProperShowCallWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
if parent is not None:
self.show()
The above will make the widget automatically visible when it's created with a parent, even if it is not going to be added to a layout manager.
Be aware that, actually, you should always use layouts managers (unless you really know what you are doing), so calling show() is pointless, as a child widget is implicitly shown when added to a widget layout, unless setVisible(False) (or hide()) is explicitly called.
So, not only you should remove those show() calls as written at the beginning of this post, but you should also remove self.show() in the __init__() of Window and move it into your "main" code:
def main():
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec()) # <- you should use sys.exit()
Do not underestimate the documentation
I strongly advise you to take your time to carefully study the related documentation for:
the visible property;
isHidden() (which is not the same as not isVisible());
isVisibleTo();
window() and isWindow();
the Qt.WindowFlags (notably, Qt.Window, automatically set for any widget created without a parent);
parentWidget(), QObject.parent() and the concepts behind Qt object trees and ownership;
the whole documentation about QWidget and the inherited QObject type, starting from their detailed description, going through all their functions, and then doing the same for all the related classes used by those functions;
Finally, consider that there are always exceptions. There are widgets that, even if created with a parent, are still shown as top level widgets. The most common case is QDialog (and all its subclasses, such as QMessageBox), which is always considered a top level window by default, even if it has a parent widget. That is because they are modal windows, and the parent widget is used as a reference to know to what window they are considered modal (blocking any input to those windows until the dialog is closed). Other similar cases are: menus, tooltips, floating tool bars/dock widgets and splash screens (see the Qt.WindowFlags above: they all use a window flag that uses the Qt.Window enum).

QGraphicsView / QGraphicsScene size matching

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!

Qt:How to create a Window that does not minimize and do not block background GUI

I have a QMainWindow that is a child to another window. When the user clicks anywhere in the parent window, I do not want the child window to be minimized. The child window should lose the focus and user should be able to continue working on the parent window.
This functionality is similar to the find/replace dialogs found in libreoffice/excel/openoffice etc as shown below. We can see that the task-bar shows only the parent application window and the child window is not visible in task-bar.
Are there any signals on QMainWindow that could help me achieve this? Or what would be the best way to do this?
If you open the new window as Dialog and give it a parent, it should stay on top of the parent. Since you're using QMainWindow, you can pass this with the constructor. If you decide to use QDialog, make sure you make it modeless with setModal(False). Otherwise it will block the parent.
A small example:
import sys
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
w = QtGui.QWidget()
layout = QtGui.QVBoxLayout(w)
self.button = QtGui.QPushButton('Open Dialog')
self.text = QtGui.QTextEdit()
layout.addWidget(self.button)
layout.addWidget(self.text)
self.setCentralWidget(w)
self.button.clicked.connect(self.openDialog)
def openDialog(self):
self.dialog = QtGui.QMainWindow(self, QtCore.Qt.Dialog)
self.dialog.show()
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())

Qt: Potential side-effects of Layout?

from PySide.QtGui import *
import sys
app = QApplication(sys.argv)
layout = QHBoxLayout()
print issubclass(type(layout), QWidget)
# Layout is not a kind of QWidget
window = QWidget()
window.resize(500, 500)
window.show()
window.setLayout(layout)
butt = QPushButton("asdf", parent = None)
butt.resize(100, 100)
butt.show()
layout.addWidget(butt)
print butt.parent()
app.exec_()
The parent object of butt is window in fact.
But I haven't set its parent to window explicitly.
Can I say that the Layout Object has some side effects which may
set the added widget's parent to the container that it applied to?
Yes.
Relevant part from the docs:
When you use a layout, you do not need to pass a parent when
constructing the child widgets. The layout will automatically reparent
the widgets (using QWidget::setParent()) so that they are children of
the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.

Why won't my dialog show properly when I set its parent on instantiation?

In the attached example script, why won't the MyDialog instance show properly when I set MyDialog's parent to self on line 20 instead of leaving it blank? First I thought the shortcut had stopped working somehow, but obviously that's not the case.
In this case it doesn't really make any difference whether the parent is set, but in my real case I need the parent to be set.
Am I missing something obvious here?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyDialog(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setFocusPolicy(Qt.StrongFocus)
label = QLabel(self)
label.setText("World")
hbox = QHBoxLayout()
hbox.addWidget(label)
self.setLayout(hbox)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.my_dialog = MyDialog()
#self.my_dialog = MyDialog(self)
label = QLabel(self)
label.setText("Hello")
self.setCentralWidget(label)
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self, self.show_my_dialog)
shortcut.setContext(Qt.ApplicationShortcut)
self.show()
def show_my_dialog(self):
md = self.my_dialog
if md.isVisible():
md.hide()
print 'hide'
else:
md.show()
print 'show'
def main():
app = QApplication([])
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
On my machine (Kubuntu 11.10) it's working.
If Dialog's parent is None - it opens another frameless window, and i have two entries in taskbar. If i close the main window, dialog remains.
If you are using Windows - the behavior you described might be related to it. Maybe the window is shown, it's just behind other windows?
If Dialog's parent is the main window - the frameless dialog is shown inside the main window - in the top left corner near the label.
You might be interested in this info:
A dialog window is a top-level window mostly used for short-term tasks
and brief communications with the user. QDialogs may be modal or
modeless. QDialogs can provide a return value, and they can have
default buttons. QDialogs can also have a QSizeGrip in their
lower-right corner, using setSizeGripEnabled().
Note that QDialog (an
any other widget that has type Qt::Dialog) uses the parent widget
slightly differently from other classes in Qt. A dialog is always a
top-level widget, but if it has a parent, its default location is
centered on top of the parent's top-level widget (if it is not
top-level itself). It will also share the parent's taskbar entry.
Use
the overload of the QWidget::setParent() function to change the
ownership of a QDialog widget. This function allows you to explicitly
set the window flags of the reparented widget; using the overloaded
function will clear the window flags specifying the window-system
properties for the widget (in particular it will reset the Qt::Dialog
flag).

Resources