如何在Qt中保持小部件的长宽比?

问题描述 投票:0回答:6

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

user-interface qt widget aspect-ratio
6个回答
22
投票

您不必实现自己的布局管理器。您可以通过继承

QWidget
并重新实现

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

保持方形。然而,

heightForWidth()
不适用于 X11 的顶级窗口,因为显然 X11 协议不支持它。至于居中,可以传入
Qt::AlignCenter
作为
QBoxLayout::addWidget()
的第三个参数或
QGridLayout::addWidget()
的第五个参数。

注意: 至少在较新版本的 Qt 中,QWidget 不再具有

heightForWidth
widthForHeight
(因此它们不能被覆盖),因此
setWidthForHeight(true)
setHeightForWidth(true)
仅对后代有效QGraphicsLayout.


8
投票

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

  • 按照 marc-mutz-mmutz 的建议实现
    heightForWidth
    hasHeightForWidth
    对我来说根本不起作用。
  • 我简单地考虑过实现一个自定义布局管理器,但是所有 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++ 或您选择的语言表达应该很简单。)


7
投票

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

我认为保持固定宽高比的“正确”方法是创建自定义布局。您只需重写两个方法:

QLayoutItem::hasHeightForWidth()
QLayoutItem::heightForWidth()


7
投票

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

子类化QLayout时要实现的方法

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

进一步阅读


4
投票

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

首先子类化qObject来创建过滤器。 有关事件过滤器的更多信息。

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

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


0
投票

参见 这个答案 及其 PyQt 实现

在这里我进一步改进了它来处理小部件的最小/最大尺寸限制。

请注意,所提供链接中的初始答案附加了一些许可证,因此您应该遵守此许可证才能使用此代码。

# Adapted from https://gist.github.com/sjdv1982/75899d10e6983b878f63083e3c47b39b

from PyQt5.QtWidgets import QLayout, QWidget
from PyQt5.QtCore import Qt, QSize


class AspectLayout(QLayout):
    def __init__(self, aspect):
        self.aspect = aspect
        self.item = None
        super().__init__()
        self.setContentsMargins(0,0,0,0)

    def addItem(self, item):
        assert self.item is None, "AspectLayout can contain only 1 widget"
        self.item = item

    def itemAt(self, index):
        if index != 0:
            return None
        if self.item is None:
            return None
        return self.item

    def takeAt(self, index):
        if index != 0:
            return None
        if self.item is None:
            return None
        result = self.item
        self.item = None
        return result

    def setGeometry(self, rect):
        super().setGeometry(rect)
        if self.item is None:
            return
        widget = self.item.widget()  # type: QWidget
        margins = self.getContentsMargins()
        w_avail = rect.width() - margins[1] - margins[3]
        w_max = min(w_avail, widget.maximumWidth())
        h_avail = rect.height() - margins[0] - margins[2]
        h_max = min(h_avail, widget.maximumHeight())
        w_min = widget.minimumWidth()
        h_min = widget.minimumHeight()
        h = h_max
        w = h * self.aspect
        if w > w_max:
            x = margins[1]
            w = w_max
            h = w/self.aspect
            if self.item.alignment() &  Qt.AlignTop:
                y = margins[0]
            elif self.item.alignment() &  Qt.AlignBottom:
                y = rect.height() - margins[2] - h
            else:
                y = margins[0] + (h_max-h) / 2
        else:
            y = margins[0]
            if self.item.alignment() &  Qt.AlignLeft:
                x = margins[1]
            elif self.item.alignment() &  Qt.AlignRight:
                x = rect.width() - margins[3] - w
            else:
                x = margins[1] + (w_max-w) / 2
        self.item.widget().setGeometry(
            rect.x() + x,
            rect.y() + y,
            w, h)

    def sizeHint(self):
        margins = self.getContentsMargins()
        if self.item is None:
            return QSize(margins[0]+margins[2], margins[1]+margins[3])
        widget = self.item.widget()  # type: QWidget
        s = self.item.sizeHint()
        w, h = s.width(), s.height()
        if w < h * self.aspect:
            w = int(h * self.aspect)
            if w > widget.maximumWidth():
                w = widget.maximumWidth()
                h = int(w / self.aspect)
                if h < widget.minimumHeight():
                    h = widget.minimumHeight()
                    w = int(h * self.aspect)
        elif h < w / self.aspect:
            h = int(w / self.aspect)
            if h > widget.maximumHeight():
                h = widget.maximumHeight()
                w = int(h * self.aspect)
                if w < widget.minimumWidth():
                    w = widget.minimumWidth()
                    h = int(w / self.aspect)
        return QSize(margins[0]+margins[2] + w, margins[1]+margins[3] + h)

    def minimumSize(self):
        margins = self.getContentsMargins()
        if self.item is None:
            return QSize(margins[0]+margins[2],margins[1]+margins[3])
        widget = self.item.widget()  # type: QWidget
        s = widget.minimumSize()
        w, h = s.width(), s.height()
        if w < h * self.aspect:
            w = int(h * self.aspect)
        elif h < w / self.aspect:
            h = int(w / self.aspect)
        return QSize(margins[0]+margins[2] + w, margins[1]+margins[3] + h)

    def expandingDirections(self):
       return Qt.Horizontal | Qt.Vertical

    def hasHeightForWidth(self):
        return False

    def count(self):
        if self.item is None:
            return 0
        else:
            return 1

    def heightForWidth(self, width):
        margins = self.getContentsMargins()
        height = (width - margins[1] - margins[3]) / self.aspect
        height += margins[0] + margins[2]
        return int(height)


if __name__ == "__main__":
    from PyQt5.QtWidgets import (
        QVBoxLayout, QLabel, QWidget, QApplication, QTextEdit, QPushButton)

    app = QApplication([])
    w = QWidget()
    layout = QVBoxLayout()
    w.setLayout(layout)

    # label whose aspect ratio will be kept
    aspect_layout = AspectLayout(2)
    layout.addLayout(aspect_layout)
    label = QLabel()
    aspect_layout.addWidget(label)

    # styling of the label
    control = QTextEdit('background-color: yellow;')
    control.setFixedHeight(100)
    layout.addWidget(control)
    label.setStyleSheet(control.toPlainText())
    ok_button = QPushButton('OK')
    ok_button.clicked.connect(lambda: label.setStyleSheet(control.toPlainText()))
    layout.addWidget(ok_button)

    w.show()
    app.exec()

© www.soinside.com 2019 - 2024. All rights reserved.