如何在PyQt4中允许QMessageBox调整大小

5
我正在使用QMessageBox中的一个很好的功能,可以选择向用户显示详细文本。但是,在展开后窗口仍然相当小,立即尝试调整窗口大小以便更多的细节可见。即使设置了我认为是正确的设置,它也不允许调整大小。
以下是PyQt4代码的相关片段:
mb = QMessageBox()
mb.setText("Results written to '%s'" % filename)
mb.setDetailedText(str(myData))
mb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
mb.setSizeGripEnabled(True)

我有一个问题,这个步骤是否有遗漏或者说这是否可能实现?

你的意思是当QMessageBox显示时,它不够大以显示你的内容(例如,某些内容被截断了)? - Brian Roach
抱歉我表述不够清晰。我的意思是详细文本只能在一个小型的文本区域(带滚动条)中显示。虽然可见,但从用户的角度来看并不舒适。如果QMessageBox可以根据用户的需求手动调整大小,整体体验会大为改善。即使调用了setSizePolicy()setSizeGripEnabled()方法,仍然无法调整窗口大小。 - metasim
1
你尝试过使用MinimumExpanding大小策略吗?除此之外,检查一下设置的maximumSize是多少。你可能需要子类化QMessageBox并重新实现sizeHint()以返回所需大小。我发现让顶层窗口根据其内容适当地调整大小有点神秘。你可以尝试的另一件事是设置布局的大小约束:mb.layout()->setSizeConstraint(QLayout::SetMaximumSize)(啊,这是C++ - 不熟悉Python语法)。 - Brian Roach
5个回答

6
这是我会使用的解决方案。虽然它不能使对话框可调整大小,但它可以在细节框可见时自动调整对话框大小以适应内容。我毫不羞耻地从serge_gubenko的答案中借鉴了一些思路。即使您更喜欢实现他的调整大小功能,我也谦卑地提供其他改进建议。
# 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

1
这在Linux上根本不起作用。但即使它能工作,它也无法回答问题,因为它不能使消息框可调整大小。(附注:在此示例中覆盖__init__是完全多余的)。 - ekhumoro

5
如果您想要制作一个可调整大小的消息框,请检查下面的代码是否适用于您:
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

以下是如何调用消息框:

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

此解决方案取自这里

希望这可以帮到您,祝好!


这绝对是正确的方向(可惜必须在事件处理程序中完成)。现在对话框可以调整大小,但所有的调整都会应用到顶部的标签而不是文本框。我花了大约半个小时来尝试设置其他组件的sizePolicy和底层布局的rowStretch,但没有进展。这肯定比我之前的做法要好。如果我找到如何将所有额外的垂直空间分配给文本区域,我会发布后续内容。 - metasim
@serge_gubenko 当我按下对话框上的最大化按钮(当按下“显示详细信息”按钮时会显示该按钮),对话框会瞬间最大化,然后立即恢复正常(并移动到屏幕左上角)。有什么想法可以修复最大化按钮的工作吗? - Pushpak Dagade
这似乎是目前最好的解决方案。但是,在每次单击“显示详细信息”/“隐藏详细信息”按钮时,它都会调整为原始的小尺寸。似乎在QMessageBox内部有一个updateSize()方法来计算大小,但覆盖它并没有任何区别,至少在PyQt中是如此。 - Jason
我尝试在拖动和调整大小时打印出所有事件类型。通过注释掉一些事件,它可以正常工作,最终只剩下一个:QEvent :: CursorChange需要处理。 - ZHANG Zikai
以上代码存在许多问题,最重要的是:1. 大小函数被不必要地调用了任何可能的事件(像这样的小部件仅在显示时就会接收到超过一百个事件);2. 在顶层窗口上设置大小策略是无用的;3. 将最小大小设置为0是错误的。我强烈反对使用它。 - musicamante

2
由于其性质,QMessageBox 不应该允许调整大小。如果需要调整大小,应该使用 QDialog。
话虽如此,固定大小的限制是由内部布局引起的,该布局在对话框的相关方面发生更改时(按钮、[取消]设置详细文本等)也会被替换。
由于布局管理完全是内部的,并且它总是重置对话框的固定大小,解决方法是监视相关事件,特别是 LayoutRequest(每当需要重新布局时调用)和 Resize,并覆盖最大大小。
使用子类的解决方案非常简单。
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)

或者,使用事件过滤器:

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)

注意:上述方法都不能与QMessageBox的静态方法一起使用,因为它们创建了一个私有实例。

虽然这是一个不错的改进,但它并没有完全解决原始问题,因为详细文本框无法正确调整大小(即默认为固定高度)。 - ekhumoro
@ekhumoro 嗯,我承认我只在Linux上测试过它,但是当我调整消息框的大小时,详细文本也会被调整大小。也许不是很好(虽然我可以添加一个布局拉伸的修复),但它确实根据消息框的大小进行调整。我已经测试了有和没有不透明的调整大小。 - musicamante
1
我在arch linux(使用openbox wm)上测试了你提供的两个解决方案,它们都不能垂直调整详细文本框的大小。你使用的是哪个具体平台?这里提供的其他两个解决方案需要修改文本框,这肯定不是巧合。对于我来说,文本框的默认最大尺寸为QSize(16777215, 100),而你提供的两个解决方案都没有影响到它。至少,在大多数系统上,重置文本框的最大高度似乎是必要的。 - ekhumoro
对不起,我可能太累了,没看到那个。我再次检查了一遍,并添加了一些修改,应该可以解决所有问题。谢谢。 - musicamante

0

这个程序可以运行,但只在Gnome 2下的Linux系统上进行了测试。它只能水平调整大小,除非打开“显示细节”文本,此时它可以在两个方向上调整大小。点击“显示细节”按钮仍会将其重置为初始大小,这取决于您的观点,可能是一个功能或者是一个错误:

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

0

一些时间过去了,但问题仍然存在。

我做了以下实现,在OSX和PySide6上运行良好,但未在其他系统上进行测试。

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

调用的方式为:

    _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()

通过这个方法,文本框的默认宽度设置为150,但可以使用setWidth方法进行更改。高度将自动设置。

如果提供了详细的文本内容,则文本框的宽度略小于消息框本身的总宽度(-50),高度取决于内容。

请注意,我尚未将其用于文本框中的大型内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接