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

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

【问题讨论】:

    标签: user-interface qt widget aspect-ratio


    【解决方案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 版本中做到这一点?
    【解决方案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;
    返回布局的首选大小。

    进一步阅读

    【讨论】:

    • 你说的是子类化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 的事件并在调整大小事件时设置正确的高度。

    【讨论】:

    • 为避免闪烁 - 如上面的答案所述,不要将小部件添加到布局中,只需使其成为父级的子级,然后手动设置大小。
    猜你喜欢
    • 2014-08-07
    • 1970-01-01
    • 2016-01-13
    • 2012-08-25
    • 2015-09-26
    • 2017-11-13
    • 2010-12-17
    • 2016-10-06
    • 2018-04-02
    相关资源
    最近更新 更多