动态更改 QLabel 的字体大小以适应可用 space
Dynamically change font size of QLabel to fit available space
我正在尝试使用所有可用的 space 制作 3 QLabel
比例的水平布局。更具体地说,这就是我所拥有的
这就是我的目标
目前,第二个图像是通过使用滑块更改 qlabels 的样式表来实现的。此外,由于我在组框内的布局中有三个标签,所以组框会调整大小以适合其内容,很酷。
现在我想放弃滑块方法,而是在移动分离器时自动调整可用的 space。在 this 问题中,OP 重新实现了 resizeEvent
,并且我看到其他 post 提出了相同的建议,并用这个 while( !doesFit )
或类似的东西逐点改变。
我尝试在调整大小事件和 splitterMoved 事件上使用这种方法。但是,这种方法很容易出现反馈循环和其他显示错误。在另一个问题中,他们建议启用 ignoreSizePolicy 以防止大小策略重新触发 sizeevent,但我喜欢 qt 如何处理布局的大小,它如何保持最小大小,然后在用户坚持时折叠小部件。如果 HLayout
忽略由 QLabels
触发的调整大小事件,也许它会起作用,但恕我直言,这个想法仍然不干净。
我想知道这是否是实现此目标的推荐方法,是否存在不太不稳定的解决方案,也许使用样式表。有一些我也可以放弃的行为,最小大小限制(因此用户可能会隐藏组框)。
如果这是推荐的方法,如果我有三个单独的标签,其中一个(数字)动态快速地更改其文本,我应该如何使用 fontmetrics?它不应该对性能产生影响,while
循环让我很警惕。
听起来 while(!fit)
方法无法解决问题。或者是吗?
--- 编辑重复问题
创建一个事件过滤器,如果重新设计以处理具有 3 个标签的布局,它也可能会起作用。我最终使用了第一个提到的 post 的版本和评论中提到的 post 的变体。如果问题重新打开,我会 post 回答。
可以应用 中的牛顿方法来处理给定布局中的所有小部件。它适用于任何具有可设置字体的小部件,而不仅仅是 QLabel
.
当给定一个好的起点时,牛顿算法收敛得相当快,例如交互式调整大小时。循环只执行一次并不少见。另一方面,QWidget::sizeHint
是整数值,并且小部件可能会四舍五入小数字体大小,因此有时迭代比预期的要慢一些。迭代次数被限制以确保良好的性能。
标签的自定义替换,提供 QSizeF sizeHintF()
,在这里效果更好。
小部件的最小尺寸有点牵强,因为尺寸不会随着小部件内容的变化而更新。不过,这可以很容易地解决。
// https://github.com/KubaO/Whosebugn/tree/master/questions/label-text-size-vert-40861305
#include <QtWidgets>
class LabelStretcher : public QObject {
Q_OBJECT
static constexpr const char kMinimumsAcquired[] = "ls_minimumsAcquired";
static constexpr const char kStretcherManaged[] = "ls_stretcherManaged";
public:
LabelStretcher(QObject *parent = 0) : QObject(parent) {
apply(qobject_cast<QWidget*>(parent));
}
void apply(QWidget *widget) {
if (!widget) return;
setManaged(widget);
setMinimumSize(widget);
widget->installEventFilter(this);
}
void setManaged(QWidget *w, bool managed = true) {
w->setProperty(kStretcherManaged, managed);
}
protected:
bool eventFilter(QObject * obj, QEvent * ev) override {
auto widget = qobject_cast<QWidget*>(obj);
if (widget && ev->type() == QEvent::Resize)
resized(widget);
return false;
}
private:
void onLayout(QLayout *layout, const std::function<void(QWidget*)> &onWidget) {
if (!layout) return;
auto N = layout->count();
for (int i = 0; i < N; ++i) {
auto item = layout->itemAt(i);
onWidget(item->widget());
onLayout(item->layout(), onWidget);
}
}
void setFont(QLayout *layout, const QFont &font) {
onLayout(layout, [&](QWidget *widget){ setFont(widget, font); });
}
void setFont(QWidget *widget, const QFont &font) {
if (!widget || !widget->property(kStretcherManaged).toBool()) return;
widget->setFont(font);
setFont(widget->layout(), font);
}
void setMinimumSize(QWidget *widget) {
if (widget->layout()) return;
widget->setMinimumSize(widget->minimumSizeHint());
}
static int dSize(const QSizeF & inner, const QSizeF & outer) {
auto dy = inner.height() - outer.height();
auto dx = inner.width() - outer.width();
return std::max(dx, dy);
}
qreal f(qreal fontSize, QWidget *widget) {
auto font = widget->font();
font.setPointSizeF(fontSize);
setFont(widget, font);
auto d = dSize(widget->sizeHint(), widget->size());
qDebug() << "f:" << fontSize << "d" << d;
return d;
}
qreal df(qreal fontSize, qreal dStep, QWidget *widget) {
fontSize = std::max(dStep + 1.0, fontSize);
return (f(fontSize + dStep, widget) - f(fontSize - dStep, widget)) / dStep;
}
void resized(QWidget *widget) {
qDebug() << "pre: " << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
if (!widget->property(kMinimumsAcquired).toBool()) {
onLayout(widget->layout(), [=](QWidget *widget){ setMinimumSize(widget); });
widget->setProperty(kMinimumsAcquired, true);
}
// Newton's method
auto font = widget->font();
auto fontSize = font.pointSizeF();
qreal dStep = 1.0;
int i;
for (i = 0; i < 10; ++i) {
auto prevFontSize = fontSize;
auto d = df(fontSize, dStep, widget);
if (d == 0) {
dStep *= 2.0;
continue;
}
fontSize -= f(fontSize, widget)/d;
fontSize = std::max(dStep + 1.0, fontSize);
auto change = fabs(prevFontSize - fontSize)/fontSize;
qDebug() << "d:" << d << " delta" << change;
if (change < 0.01) break; // we're within 1% of target
}
font.setPointSizeF(fontSize);
setFont(widget, font);
qDebug() << "post:" << i << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
}
};
constexpr const char LabelStretcher::kMinimumsAcquired[];
constexpr const char LabelStretcher::kStretcherManaged[];
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout layout{&w};
LabelStretcher stretch{&w};
QLabel labels[6];
QString texts[6] = {"V", "30.0", "kts", "H", "400.0", "ft"};
int i = 0, j = 0, k = 0;
for (auto & label : labels) {
stretch.setManaged(&label);
label.setFrameStyle(QFrame::Box);
label.setText(texts[k++]);
if (j == 0) label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
else if (j == 1) label.setAlignment(Qt::AlignCenter);
layout.addWidget(&label, i, j++);
if (j >= 3) { i++; j=0; }
}
w.show();
return app.exec();
}
#include "main.moc"
虽然我认为 KubaOber 的回答更好,但我会 post 这个,以防有人需要 post 中提到的答案中的解决方案。
请注意,示例文本也可以从标签中检索,字体可以从样式表中检索,并且代码可以潜在地放置在组框或布局的 resizeEvent
上。它不适用于标签的 resizeEvent
,因为它们会竞争 space。
这就是 KubaOber 回答出色的原因之一。我能想到的其他原因是稳定性,因为 3 个标签 space 与示例文本不同,因此字体大小不如预期的准确。因此,字体更改可能会再次触发调整大小事件。
static void fitGroupBoxLabels(QGroupBox* groupbox, const QFont &samplefont, const QLayout* const samplelayout)
{
groupbox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
QString sampletext = "V 1000.0 kts";
QRect availablerect = samplelayout->contentsRect();
if(samplefont.pointSizeF() <= 0) return; //not initalized yet, return
QRect textrect = QFontMetrics(samplefont).boundingRect(sampletext);
if(!textrect.isValid() || !availablerect.isValid()) return; //not initalized yet, return
float factorh = availablerect.width() / (float)textrect.width();
float factorw = availablerect.height() / (float)textrect.height();
float factor = std::min(factorh, factorw);
if (factor < 0.95 || factor > 1.05)
{
float fontSize = samplefont.pointSizeF()*factor;
QString groupBoxStyle = QString("QGroupBox{font-size:8pt} QLabel{font-size:%1pt}").arg(fontSize);
groupbox->setStyleSheet(groupBoxStyle);
}
}
在努力解决这个问题之后,我创建了 DynamicFontSizeLabel 和 DynamicFontSizePushButton 小部件。希望对你有帮助。
我正在尝试使用所有可用的 space 制作 3 QLabel
比例的水平布局。更具体地说,这就是我所拥有的
这就是我的目标
目前,第二个图像是通过使用滑块更改 qlabels 的样式表来实现的。此外,由于我在组框内的布局中有三个标签,所以组框会调整大小以适合其内容,很酷。
现在我想放弃滑块方法,而是在移动分离器时自动调整可用的 space。在 this 问题中,OP 重新实现了 resizeEvent
,并且我看到其他 post 提出了相同的建议,并用这个 while( !doesFit )
或类似的东西逐点改变。
我尝试在调整大小事件和 splitterMoved 事件上使用这种方法。但是,这种方法很容易出现反馈循环和其他显示错误。在另一个问题中,他们建议启用 ignoreSizePolicy 以防止大小策略重新触发 sizeevent,但我喜欢 qt 如何处理布局的大小,它如何保持最小大小,然后在用户坚持时折叠小部件。如果 HLayout
忽略由 QLabels
触发的调整大小事件,也许它会起作用,但恕我直言,这个想法仍然不干净。
我想知道这是否是实现此目标的推荐方法,是否存在不太不稳定的解决方案,也许使用样式表。有一些我也可以放弃的行为,最小大小限制(因此用户可能会隐藏组框)。
如果这是推荐的方法,如果我有三个单独的标签,其中一个(数字)动态快速地更改其文本,我应该如何使用 fontmetrics?它不应该对性能产生影响,while
循环让我很警惕。
听起来 while(!fit)
方法无法解决问题。或者是吗?
--- 编辑重复问题
可以应用 QLabel
.
当给定一个好的起点时,牛顿算法收敛得相当快,例如交互式调整大小时。循环只执行一次并不少见。另一方面,QWidget::sizeHint
是整数值,并且小部件可能会四舍五入小数字体大小,因此有时迭代比预期的要慢一些。迭代次数被限制以确保良好的性能。
标签的自定义替换,提供 QSizeF sizeHintF()
,在这里效果更好。
小部件的最小尺寸有点牵强,因为尺寸不会随着小部件内容的变化而更新。不过,这可以很容易地解决。
// https://github.com/KubaO/Whosebugn/tree/master/questions/label-text-size-vert-40861305
#include <QtWidgets>
class LabelStretcher : public QObject {
Q_OBJECT
static constexpr const char kMinimumsAcquired[] = "ls_minimumsAcquired";
static constexpr const char kStretcherManaged[] = "ls_stretcherManaged";
public:
LabelStretcher(QObject *parent = 0) : QObject(parent) {
apply(qobject_cast<QWidget*>(parent));
}
void apply(QWidget *widget) {
if (!widget) return;
setManaged(widget);
setMinimumSize(widget);
widget->installEventFilter(this);
}
void setManaged(QWidget *w, bool managed = true) {
w->setProperty(kStretcherManaged, managed);
}
protected:
bool eventFilter(QObject * obj, QEvent * ev) override {
auto widget = qobject_cast<QWidget*>(obj);
if (widget && ev->type() == QEvent::Resize)
resized(widget);
return false;
}
private:
void onLayout(QLayout *layout, const std::function<void(QWidget*)> &onWidget) {
if (!layout) return;
auto N = layout->count();
for (int i = 0; i < N; ++i) {
auto item = layout->itemAt(i);
onWidget(item->widget());
onLayout(item->layout(), onWidget);
}
}
void setFont(QLayout *layout, const QFont &font) {
onLayout(layout, [&](QWidget *widget){ setFont(widget, font); });
}
void setFont(QWidget *widget, const QFont &font) {
if (!widget || !widget->property(kStretcherManaged).toBool()) return;
widget->setFont(font);
setFont(widget->layout(), font);
}
void setMinimumSize(QWidget *widget) {
if (widget->layout()) return;
widget->setMinimumSize(widget->minimumSizeHint());
}
static int dSize(const QSizeF & inner, const QSizeF & outer) {
auto dy = inner.height() - outer.height();
auto dx = inner.width() - outer.width();
return std::max(dx, dy);
}
qreal f(qreal fontSize, QWidget *widget) {
auto font = widget->font();
font.setPointSizeF(fontSize);
setFont(widget, font);
auto d = dSize(widget->sizeHint(), widget->size());
qDebug() << "f:" << fontSize << "d" << d;
return d;
}
qreal df(qreal fontSize, qreal dStep, QWidget *widget) {
fontSize = std::max(dStep + 1.0, fontSize);
return (f(fontSize + dStep, widget) - f(fontSize - dStep, widget)) / dStep;
}
void resized(QWidget *widget) {
qDebug() << "pre: " << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
if (!widget->property(kMinimumsAcquired).toBool()) {
onLayout(widget->layout(), [=](QWidget *widget){ setMinimumSize(widget); });
widget->setProperty(kMinimumsAcquired, true);
}
// Newton's method
auto font = widget->font();
auto fontSize = font.pointSizeF();
qreal dStep = 1.0;
int i;
for (i = 0; i < 10; ++i) {
auto prevFontSize = fontSize;
auto d = df(fontSize, dStep, widget);
if (d == 0) {
dStep *= 2.0;
continue;
}
fontSize -= f(fontSize, widget)/d;
fontSize = std::max(dStep + 1.0, fontSize);
auto change = fabs(prevFontSize - fontSize)/fontSize;
qDebug() << "d:" << d << " delta" << change;
if (change < 0.01) break; // we're within 1% of target
}
font.setPointSizeF(fontSize);
setFont(widget, font);
qDebug() << "post:" << i << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
}
};
constexpr const char LabelStretcher::kMinimumsAcquired[];
constexpr const char LabelStretcher::kStretcherManaged[];
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout layout{&w};
LabelStretcher stretch{&w};
QLabel labels[6];
QString texts[6] = {"V", "30.0", "kts", "H", "400.0", "ft"};
int i = 0, j = 0, k = 0;
for (auto & label : labels) {
stretch.setManaged(&label);
label.setFrameStyle(QFrame::Box);
label.setText(texts[k++]);
if (j == 0) label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
else if (j == 1) label.setAlignment(Qt::AlignCenter);
layout.addWidget(&label, i, j++);
if (j >= 3) { i++; j=0; }
}
w.show();
return app.exec();
}
#include "main.moc"
虽然我认为 KubaOber 的回答更好,但我会 post 这个,以防有人需要 post 中提到的答案中的解决方案。
请注意,示例文本也可以从标签中检索,字体可以从样式表中检索,并且代码可以潜在地放置在组框或布局的 resizeEvent
上。它不适用于标签的 resizeEvent
,因为它们会竞争 space。
这就是 KubaOber 回答出色的原因之一。我能想到的其他原因是稳定性,因为 3 个标签 space 与示例文本不同,因此字体大小不如预期的准确。因此,字体更改可能会再次触发调整大小事件。
static void fitGroupBoxLabels(QGroupBox* groupbox, const QFont &samplefont, const QLayout* const samplelayout)
{
groupbox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
QString sampletext = "V 1000.0 kts";
QRect availablerect = samplelayout->contentsRect();
if(samplefont.pointSizeF() <= 0) return; //not initalized yet, return
QRect textrect = QFontMetrics(samplefont).boundingRect(sampletext);
if(!textrect.isValid() || !availablerect.isValid()) return; //not initalized yet, return
float factorh = availablerect.width() / (float)textrect.width();
float factorw = availablerect.height() / (float)textrect.height();
float factor = std::min(factorh, factorw);
if (factor < 0.95 || factor > 1.05)
{
float fontSize = samplefont.pointSizeF()*factor;
QString groupBoxStyle = QString("QGroupBox{font-size:8pt} QLabel{font-size:%1pt}").arg(fontSize);
groupbox->setStyleSheet(groupBoxStyle);
}
}
在努力解决这个问题之后,我创建了 DynamicFontSizeLabel 和 DynamicFontSizePushButton 小部件。希望对你有帮助。