动态更改 QLabel 的字体大小以适应可用空间

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

我正在尝试使用 3

QLabel
比例制作水平布局,使用其所有可用空间。更具体地说,这就是我所拥有的

这就是我的目标

目前,第二张图片是通过使用滑块更改 qlabel 的样式表来实现的。此外,由于我在组框内的布局中有三个标签,因此组框会调整大小以适合其内容,很酷。

现在我想放弃滑块方法,而是在移动分离器时自动调整可用空间。在this问题中,OP重新实现了

resizeEvent
,我也看到其他帖子提出了同样的建议,并用这个
while( !doesFit )
或类似的东西逐点改变。

我尝试在调整大小事件和 splitterMoved 事件上使用这种方法。然而,这种方法很容易出现反馈循环和其他显示错误。在另一个问题中,他们建议启用ignoreSizePolicy以防止大小策略重新触发sizeevent,但我喜欢qt如何处理布局的大小,它如何保持最小大小,然后如果用户坚持,它会折叠小部件。如果

HLayout
忽略由
QLabels
触发的调整大小事件,也许它会起作用,但恕我直言,这仍然是不干净的想法。

我想知道这是否是实现此目的的推荐方法,以及是否存在不太不稳定的解决方案,也许使用样式表。我还可以删除一些行为,即最小大小限制(因此用户可能会隐藏组框)。

如果这是推荐的做法,如果我有三个单独的标签,其中之一(数字)动态快速地更改其文本,我应该如何使用字体度量?它不应该对性能产生影响,并且

while
循环让我保持警惕。

听起来

while(!fit)
的方法不会解决这个问题。或者是吗?

--- 编辑重复问题

另一篇文章创建了一个事件过滤器,如果重新设计以处理具有 3 个标签的布局,它也可能起作用。我最终使用了第一篇提到的帖子的版本以及评论中提到的帖子的变体。如果问题重新提出,我会发布答案。

c++ qt qtstylesheets
4个回答
3
投票

可以应用这个答案中的牛顿法方法来处理给定布局中的所有小部件。它适用于任何具有可设置字体的小部件,而不仅仅是

QLabel

当给定一个好的起点时,牛顿算法会相当快地收敛,例如交互式调整大小时。让循环只执行一次的情况并不罕见。另一方面,

QWidget::sizeHint
是整数值,并且小部件可能会舍入小数字体大小,因此有时迭代会比预期慢一些。迭代次数受到限制以确保良好的性能。

提供

QSizeF sizeHintF()
的标签的自定义替代品在这里效果会更好。

小部件的最小尺寸有点过大,因为尺寸不会随着小部件内容的变化而更新。不过,这可以很容易地解决。

 // https://github.com/KubaO/stackoverflown/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"

1
投票

虽然我认为 KubaOber 的答案更好,但我会发布此内容,以防它对想要帖子中提到的答案中的解决方案的人有所帮助。

请注意,也可以从标签中检索示例文本,从样式表中检索字体,并且代码可能会放置在组框或布局的

resizeEvent
上。它不适用于标签的
resizeEvent
,因为它们会争夺空间。

这就是 KubaOber 答案优越的原因之一。我能想到的其他原因是稳定性,因为 3 个标签空间与示例文本不同,因此字体大小不够准确。因此,字体更改可能会再次触发调整大小事件。

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

}


0
投票

在解决这个问题之后,我创建了 DynamicFontSizeLabel 和 DynamicFontSizePushButton 小部件。希望有帮助。

https://github.com/jonaias/DynamicFontSizeWidgets/


0
投票

这里是基于 Jonaias 答案的 Python PyQt6 代码

import sys
from PyQt6.QtCore import Qt,QRectF
from PyQt6.QtWidgets import QApplication, QLabel, QWidget,QMainWindow
from PyQt6.QtGui import QFont, QFontMetricsF, QColor, QMouseEvent, QPaintEvent

FONT_PRECISION = 0.5

class DynamicFontSizeLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
  
        self.setIndent(0)
        self.textColor=QColor("black")
        self.setMinimumSize()

    def setMinimumSize(self,w=10,h=10):
        super().setMinimumSize(w,h)

    def paintEvent(self, event: QPaintEvent):
        newFont = self.font()
        fontSize = self.getWidgetMaximumFontSize(self, self.text())
        newFont.setPointSizeF(fontSize)
        self.setFont(newFont)
        super().paintEvent(event)

    def getWidgetMaximumFontSize(self, widget: QWidget, text: str) -> float:
        font = widget.font()
        widgetRect = widget.contentsRect()
        widgetWidth = widgetRect.width()
        widgetHeight = widgetRect.height()

        newFontSizeRect = QRectF()
        currentSize = font.pointSizeF()

        step = currentSize / 2.0

        if step <= FONT_PRECISION:
            step = FONT_PRECISION * 4.0

        lastTestedSize = currentSize

        currentHeight = 0
        currentWidth = 0

        if not text:
            return currentSize

        while (
            step > FONT_PRECISION
            or currentHeight > widgetHeight
            or currentWidth > widgetWidth
        ):
            lastTestedSize = currentSize
            font.setPointSizeF(currentSize)
            fm = QFontMetricsF(font)

            label = widget.findChild(QLabel)

            if label:
                newFontSizeRect = fm.boundingRect(
                    widgetRect,
                    (Qt.TextFlag.TextWordWrap if label.wordWrap() else 0) | label.alignment(),
                    text,
                )
            else:
                newFontSizeRect = fm.boundingRect(QRectF(widgetRect), 0, text)

            currentHeight = newFontSizeRect.height()
            currentWidth = newFontSizeRect.width()

            if currentHeight > widgetHeight or currentWidth > widgetWidth:
                currentSize -= step

                if step > FONT_PRECISION:
                    step /= 2.0

                if currentSize <= 0:
                    break
            else:
                currentSize += step

        return lastTestedSize

    def setTextColor(self, color: QColor):
        if color.isValid() and color != self.textColor:
            self.textColor = color
            self.setStyleSheet("color: " + color.name() + ";")

    def getTextColor(self):
        return self.textColor

    def setTextAndColor(self, text: str, color: QColor):
        self.setTextColor(color)
        self.setText(text)

    def minimumSizeHint(self):
        return super().minimumSizeHint()

    def sizeHint(self):
        return super().sizeHint()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QMainWindow()
    label = DynamicFontSizeLabel(window)
    color="#11fff7"
    #label.setStyleSheet(f"color:{color}")
    label.setTextAndColor("Hello, PyQt!", QColor(255, 0, 0))
    #label.setText("Hello")
    window.setCentralWidget(label)
    window.show()
    sys.exit(app.exec())

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