metasim
metasim

Reputation: 4960

How to allow resizing of QMessageBox in PyQt4

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?

Upvotes: 6

Views: 16231

Answers (5)

musicamante
musicamante

Reputation: 48489

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.

Upvotes: 3

Wolfgang Studer
Wolfgang Studer

Reputation: 11

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.

Upvotes: 0

user3080602
user3080602

Reputation: 307

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;
}

Upvotes: 0

Paul Etherton
Paul Etherton

Reputation: 411

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

Upvotes: 7

serge_gubenko
serge_gubenko

Reputation: 20492

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

Upvotes: 6

Related Questions