(Last updated: September 19, 2017)
A QMessageBox dialog which looks too thin

A Google Search for "resize QMessageBox" will show that I wasn't alone in wanting to be able to resize a Qt5 QMessageBox. My main desire was to get the dialog to expand widthwise more than it would by default, as shown above. When setting "informative text" on the dialog, that text would end up getting wrapped very aggressively, which looks bad when there's file paths in there. See the above screenshot for an example of what I mean. A short little PyQt script to generate that dialog is here: qmessagebox.py

Or alternatively, here's the code snippet:

box = QtWidgets.QMessageBox(self)
box.setWindowTitle('Unable to load file')
box.setText('Unable to load file')
box.setInformativeText('[Errno 2] No such file or directory: foo/bar/baz/frotz/florb/nitfol')
box.setStandardButtons(QtWidgets.QMessageBox.Ok)
box.setDefaultButton(QtWidgets.QMessageBox.Ok)
box.setIcon(QtWidgets.QMessageBox.Critical)
return box.exec()

Now the QMessageBox implementation itself pretty thoroughly resists attempts to set its size manually, or allow window resizing, or anything along those lines. The usual QWidget size functions do nothing - setMinimumSizeHint, setMinimumWidth, resize, setSizePolicy, setFixedWidth - they're all useless. You can use setSizeGripEnabled to have the widget draw a resize handle, but the window isn't actually resizeable, so that's useless as well.

In terms of actual implementation inside QMessageBox itself, what's happening is that the widget will always get hard-resized to the width of the main text attribute, and informativeText will always get word-wrapped to that width whether you want it to or not.

Solution 1: Use setText() with newlines instead of setInformativeText()

In the end I came to believe that the actual best solution is to just not use informativeText, and instead set a regular text value which happens to include newlines. So instead of the code above, you'd end up with a single line like this:

box.setText("Unable to load file\n\n[Errno 2] No such file or directory: foo/bar/baz/frotz/florb/nitfol")

You do have to be a bit careful if you're potentially setting longer bits of text this way, because the dialog could theoretically end up much more wide than you want - even wider than your desktop in a worst-case scenario. What I ended up doing in my own code was to use Python's built-in textwrap module to do it. This little snippet ends up doing exactly what I'd wanted:

if infotext and infotext != '':
    text = "{}\n\n{}".format(message,
        "\n".join(textwrap.wrap(infotext, width=100)))
else:
    text = message
msgbox.setText(text)

Solution 2: Don't even use QMessageBox

Second-best, IMO, would be to eschew the use of QMessageBox entirely and write your own QDialog-derived class to display things how you want. This feels a little lame 'cause it seems like QMessageBox should be able to cope with all this natively, but writing a simple dialog to mimic its behavior but with more control over window sizing wouldn't be very difficult at all. I didn't bother actually doing this once I realized I could be using newlines in the text, as I've done above.

Solution 3: Playing games with the QMessageBox's internal QLayout

There's a few hits on Google suggesting this approach. QMessageBox has its own internal Layout which it uses, and you can make alterations to that, or its underlying objects. I wouldn't really recommend this myself, because the internal implementation of QMessageBox shouldn't really be relied on to stay stable from version to version. I'd suspect that it hasn't actually changed much in a long time, and it'd probably work out okay anyway, at least for awhile. Still not something to be relied on, though.

Regardless, I had taken a look into this option and come up with a few solutions here. The internal layout happens to be a QGridLayout, in a 3x3 configuration. The main "text" attribute is stored in the upper right cell (row 0, column 2), and the "informativeText" attribute is stored immediately below that (row 1, column 2). The last row is taken up by a couple of QDialogButtonBoxes.

One thing which can be done here is to add in a new fixed-width widget to the layout. You'd be able to do that with something like this:

layout = box.layout()
widget = QtWidgets.QWidget()
widget.setFixedSize(400, 1)
layout.addWidget(widget, 3, 0, 1, 3)

... so you'd have a new fixed-width widget occupying a new row at the bottom of the layout. Alternatively, you could dig into the layout container itself and manually set a width on the main text attribute, which would also make the dialog be wider:

layout = box.layout()
item = layout.itemAtPosition(0, 2)
widget = item.widget()
widget.setFixedWidth(400)

The question at that point, though is what value to use for the width, since you wouldn't want to have a dialog with a bunch of empty space hanging off the righthand side. That could actually be theoretically computed pretty easily using the QFontMetrics class - something like this should do the trick:

width = QtGui.QFontMetrics(QtGui.QFont()).boundingRect('informative text').width()

You'd probably need to make sure that you're using an approprate QFont object which represents the font being used on the dialog too, though. In the end, that's an awful lot of work against an internal structure which could go away at any point without warning. I think either of the first two solutions are much better.

Changelog

September 19, 2017
  • Initial Post