如何在 Qt 中保持小部件的纵横比以及使小部件居中?
您不必实现自己的布局管理器。您可以通过继承
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.
我也试图实现所要求的效果:一个保持固定宽高比同时保持在其分配空间中心的小部件。起初我尝试了这个问题的其他答案:
heightForWidth
和 hasHeightForWidth
对我来说根本不起作用。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++ 或您选择的语言表达应该很简单。)
从
resize()
内部调用 resizeEvent()
对我来说从来没有很好的效果——充其量它会导致窗口大小调整两次时闪烁(就像你一样),最坏的情况是无限循环。
我认为保持固定宽高比的“正确”方法是创建自定义布局。您只需重写两个方法:
QLayoutItem::hasHeightForWidth()
和 QLayoutItem::heightForWidth()
。
正确的答案是创建自定义布局管理器。这可以通过子类化 QLayout 来实现。
在我的情况下,覆盖 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 的事件并在调整大小事件时设置正确的高度。
在这里我进一步改进了它来处理小部件的最小/最大尺寸限制。
请注意,所提供链接中的初始答案附加了一些许可证,因此您应该遵守此许可证才能使用此代码。
# 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()