如何在 Qt 中保持小部件的纵横比?

Posted

技术标签:

【中文标题】如何在 Qt 中保持小部件的纵横比?【英文标题】:How to maintain widgets aspect ratio in Qt? 【发布时间】:2010-10-01 22:15:06 【问题描述】:

如何在 Qt 中保持小部件的纵横比以及如何使小部件居中?

【问题讨论】:

【参考方案1】:

您不必实现自己的布局管理器。你可以继承QWidget并重新实现

int QWidget::heightForWidth( int w )  return w; 

保持方正。但是,heightForWidth() 不适用于 X11 的顶层窗口,因为显然 X11 协议不支持它。至于居中,可以将Qt::AlignCenter作为QBoxLayout::addWidget()的第三个参数或者QGridLayout::addWidget()的第五个参数传递。

注意:至少在较新版本的 Qt 中,QWidget 不再具有 heightForWidthwidthForHeight(因此它们不能被覆盖),因此 setWidthForHeight(true)setHeightForWidth(true) only have an effect for descendants of QGraphicsLayout.

【讨论】:

是的。您需要设置sizePolicyhasHeightForWidth() == true,并且布局通常会为小部件提供它所要求的更多宽度或高度,但这是试图满足约束的布局。 QLayoutItem 解决方案将获得相同的效果,因为它们是等效的(参见QWidgetLayoutItem)。 @MarcMutz-mmutz:当然 X11 确实支持这一点:查看 XSizeHints tronche.com/gui/x/xlib/ICC/client-to-window-manager/…,它允许将窗口的纵横比告知窗口管理器。 如果我想设置 widthForHeight 怎么办? QWidget 没有widthForHeight 方法。 那么如何在新的 Qt 版本中做到这一点? 嗯...至于 Qt 5.7 doc.qt.io/qt-5/qwidget.html#hasHeightForWidth【参考方案2】:

正确的答案是创建您的自定义布局管理器。这可以通过继承QLayout 来实现。

子类化QLayout时要实现的方法

void addItem(QLayoutItem* item); 将项目添加到布局。 int count() const; 返回项目计数。 QLayoutItem* itemAt(int index) const; 返回索引处的项目引用,如果没有则返回 0。 QLayoutItem* takeAt(int index); 从索引中获取并返回布局中的项目,如果没有则返回 0。 Qt::Orientations expandingDirections() const; 返回布局扩展方向。 bool hasHeightForWidth() const; 判断布局是否处理宽度计算的高度。 QSize minimumSize() const; 返回布局的最小尺寸。 void setGeometry(const QRect& rect); 设置布局的几何形状和其中的项目。在这里,您必须保持纵横比并进行居中。 QSize sizeHint() const; 返回布局的首选大小。

进一步阅读

Maintaining square form for a widget in Qt @ Forum Nokia Implementing a layout manager in Qt @ Forum Nokia Writing custom layout managers @ Qt documentation

【讨论】:

你说的是子类化QLayout,但是你引用的函数来自QLayoutItem,它只是调用了来自QWidget的对应函数。那么继承QLayout 可以给你什么QWidget 本身不能? 还有,所有链接都失效了。【参考方案3】:

resizeEvent() 内部调用resize() 对我来说从来没有奏效过——充其量它会在窗口大小调整两次时导致闪烁(就像你一样),最坏的情况是无限循环。

我认为保持固定纵横比的“正确”方法是创建自定义布局。您只需要重写两个方法,QLayoutItem::hasHeightForWidth()QLayoutItem::heightForWidth()

【讨论】:

这似乎是正确的答案,但我必须在工作时研究它,并在完成后给出更详细的答案。【参考方案4】:

我也试图实现所要求的效果:一个保持固定纵横比的小部件,同时保持在其分配空间的中心。起初我尝试了这个问题的其他答案:

按照 marc-mutz-mmutz 的建议实现 heightForWidthhasHeightForWidth 根本不适合我。 我简要地研究了实现自定义布局管理器,但 Bleadof 的所有链接都已失效,当我找到 the documentation 并通读它时,它看起来对我想要实现的目标来说太复杂了。

