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

38

如何在Qt中保持窗口小部件的纵横比,以及如何将窗口小部件居中?

5个回答

21

您不必实现自己的布局管理器。您可以通过继承QWidget并重新实现来完成。

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

保持正方形。然而,在X11上,heightForWidth()在顶级窗口上不起作用,因为显然X11协议不支持它。至于居中,您可以将Qt::AlignCenter作为QBoxLayout :: addWidget()的第三个参数或QGridLayout :: addWidget()的第五个参数传递。

注意:在较新版本的Qt中,QWidget不再具有heightForWidthwidthForHeight(因此无法重写它们),因此setWidthForHeight(true)setHeightForWidth(true) 仅对QGraphicsLayout的后代有效


是的。你需要设置 sizePolicy,使得 hasHeightForWidth() == true,布局经常会给窗口部件更多的宽度或高度,但这是布局尝试满足约束条件。使用QLayoutItem 解决方案将产生相同的效果,因为它们是等价的(请参阅 QWidgetLayoutItem)。 - Marc Mutz - mmutz
@MarcMutz-mmutz:当然,X11是支持这个功能的:请看一下XSizeHints http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/wm-normal-hints.html,它允许告诉窗口管理器一个窗口的纵横比。 - datenwolf
7
如果我想设置一个 widthForHeight,怎么办呢?QWidget没有 widthForHeight 方法。 - Neptilo
5
新的Qt版本中如何做到这一点? - Tomáš Zato
关于Qt 5.7的问题,可以参考http://doc.qt.io/qt-5/qwidget.html#hasHeightForWidth。 - cbuchart

9

正确的答案是创建自定义布局管理器。这可以通过子类化 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;
返回布局的首选大小。

进一步阅读


1
你在谈论子类化QLayout,但是你引用的函数来自于QLayoutItem,它只是从QWidget调用相应的函数。那么继承QLayout能给你什么,而QWidget本身不能呢? - Marc Mutz - mmutz
3
所有链接都失效了。 - Tomáš Zato

7

resizeEvent()中调用resize()从未对我有好处——最好的情况是会导致窗口大小调整两次(就像你一样)引起闪烁,最坏的情况是无限循环。

我认为保持固定宽高比的“正确”方法是创建自定义布局。您只需要覆盖两个方法:QLayoutItem::hasHeightForWidth()QLayoutItem::heightForWidth()


这似乎是正确的答案,但我得在工作时仔细研究一下,也许在那之后能给出更详细的答案。 - Bleadof
我同意这个观点,它可能会导致无限循环。因为在resizeEvent函数内调用setMinimumHeight,有时当我调整大小时,程序会崩溃,我的调用堆栈非常庞大。遗憾的是,我正在考虑创建自己的布局。 - undefined

5
我也试图实现所请求的效果:一个小部件在保持固定宽高比的同时保持在其分配空间的中心。起初,我尝试了来自此问题的其他答案:
- 实施像marc-mutz-mmutz建议的heightForWidthhasHeightForWidth并没有对我起作用。 - 我简要地研究了实现自定义布局管理器,但是Bleadof的所有链接都已失效,当我找到文档并阅读时,它看起来对于我想要实现的内容来说过于复杂。 最终,我创建了一个自定义小部件,响应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++或您选择的语言表达应该很简单。)

1
这是为数不多真正有效的选项之一。我发现在调整大小时窗口会出现卡顿,这是与我的应用程序特别相关还是这个解决方案普遍存在的问题? - Kastakin
@LorenzoCastellino 我在我的应用程序中没有看到任何窗口调整大小时的卡顿。 - talljosh

4
在我的情况下,覆盖heightForWidth()无效。对于某些人来说,获得使用resize事件的工作示例可能会很有帮助。
首先,子类化qObject以创建过滤器。更多关于事件过滤器的信息请参见Qt文档
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定义之外进行定义,以避免出现警告。感谢这篇答案。

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监听器。感谢这个答案。点击此处了解更多。
FilterObject *filter = new FilterObject();
QWidget *targetWidget = new QWidget();//let it be target object
filter->target=targetWidget;
targetWidget->installEventFilter(filter);

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

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

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