5

I'm using the nice feature in QMessageBox to optionally show detailed text to the user. However, the window after expansion is still fairly small, and one immediately tries to resize the window so more of the details are visible. Even after setting what I think are the proper settings it won't allow resizing.

Here's the relevant snippet of PyQt4 code:

mb = QMessageBox()
mb.setText("Results written to '%s'" % filename)
mb.setDetailedText(str(myData))
mb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
mb.setSizeGripEnabled(True)

Am I missing a step and/or is this at all possible?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
metasim
  • 4,793
  • 3
  • 46
  • 70
  • Do you mean that when the QMessageBox is displayed, it's not large enough to display your content (e.g. things are getting cut off)? – Brian Roach Apr 16 '10 at 18:46
  • Sorry I wasn't clear. I meant that the detailed text is confined to a *small* text area (with scrollbars). Viewable, but not pleasant from a user perspective. If the `QMessageBox` could just be resized manually as the user desires, it would improve the overall experience greatly. Even with the calls to `setSizePolicy()` and `setSizeGripEnabled()` it is still not possible to resize the window. – metasim Apr 16 '10 at 19:11
  • 1
    Did you try the MinimumExpanding size Policy? Beyond that, check to see what maximumSize is being set to. You may need to subclass QMessageBox and re-implement sizeHint() to return what you want. I've always found there's a bit of voodoo involved getting top-level widgets to re-size themselves appropriately based on their contents. The other thing you could try is setting the layout's size constraint: `mb.layout()->setSizeConstraint(QLayout::SetMaximumSize)` (Ah, that's c++ - not familiar with the python syntax) – Brian Roach Apr 16 '10 at 20:08

5 Answers5

6

This is the solution I would use. This doesn't make the dialog resizable, but it does make the dialog change itself to a sensible size when the details box is visible. I have unashamedly stolen some ideas from serge_gubenko's answer. Even if you'd rather implement his resizing I humbly offer some other improvements below.

# Safe since everything in the namespace begins with 'Q'
from PyQt4.QtGui import *

class MyMessageBox(QMessageBox):

    # This is a much better way to extend __init__
    def __init__(self, *args, **kwargs):            
        super(MyMessageBox, self).__init__(*args, **kwargs)
        # Anything else you want goes below

    # We only need to extend resizeEvent, not every event.
    def resizeEvent(self, event):

        result = super(MyMessageBox, self).resizeEvent(event)

        details_box = self.findChild(QTextEdit)
        # 'is not' is better style than '!=' for None
        if details_box is not None:
            details_box.setFixedSize(details_box.sizeHint())

        return result
Paul Etherton
  • 391
  • 3
  • 11
  • 1
    This does not work at all on linux. But even if it did work, it wouldn't answer the question, because it doesn't make the message-box resizable. (PS: the overriding of `__init__` is totally redundant in this example). – ekhumoro Nov 04 '17 at 19:07
5

if you're looking to make a resizable message box, pls, check if code below would work for you:

class MyMessageBox(QtGui.QMessageBox):
    def __init__(self):
        QtGui.QMessageBox.__init__(self)
        self.setSizeGripEnabled(True)

    def event(self, e):
        result = QtGui.QMessageBox.event(self, e)

        self.setMinimumHeight(0)
        self.setMaximumHeight(16777215)
        self.setMinimumWidth(0)
        self.setMaximumWidth(16777215)
        self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)

        textEdit = self.findChild(QtGui.QTextEdit)
        if textEdit != None :
            textEdit.setMinimumHeight(0)
            textEdit.setMaximumHeight(16777215)
            textEdit.setMinimumWidth(0)
            textEdit.setMaximumWidth(16777215)
            textEdit.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)

        return result

here's how messagebox is called:

mb = MyMessageBox()
mb.setText("Results written to '%s'" % 'some_file_name')
mb.setDetailedText('some text')
mb.exec_()

solution is taken from here

hope this helps, regards

