【PyQt】QLabelのサイズ変化に合わせて文字サイズを自動的に変更する【Qt】


はじめに

 LinuxのX上でちょっとしたアプリを作りたいときPyqtって便利ですね。pythonとpython-pyqt5入れたら終わりだしwinでもlinuxでもコード変更なしで同じように動くとことか、Qtのシグナル・スロットの作法もC/C++だと拷問みたく文字打たされるあの感じが無いところとか(笑)

 いま作ってるアプリはグリッドレイアウトを使ってて、ウィンドウのサイズを変化させたらグリッドレイアウトに配置したQLabelのサイズも変化します。でもQLabelの中の文字列のサイズは変化しなくて、WPFのViewBoxみたいなのがないかなーと調べるも無さそうで、英語のQAサイトのものを参考にして作りました。(アプリの完成品はこんな感じです

QLabelの中の文字のサイズをQLabelの枠のサイズに応じて変化させる

 QLabelの中の文字のサイズをQLabelの枠のサイズに応じて変化させる方法は、ざっくり言うとQLabelを継承してresizeEventをオーバーライドしてそこでフォントサイズを可変してやるというもの。

#! /usr/bin/python3
# -*- coding: utf-8 -*-

from PyQt5 import QtCore
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QLabel, QSizePolicy

import sys

class QCustomLabel(QLabel):
    def __init__(self, text):
        super(QCustomLabel, self).__init__(text)
        self._font = QFont()
        self.setFont(self._font)
        self.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self._fontScale = 1.0

    def setFontFamily(self, face):
        self._font.setFamily(face)

    def setFontScale(self, scale):
        self._fontScale = scale

    def resizeEvent(self, evt):
        width = self.size().width() / len(self.text())
        height = self.size().height()
        baseSize = 0
        if width > height:
            baseSize = height
        else:
            baseSize = width

        self._font.setPixelSize(baseSize * self._fontScale)
        self.setFont(self._font)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()

    style = 'QWidget{background-color:#1976D2;} QLabel{color:#FFFFFF; background-color:#2196F3; border-color:#FF9800;}'
    app.setStyleSheet(style)

    layout = QGridLayout()
    layout.setContentsMargins(5, 5, 5, 5)

    labelA = QCustomLabel('あ')
    labelA.setFontFamily('源ノ角ゴシック Code JP M')
    layout.addWidget(labelA, 0, 0)

    labelB = QCustomLabel('あ')
    labelB.setFontFamily('源ノ角ゴシック Code JP M')
    labelB.setFontScale(2)
    layout.addWidget(labelB, 0, 1)

    window.setLayout(layout)
    window.resize(300, 200)
    window.show()
    sys.exit(app.exec_())

解説

    def __init__(self, text):
        super(QCustomLabel, self).__init__(text)
        self._font = QFont()
        self.setFont(self._font)
        self.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self._fontScale = 1.0

setSizePolicyでignoredを指定するのが一つ目のミソ。初期化時に入るフォントサイズと文字数がQlabel自体の最小サイズになるのを防ぎます。

    def setFontFamily(self, face):
        self._font.setFamily(face)

    def setFontScale(self, scale):
        self._fontScale = scale

setFontFamilyはQLabelのフォントを変えるの面倒だったので作ったもの。

setFontScaleはresizeの時に決定したサイズに掛ける係数。

    def resizeEvent(self, evt):
        width = self.size().width() / len(self.text())
        height = self.size().height()
        baseSize = 0
        if width > height:
            baseSize = height
        else:
            baseSize = width

        self._font.setPixelSize(baseSize * self._fontScale)
        self.setFont(self._font)

 ウィジェットのサイズが変更されるとこのイベントがコールされるので、その時のサイズを調べてフォントサイズをセットする。

 width側は単純に文字数で割って一文字あたりの幅を算出しているのだけど、フォントによっては正確でなくなるのでこの辺はQFontMetricsで取って計算するらしいのだけど、ちょっと計算がややこしいので端折りました…。

 で、どちらかサイズの小さいほうがセットすべきフォントのpixelSizeになる。self._fontScaleはおまけ機能のフォント大きさの変更に対応したもの。1ならなにもしない。  

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()

    style = 'QWidget{background-color:#1976D2;} QLabel{color:#FFFFFF; background-color:#2196F3; border-color:#FF9800;}'
    app.setStyleSheet(style)

    layout = QGridLayout()
    layout.setContentsMargins(5, 5, 5, 5)

    labelA = QCustomLabel('あ')
    labelA.setFontFamily('源ノ角ゴシック Code JP M')
    layout.addWidget(labelA, 0, 0)

    labelB = QCustomLabel('あ')
    labelB.setFontFamily('源ノ角ゴシック Code JP M')
    labelB.setFontScale(2)
    layout.addWidget(labelB, 0, 1)

    window.setLayout(layout)
    window.resize(300, 200)
    window.show()
    sys.exit(app.exec_())

 あとは、わかりやすいようにsetStyleSheetでちょっと色を変えて、メインのwindow背景とlabelの背景を分離、グリッドレイアウトのコンテンツ間にマージン開けてメインwindowの背景が見えるようにしてます。

一つ目の左に表示する"あ"はx1倍、二つ目の"あ"はx2倍で表示しています。

最後ですが、中のすべてのウィジェットのsetSizePolicyがIgnoreなのでウィンドウサイズを明示して指定しないと最初小さく表示されてしまいますので、300x200を指定しています。

結果


ちなみにQPushButtonとかでも応用可能のはずです。

参考

Automatically resizing label text in Qt - strange behaviour