我最终创建了一个自定义小部件,它响应 resizeEvent 并使用 setContentsMargin 设置边距,以便剩余内容区域保持所需的比例。

我发现我还必须在两个方向上将小部件的大小策略设置为QSizePolicy::Ignored,以避免因子小部件的大小请求而导致奇怪的大小调整问题——最终结果是我的小部件接受其父小部件分配给它的任何大小(然后如上所述设置其边距以在其内容区域中保持所需的纵横比)。

我的代码如下所示:

from PySide2.QtWidgets import QWidget, QSizePolicy


class AspectWidget(QWidget):
    '''
    A widget that maintains its aspect ratio.
    '''
    def __init__(self, *args, ratio=4/3, **kwargs):
        super().__init__(*args, **kwargs)
        self.ratio = ratio
        self.adjusted_to_size = (-1, -1)
        self.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored))

    def resizeEvent(self, event):
        size = event.size()
        if size == self.adjusted_to_size:
            # Avoid infinite recursion. I suspect Qt does this for you,
            # but it's best to be safe.
            return
        self.adjusted_to_size = size

        full_width = size.width()
        full_height = size.height()
        width = min(full_width, full_height * self.ratio)
        height = min(full_height, full_width / self.ratio)

        h_margin = round((full_width - width) / 2)
        v_margin = round((full_height - height) / 2)

        self.setContentsMargins(h_margin, v_margin, h_margin, v_margin)

(很明显,这段代码是用 Python 编写的,但它应该可以直接用 C++ 或您选择的语言来表达。)

【讨论】:

这是少数几个真正有效的选项之一。我在调整大小时往往会发现窗口卡顿,这是与我的应用程序特别相关还是此解决方案的常见问题? @LorenzoCastellino 我在我的应用程序中看不到任何调整窗口大小的卡顿。【参考方案5】:

在我的情况下,覆盖 heightForWidth() 不起作用。而且,对于某些人来说,获得使用调整大小事件的工作示例可能会有所帮助。

首先子类 qObject 来创建过滤器。 More about event filters.

class FilterObject:public QObject
public:
    QWidget *target = nullptr;//it holds a pointer to target object
    int goalHeight=0;
    FilterObject(QObject *parent=nullptr):QObject(parent)//uses QObject constructor
    bool eventFilter(QObject *watched, QEvent *event) override;//and overrides eventFilter function
;

然后是 eventFilter 函数。它的代码应该在 FilterObject 定义之外定义以防止警告。 Thanks to this answer.

bool FilterObject::eventFilter(QObject *watched, QEvent *event) 
    if(watched!=target)//checks for correct target object.
        return false;
    
    if(event->type()!=QEvent::Resize)//and correct event
        return false;
    

    QResizeEvent *resEvent = static_cast<QResizeEvent*>(event);//then sets correct event type

    goalHeight = 7*resEvent->size().width()/16;//calculates height, 7/16 of width in my case
    if(target->height()!=goalHeight)
        target->setFixedHeight(goalHeight);
    

    return true;
;

然后在主代码中创建 FilterObject 并将其设置为 EventFilter 侦听器到目标对象。 Thanks to this answer.

FilterObject *filter = new FilterObject();
QWidget *targetWidget = new QWidget();//let it be target object
filter->target=targetWidget;
targetWidget->installEventFilter(filter);

现在过滤器将接收所有 targetWidget 的事件并在调整大小事件时设置正确的高度。

【讨论】:

为避免闪烁 - 如上面的答案所述,不要将小部件添加到布局中,只需使其成为父级的子级,然后手动设置大小。

以上是关于如何在 Qt 中保持小部件的纵横比?的主要内容,如果未能解决你的问题,请参考以下文章

如何像 QLabel 一样快速调整图像大小?

试图在线程中保持 qt 小部件的焦点

Qt 加载消息应保持在父小部件的顶部并居中

用户可调整大小的小部件,如 3D 建模程序

Java:保持JPanel背景图像的纵横比

其他非 qt 窗口顶部的 Qt 小部件