serge_gubenko
  • 20,186
  • 2
  • 61
  • 64
  • This is definitely on the right track (shame it has to be done in the event handler). The dialog does resize now, but all the resizing goes to the label at the top, and not the text box. I spent about half an hour playing with setting the `sizePolicy` of the other components and the `rowStretch` of the underlying layout, but didn't get any further. It's definitely better than what I had. I'll post a followup if I figure out how to give all the extra vertical space to the text area. – metasim Apr 19 '10 at 14:04
  • @serge_gubenko when I press the maximize button on the dialog (this is shown when the 'Show Details' button is pressed), the dialog maximizes for an instant and then immediately comes back to normal (and moves to the top left corner of the screen). Any idea how can the maximize button working be fixed? – Pushpak Dagade Jul 10 '13 at 09:02
  • This seems to be the best solution so far. However on every click of the "Show Details"/"Hide Details" button it resizes to the original small size. It seems that there is a `updateSize()` method inside `QMessageBox` that is computing the size, but overwriting it doesn't make any difference, at least in PyQt. – Jason Mar 05 '19 at 14:02
  • I tried print out all event types while I drag and resize with the grip. It works fine by commenting a few events and in the end I had just one left: QEvent::CursorChange to handle. – ZHANG Zikai May 03 '21 at 08:45
  • The above code has lots of issues, most importantly: 1. the size functions are unnecessarily called for **any possible event** (a widget like this would receive more than a hundred of events alone just when it's shown); 2. setting the size policy on a top level window is useless; 3. setting the minimum size to 0 is just *wrong*. I strongly discourage to use it. – musicamante Jun 02 '22 at 14:41
2

Due to its nature, a QMessageBox should never allow resizing. If you need that, you should probably use a QDialog instead.

That said, the fixed size restraint is caused by the internal layout, which is also replaced any time relevant aspects of the dialog change (buttons, [un]setting detailed text, etc.).

Since the layout management is completely internal, and it always resets the fixed size of the dialog, the solution is to watch for relevant events, specifically LayoutRequest (which is called whenever the layout needs to be redone) and Resize and override the maximum size.

With a subclass, the solution is pretty simple:

class ResizableMessageBox(QtWidgets.QMessageBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setSizeGripEnabled(True)

    def event(self, event):
        if event.type() in (event.LayoutRequest, event.Resize):
            if event.type() == event.Resize:
                res = super().event(event)
            else:
                res = False
            details = self.findChild(QtWidgets.QTextEdit)
            if details:
                details.setMaximumSize(16777215, 16777215)
            self.setMaximumSize(16777215, 16777215)
            return res
        return super().event(event)

Alternatively, with an event filter:

class Something(QWidget):
    # ...
    def showMessageBox(self):
        mb = QtWidgets.QMessageBox()
        mb.setSizeGripEnabled(True)
        mb.setWindowTitle('Hello')
        mb.setText('I am a message box')
        mb.setDetailedText('very long text ' * 10)
        mb.setProperty('resizable', True)
        mb.installEventFilter(self)
        mb.exec()

    def eventFilter(self, obj, event):
        if (isinstance(obj, QtWidgets.QMessageBox) 
            and obj.property('resizable')
            and event.type() in (event.LayoutRequest, event.Resize)):
                if event.type() == event.Resize:
                    obj.event(event)
                details = obj.findChild(QtWidgets.QTextEdit)
                if details:
                    details.setMaximumSize(16777215, 16777215)
                obj.setMaximumSize(16777215, 16777215)
                return True
        return super().eventFilter(obj, event)

Note: none of the above will work with the static methods of QMessageBox, since they create a private instance.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Whilst this is a nice refinement, it doesn't quite deal with the OP's original issue, since the detailed text-box won't resize properly (i.e. it defaults to a fixed height). – ekhumoro Jun 02 '22 at 19:14
  • @ekhumoro Uhm, I admit that I tested it on Linux only, but when I resize the message box, the detailed text is also resized. Maybe not properly (I could add a fix for the layout stretch, though), but it does resize according to the message box size. I tested it both with opaque resize and without. – musicamante Jun 02 '22 at 22:04
  • 1
    I tested both your solutions on arch linux (with openbox wm), and they do not resize the detailed text-box vertically. What specific platform are you on? Two of the other solutions given here require modifications to the text-box, which surely cannot be coincidental. For me, the default max-size of the text-box is `QSize(16777215, 100)`, and neither of your solutions affect that. As a minimum, resetting the max-height of the text-box would seem to be necessary on most systems. – ekhumoro Jun 03 '22 at 11:01
  • I'm sorry, I must have been extremely tired to not see *that*. I checked it again, and added a couple of modifications that should fix all aspects. Thank you. – musicamante Jun 03 '22 at 14:55
0

This works, but only tested on Linux under Gnome 2. It resizes horizontally only unless the "show details" text is turned on, in which case it resizes in both directions. The "show details" button still resets it to the initial size, this is either a feature or a bug depending on your pov:

bool MyMessageBox::event(QEvent* e)
{
    bool result = QMessageBox::event(e);
    // force resizing back on, QMessageBox keeps turning it off:
    if (maximumWidth() != QWIDGETSIZE_MAX) {
        QTextEdit *textEdit = findChild<QTextEdit*>();
        if (textEdit && textEdit->isVisible()) {
            textEdit->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
            textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
            setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
            setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        } else {
            setMaximumWidth(QWIDGETSIZE_MAX);
            setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
        }
    }
    return result;
}
user3080602
  • 307
  • 3
  • 4
0

Some time passed but issue is still valid.

I made following implementation, which worked well on OSX and PySide6, but not tested on other systems.

class MyMessageBox(QMessageBox):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._width = 150   # default

        self.setSizeGripEnabled(True)

    def setWidth(self, width):
        self._width = width

    def resizeEvent(self, event):
        _result = super().resizeEvent(event)

        self.setFixedWidth(self._width)

        _text_box = self.findChild(QTextEdit)
        if _text_box is not None:
            # get width
            _width = int(self._width - 50)  # - 50 for border area
            # get box height depending on content
            _font = _text_box.document().defaultFont()
            _fontMetrics = QFontMetrics(_font)
            _textSize = _fontMetrics.size(0, details_box.toPlainText(), 0)
            _height = int(_textSize.height()) + 30  # Need to tweak
            # set size
            _text_box.setFixedSize(_width, _height)

        return _result

Call is done with:

    _msg_box = MyMessageBox(parent=self)
    _msg_box.setIcon(icon)
    _msg_box.setText(title_text)
    _msg_box.setInformativeText(message)
    _msg_box.setDetailedText(detailed_text)

    # define a reference width and apply it
    _width = int(self.geometry().width() / 2.5)
    _msg_box.setWidth(_width)

    _msg_box.exec()

With this, the default width is set to 150, but might be change with the setWidth method. The height is set automatically. In case a detailed Text is provided, the width of the text box is a little smaller than the total width of the message box itself (- 50) and the height is depending on the content.

Note I have not used it with larger content in the text box.