【问题标题】:Custom Table with Auto-Scaling Text具有自动缩放文本的自定义表格
【发布时间】:2021-03-19 02:29:49
【问题描述】:

我正在尝试制作一个表格,其中文本“填充”可用单元格,从而最大化字体。我遇到了这些问题:

(1) 缩小窗口时,文字的字体大小不会随着大小的变化而平滑减小。

(2)由于字体没有正确调整大小,会出现剪裁(多在文本的左右两端)

文本大小发生在 scale_text() 中,它基于此stackoverflow answer。这个问题是因为如何处理水平/垂直分解而发生的吗?我不知道为什么文字会突然变得很大,即使字体的磅值与周围的没有任何不同。

下面的图片和MRE:

MRE:

import sys
from PyQt4 import QtCore, QtGui

class TestTable(QtGui.QTableWidget):

    def __init__(self):
        QtGui.QTableWidget.__init__(self)
        self.setColumnCount(3)
        self.setRowCount(8)

        self.header_horizontal = TestHeader(self, QtCore.Qt.Horizontal)
        self.header_vertical = TestHeader(self, QtCore.Qt.Vertical)

        self.setHorizontalHeader(self.header_horizontal)
        self.setVerticalHeader(self.header_vertical)

        self.font = QtGui.QFont()
        self.font.setPointSize(10)
        self.font_metrics = QtGui.QFontMetrics(self.font)

        self.auto_size = 0

        self.align_left = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignLeft
        self.align_center = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
        self.align_right = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignRight

    def widget_at_pos(self, row, column):   # gets widget at (row,col)
        if not 0 <= row < self.rowCount():
            return None
        if not 0 <= column < self.columnCount():
            return None

        cell = self.cellWidget(row, column)
        if cell is None:
            cell = self.item(row, column)
        return cell

    def set_header_labels(self, labels, direction="horizontal"):    # sets headers
        if not isinstance(labels, list):
            return

        if direction == "horizontal":
            self.setColumnCount(len(labels))
        elif direction == "vertical":
            self.setRowCount(len(labels))
        else:
            return

        i = 0
        for label in labels:
            self.set_header_label(i, label, direction)
            i += 1

    def set_header_label(self, index, label, direction="horizontal"):
        item = QtGui.QTableWidgetItem()
        item.setText(label)
        if direction == "horizontal":
            self.setHorizontalHeaderItem(index, item)
        elif direction == "vertical":
            self.setVerticalHeaderItem(index, item)
        else:
            return

    def set_box_column(self, column=0, fmt="text", default=-1): # adds boxes in one column
        items = list()
        for row in range(self.rowCount()):
            item = self.set_box(default, fmt, row, column)
            items.append(item)
        return items

    def set_box(self, value=None, fmt="text", row=0, column=0):
        if fmt == "text":
            box = SizedLineEdit(self)
            box.setText(value)
        elif fmt == "number":
            box = SizedSpinBox(self)
            box.setValue(value)
        else:
            return None

        box.setFont(self.font)
        self.setCellWidget(row, column, box)
        return box

    def clamp_font_table(self):     # clamps each row to their minimum font
        for i in range(self.rowCount()):
            self.clamp_font_row(i)

    def clamp_font_row(self, row):  # Sets widgets in row to smallest font
        min_font = 0

        for i in range(self.columnCount()): # find min font
            cell = self.widget_at_pos(row, i)
            if cell is None:
                continue
            if not isinstance(cell, (SizedSpinBox, SizedLineEdit)):
                continue
            if i == 0:
                min_font = cell.getFont().pointSize()
                continue
            min_font = min(min_font, cell.getFont().pointSize())

        for i in range(self.columnCount()):
            cell = self.widget_at_pos(row, i)
            if not isinstance(cell, (SizedSpinBox, SizedLineEdit)):
                continue
            cell.setFont(size=min_font)

    def scale_text_table(self):     # resize contents of cells to fit the space
        for i in range(self.rowCount()):
            for j in range(self.columnCount()):
                self.scale_text_cell(i, j)
            self.clamp_font_row(i)

    def scale_text_cell(self, row, column): # resize contents of cell to fit the space
        cell = self.widget_at_pos(row, column)
        if cell is None:
            return
        if isinstance(cell, (SizedSpinBox, SizedLineEdit)):
            cell.scale_text()

    def scale_to_data(self):    # adjusts column spacing to account for cell contents
        horizontal_header = self.horizontalHeader()
        vertical_header = self.verticalHeader()
        if not isinstance(horizontal_header, QtGui.QHeaderView):
            return
        if not isinstance(vertical_header, QtGui.QHeaderView):
            return

        horizontal_header.scale_to_data()
        vertical_header.scale_to_data()

        if self.auto_size == 1:
            horizontal_header.setResizeMode(QtGui.QHeaderView.Interactive)
            horizontal_header.setStretchLastSection(True)
            if self.rowCount() > 1:
                vertical_header.setResizeMode(QtGui.QHeaderView.Stretch)
                vertical_header.setStretchLastSection(True)
        else:
            horizontal_header.setResizeMode(QtGui.QHeaderView.Stretch)
            horizontal_header.setStretchLastSection(True)

            vertical_header.setResizeMode(QtGui.QHeaderView.Stretch)
            vertical_header.setStretchLastSection(True)

        TestTable.resizeEvent(self, QtGui.QResizeEvent(self.size(), QtCore.QSize()))

    def resizeEvent(self, *args, **kwargs):
        self.scale_text_table()
        self.clamp_font_table()
        QtGui.QTableWidget.resizeEvent(self, *args, **kwargs)

class TestHeader(QtGui.QHeaderView):

    def __init__(self, parent, orientation=QtCore.Qt.Horizontal):
        QtGui.QHeaderView.__init__(self, orientation, parent)
        self.parent = parent
        self.setResizeMode(QtGui.QHeaderView.Interactive)

    def scale_to_data(self): # resize header to fit the content of its items
        for i in range(self.count()):
            width = self.sectionSize(i) # save current section width
            self.setResizeMode(QtGui.QHeaderView.ResizeToContents) # resize cell
            width = max(width, self.sectionSize(i)) # compare sizes
            self.setResizeMode(QtGui.QHeaderView.Interactive)
            self.resizeSection(i, width)

class SizedLineEdit(QtGui.QLineEdit):

    def __init__(self, parent=None, text="sizedlineedit", *args):
        QtGui.QLineEdit.__init__(self, parent, *args)
        self.parent = parent
        self.value = unicode(text)
        self.setReadOnly(True)

        self.minimum_characters = 1
        self.min_text_size = 8
        self.max_text_size = 300

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)

        self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)

        self.font = QtGui.QFont()
        self.font_metrics = QtGui.QFontMetrics(self.font)
        self.setMinimumHeight(self.font.pointSize() * 1.1)
        self.setFont(self.font)

        self.setText(self.value)
        self.scale_text()

    def setFont(self, font=None, size=8):
        if isinstance(font, QtGui.QFont):
            self.font = font
        size = int(size)
        self.font.setPointSize(size)

        self.font_metrics = QtGui.QFontMetrics(self.font)
        QtGui.QLineEdit.setFont(self, self.font)

    def getFont(self):
        return self.font

    def setText(self, text):
        self.value = text
        QtGui.QLineEdit.setText(self, text)
        self.scale_text()

    def get_minimum_width(self):
        width = self.font_metrics.width(self.text())
        s = "0" * self.minimum_characters
        min_calc_width = self.font_metrics.width(s)
        return max(width, min_calc_width)

    def scale_text(self):   # resize text to fit in the cell
        h_factor = 1
        v_factor = 1

        r_width = float(self.rect().width())
        t_width = float(self.get_minimum_width())

        r_height = float(self.rect().height())
        t_height = float(self.font_metrics.height())

        if r_width > 0 and t_width > 0:
            h_factor = r_width / t_width

        if r_height > 0 and t_height > 0:
            v_factor = r_height / t_height

        factor = min(h_factor, v_factor)

        if factor < 1 or factor > 1.3:
            new_size = int(self.font.pointSize() * factor)
            if new_size % 2 != 0:
                new_size -= 1
            new_size = max(new_size, self.min_text_size)
            new_size = min(new_size, self.max_text_size)
            # print new_size
            self.setFont(size=new_size)

    def resizeEvent(self, *args, **kwargs):
        QtGui.QLineEdit.resizeEvent(self, *args, **kwargs)
        self.scale_text()   # call twice to avoid wacky sizing
        self.scale_text()

class SizedSpinBox(QtGui.QDoubleSpinBox):

    def __init__(self, parent=None):
        QtGui.QDoubleSpinBox.__init__(self, parent=parent)
        self.parent = parent
        self.setReadOnly(True)

        self.min_text_size = 8
        self.max_text_size = 300

        self.min = float("-inf")
        self.max = float("inf")

        self.leading = 0

        # regex validation
        float_regex = QtCore.QRegExp("[0-9-.]*")
        float_regex.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.validator = QtGui.QRegExpValidator(float_regex, self)

        # size policy
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)

        # alignment
        self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)

        # font
        self.font = QtGui.QFont()
        self.font_metrics = QtGui.QFontMetrics(self.font)
        self.setMinimumHeight(self.font.pointSize() * 1.1)
        self.setFont(self.font)

        self.line_edit = self.findChild(QtGui.QLineEdit)
        self.line_edit.installEventFilter(self)

        self.setValue(-1)
        self.setDecimals(3)
        self.scale_text()

    def setFont(self, font=None, size=8):
        if isinstance(font, QtGui.QFont):
            self.font = font
        size = int(size)
        self.font.setPointSize(size)

        self.font_metrics = QtGui.QFontMetrics(self.font)
        QtGui.QDoubleSpinBox.setFont(self, self.font)

    def getFont(self):
        return self.font

    def setValue(self, value):
        QtGui.QDoubleSpinBox.setValue(self, value)
        self.scale_text()

    def get_minimum_width(self):
        s = "0" * (self.leading + self.decimals() + 1)
        width = self.font_metrics.width(self.text())
        calc_width = self.font_metrics.width(s)
        return max(width, calc_width)

    def scale_text(self):   # resize text to fit within space
        h_factor = 1
        v_factor = 1

        r_width = float(self.rect().width())
        t_width = float(self.get_minimum_width())

        r_height = float(self.rect().height())
        t_height = float(self.font_metrics.height())

        if r_width > 0 and t_width > 0:
            h_factor = r_width / t_width

        if r_height > 0 and t_height > 0:
            v_factor = r_height / t_height

        factor = min(h_factor, v_factor)

        if factor < 1 or factor > 1.3:
            new_size = int(self.font.pointSize() * factor)
            if new_size % 2 != 0:
                new_size -= 1
            new_size = max(new_size, self.min_text_size)
            new_size = min(new_size, self.max_text_size)
            # print new_size
            self.setFont(size=new_size)

    def resizeEvent(self, *args, **kwargs):
        QtGui.QDoubleSpinBox.resizeEvent(self, *args, **kwargs)
        self.scale_text()
        self.scale_text()


def main():
    app = QtGui.QApplication(sys.argv)
    tbl = TestTable()

    columns = [u"Column 1", u"Column 2", u"Column 3", u"Column 4"]
    rows = [u"Row 1", u"Row 2", u"Row 3", u"Row 4", u"Row 5", u"Row 6"]

    tbl.set_header_labels(columns, "horizontal")
    tbl.set_header_labels(rows, "vertical")

    tbl.set_box_column(0, "text", "False")
    tbl.set_box_column(1, "number", -1)
    tbl.set_box_column(2, "text", "True")
    tbl.set_box_column(3, "number", 1)

    tbl.show()
    tbl.scale_to_data()
    app.exec_()

main()

【问题讨论】:

    标签: python pyqt4


    【解决方案1】:

    我得到了这个工作。更新后的 MRE 包含在末尾。

    上面的代码我做了一些改动:

    (1) 更改了宽度的计算方式。

    def get_minimum_width(self):   # calculates width using min_font_size
        width = self.min_font_metrics.width(self.text())
        s = "0" * (self.leading + self.decimals() + 1)
        min_calc_width = self.min_font_metrics.width(s)
        return max(width, min_calc_width)
    
    def get_width(self):   # calculates width using current font size
        width = self.font_metrics.width(self.text())
        s = "0" * (self.leading + self.decimals() + 1)
        calc_width = self.font_metrics.width(s)
        return max(width, calc_width)
    

    (2) 设置文本时设置最小尺寸提示。这会将 LineEdit 的最小大小设置为允许的最小文本大小的文本大小。

    def setValue(self, value):
        QtGui.QDoubleSpinBox.setValue(self, value)
        self.setMinimumWidth(self.get_minimum_width()) # added line
        self.scale_text()
    

    (3) 计算每次调整大小时可能的最大字体大小。剪裁时,文本会将自身设置为允许的最大字体,即 300。如果我们根据框的大小计算最大字体大小,我们可以将最大值设置为 45(随机大小)而不是 300。

    def setFont(self, font=None, size=8):
        if isinstance(font, QtGui.QFont):
            self.font = font
    
        size = int(size)
        size = max(size, self.min_text_size)
        size = min(size, self.calculate_max_font()) # clamps fonts with updating size
        self.font.setPointSize(size)
    
        self.font_metrics = QtGui.QFontMetrics(self.font)
        QtGui.QDoubleSpinBox.setFont(self, self.font)
    
    def calculate_max_font(self, lower=8, upper=300): # O(logn) alg to calculate maximum possible font
        mid = lower + ((upper - lower) // 2)
        mid_font = QtGui.QFont()
        mid_font.setPointSize(mid)
        metrics = QtGui.QFontMetrics(mid_font)
    
        v_factor = 1
        h_factor = 1
    
        r_width = float(self.rect().width())
        t_width = float(metrics.width(self.text()))
    
        r_height = float(self.rect().height())
        t_height = float(metrics.height())
    
        if r_width > 0 and t_width > 0:
            h_factor = r_width / t_width
    
        if r_height > 0 and t_height > 0:
            v_factor = r_height / t_height
    
        if v_factor > 1.2 and h_factor > 1.3:
            return self.calculate_fit(mid, upper)
        elif v_factor < 1 and h_factor < 1:
            return self.calculate_fit(lower, mid)
    
        factor = min(h_factor, v_factor)
    
        if factor < 1 or factor > 1.3:
            new_size = int(mid * factor)
            if new_size % 2 != 0:
                new_size -= 1
            return new_size
    
        return mid
    

    (4) 在 scale_text 中添加了截取文本的捕获。 rect() 是小部件的边界矩形。如果文本超出该边界,则设置字体时出现问题。

    def scale_text(self):  # resize text to fit within space
        h_factor = 1
        v_factor = 1
    
        r_width = float(self.rect().width())
        t_width = float(self.get_width())
    
        r_height = float(self.rect().height())
        t_height = float(self.font_metrics.height())
    
        if t_width > r_width and t_height > r_height: # added
            self.setFont(size=self.min_text_size)
            return
    
        if r_width > 0 and t_width > 0:
            h_factor = r_width / t_width
    
        if r_height > 0 and t_height > 0:
            v_factor = r_height / t_height
    
        factor = min(h_factor, v_factor)
    
        if factor < 1 or factor > 1.3:
            new_size = int(self.font.pointSize() * factor)
            if new_size % 2 != 0:
                new_size -= 1
            self.setFont(size=new_size)
    

    (5) 减少了调整大小的调用次数,特别是在表的调整大小事件处理程序中进行的调用。

    def resizeEvent(self, *args, **kwargs):
        QtGui.QTableWidget.resizeEvent(self, *args, **kwargs)
        self.clamp_font_table()
    

    我仍然遇到一些错误,但在大多数情况下,这应该很干净。

    更新的 MRE:

    import sys
    from PyQt4 import QtCore, QtGui
    
    class TestTable(QtGui.QTableWidget):
    
        def __init__(self):
            QtGui.QTableWidget.__init__(self)
            self.setColumnCount(3)
            self.setRowCount(8)
    
            self.header_horizontal = TestHeader(self, QtCore.Qt.Horizontal)
            self.header_vertical = TestHeader(self, QtCore.Qt.Vertical)
    
            self.setHorizontalHeader(self.header_horizontal)
            self.setVerticalHeader(self.header_vertical)
    
            self.font = QtGui.QFont()
            self.font.setPointSize(10)
            self.font_metrics = QtGui.QFontMetrics(self.font)
    
            self.auto_size = 0
    
            self.align_left = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignLeft
            self.align_center = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
            self.align_right = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignRight
    
            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
    
        def widget_at_pos(self, row, column):   # gets widget at (row,col)
            if not 0 <= row < self.rowCount():
                return None
            if not 0 <= column < self.columnCount():
                return None
    
            cell = self.cellWidget(row, column)
            if cell is None:
                cell = self.item(row, column)
            return cell
    
        def set_header_labels(self, labels, direction="horizontal"):    # sets headers
            if not isinstance(labels, list):
                return
    
            if direction == "horizontal":
                self.setColumnCount(len(labels))
            elif direction == "vertical":
                self.setRowCount(len(labels))
            else:
                return
    
            i = 0
            for label in labels:
                self.set_header_label(i, label, direction)
                i += 1
    
        def set_header_label(self, index, label, direction="horizontal"):
            item = QtGui.QTableWidgetItem()
            item.setText(label)
            if direction == "horizontal":
                self.setHorizontalHeaderItem(index, item)
            elif direction == "vertical":
                self.setVerticalHeaderItem(index, item)
            else:
                return
    
        def set_box_column(self, column=0, fmt="text", default=-1): # adds boxes in one column
            items = list()
            for row in range(self.rowCount()):
                item = self.set_box(default, fmt, row, column)
                items.append(item)
            return items
    
        def set_box(self, value=None, fmt="text", row=0, column=0):
            if fmt == "text":
                box = SizedLineEdit(self)
                box.setText(value)
            elif fmt == "number":
                box = SizedSpinBox(self)
                box.setValue(value)
            else:
                return None
    
            box.setFont(self.font)
            self.setCellWidget(row, column, box)
            return box
    
        def clamp_font_table(self):     # clamps each row to their minimum font
            for i in range(self.rowCount()):
                self.clamp_font_row(i)
    
        def clamp_font_row(self, row):  # Sets widgets in row to smallest font
            min_font = 0
    
            for i in range(self.columnCount()): # find min font
                cell = self.widget_at_pos(row, i)
                if cell is None:
                    continue
                if not isinstance(cell, (SizedSpinBox, SizedLineEdit)):
                    continue
                if i == 0:
                    min_font = cell.getFont().pointSize()
                    continue
                min_font = min(min_font, cell.getFont().pointSize())
    
            for i in range(self.columnCount()):
                cell = self.widget_at_pos(row, i)
                if not isinstance(cell, (SizedSpinBox, SizedLineEdit)):
                    continue
                cell.setFont(size=min_font)
    
        def scale_text_table(self):     # resize contents of cells to fit the space
            for i in range(self.rowCount()):
                for j in range(self.columnCount()):
                    self.scale_text_cell(i, j)
                self.clamp_font_row(i)
    
        def scale_text_cell(self, row, column): # resize contents of cell to fit the space
            cell = self.widget_at_pos(row, column)
            if cell is None:
                return
            if isinstance(cell, (SizedSpinBox, SizedLineEdit)):
                cell.scale_text()
    
        def scale_to_data(self):    # adjusts column spacing to account for cell contents
            horizontal_header = self.horizontalHeader()
            vertical_header = self.verticalHeader()
            if not isinstance(horizontal_header, QtGui.QHeaderView):
                return
            if not isinstance(vertical_header, QtGui.QHeaderView):
                return
    
            TestTable.resizeEvent(self, QtGui.QResizeEvent(self.size(), QtCore.QSize()))
    
            horizontal_header.scale_to_data()
            vertical_header.scale_to_data()
    
            if self.auto_size == 1:
                horizontal_header.setResizeMode(QtGui.QHeaderView.Interactive)
                horizontal_header.setStretchLastSection(True)
                if self.rowCount() > 1:
                    vertical_header.setResizeMode(QtGui.QHeaderView.Stretch)
                    vertical_header.setStretchLastSection(True)
            else:
                horizontal_header.setResizeMode(QtGui.QHeaderView.Stretch)
                horizontal_header.setStretchLastSection(True)
    
                vertical_header.setResizeMode(QtGui.QHeaderView.Stretch)
                vertical_header.setStretchLastSection(True)
    
        def resizeEvent(self, *args, **kwargs):
            QtGui.QTableWidget.resizeEvent(self, *args, **kwargs)
            self.clamp_font_table()
    
    class TestHeader(QtGui.QHeaderView):
    
        def __init__(self, parent, orientation=QtCore.Qt.Horizontal):
            QtGui.QHeaderView.__init__(self, orientation, parent)
            self.parent = parent
            self.setResizeMode(QtGui.QHeaderView.Interactive)
    
        def scale_to_data(self): # resize header to fit the content of its items
            for i in range(self.count()):
                width = self.sectionSize(i) # save current section width
                self.setResizeMode(QtGui.QHeaderView.ResizeToContents) # resize cell
                width = max(width, self.sectionSize(i)) # compare sizes
                self.setResizeMode(QtGui.QHeaderView.Interactive)
                self.resizeSection(i, width)
    
    class SizedLineEdit(QtGui.QLineEdit):
    
        def __init__(self, parent=None, text="sizedlineedit", *args):
            QtGui.QLineEdit.__init__(self, parent, *args)
            self.parent = parent
            self.value = unicode(text)
            self.setReadOnly(True)
    
            self.minimum_characters = 1
            self.min_text_size = 8
    
            sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
            sizePolicy.setHorizontalStretch(1)
            sizePolicy.setVerticalStretch(1)
            sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
            self.setSizePolicy(sizePolicy)
    
            self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
    
            self.font = QtGui.QFont()
            self.font.setPointSize(self.min_text_size)
            self.font_metrics = QtGui.QFontMetrics(self.font)
            self.setMinimumHeight(self.font.pointSize() * 1.1)
            self.setMaximumHeight(self.max_text_size * 1.1)
            self.setFont(self.font)
    
            min_font = QtGui.QFont()
            min_font.setPointSize(self.min_text_size)
            self.min_font_metrics = QtGui.QFontMetrics(min_font)
    
            self.setText(self.value)
            self.scale_text()
    
        def setFont(self, font=None, size=8):
            if isinstance(font, QtGui.QFont):
                self.font = font
    
            size = int(size)
            size = max(size, self.min_text_size)
            size = min(size, self.calculate_max_font())
            self.font.setPointSize(size)
            self.font_metrics = QtGui.QFontMetrics(self.font)
            QtGui.QLineEdit.setFont(self, self.font)
    
        def getFont(self):
            return self.font
    
        def setText(self, text):
            self.value = text
            QtGui.QLineEdit.setText(self, text)
            self.setMinimumWidth(self.get_minimum_width())
            self.scale_text()
    
        def get_minimum_width(self):
            width = self.min_font_metrics.width(self.text())
            s = "0" * self.minimum_characters
            min_calc_width = self.min_font_metrics.width(s)
            return max(width, min_calc_width)
    
        def get_width(self):
            width = self.font_metrics.width(self.text())
            s = "0" * self.minimum_characters
            min_calc_width = self.font_metrics.width(s)
            return max(width, min_calc_width)
    
        def calculate_max_font(self, lower=8, upper=300):
            mid = lower + ((upper - lower) // 2)
            mid_font = QtGui.QFont()
            mid_font.setPointSize(mid)
            metrics = QtGui.QFontMetrics(mid_font)
    
            v_factor = 1
            h_factor = 1
    
            r_width = float(self.rect().width())
            t_width = float(metrics.width(self.text()))
    
            r_height = float(self.rect().height())
            t_height = float(metrics.height())
    
            if r_width > 0 and t_width > 0:
                h_factor = r_width / t_width
    
            if r_height > 0 and t_height > 0:
                v_factor = r_height / t_height
    
            if v_factor > 1.2 and h_factor > 1.2:
                return self.calculate_fit(mid, upper)
            elif v_factor < 1 and h_factor < 1:
                return self.calculate_fit(lower, mid)
    
            factor = min(h_factor, v_factor)
    
            if factor < 1 or factor > 1.2:
                new_size = int(mid * factor)
                if new_size % 2 != 0:
                    new_size -= 1
                return new_size
    
            return upper
    
        # def scale_text(self):
        #     self.setFont(size=self.calculate_fit())
    
        def scale_text(self):  # resize text to fit within space
            h_factor = 1
            v_factor = 1
    
            r_width = float(self.rect().width())
            t_width = float(self.get_width())
    
            r_height = float(self.rect().height())
            t_height = float(self.font_metrics.height())
    
            if t_width > r_width and t_height > r_height:
                self.setFont(size=self.min_text_size)
                return
    
            if r_width > 0 and t_width > 0:
                h_factor = r_width / t_width
    
            if r_height > 0 and t_height > 0:
                v_factor = r_height / t_height
    
            factor = min(h_factor, v_factor)
    
            if factor < 1 or factor > 1.3:
                new_size = int(self.font.pointSize() * factor)
                if new_size % 2 != 0:
                    new_size -= 1
                self.setFont(size=new_size)
    
        def resizeEvent(self, *args, **kwargs):
            QtGui.QLineEdit.resizeEvent(self, *args, **kwargs)
            self.scale_text()
    
    class SizedSpinBox(QtGui.QDoubleSpinBox):
    
        def __init__(self, parent=None):
            QtGui.QDoubleSpinBox.__init__(self, parent=parent)
            self.parent = parent
            self.setReadOnly(True)
    
            self.min_text_size = 8
    
            self.min = float("-inf")
            self.max = float("inf")
    
            self.leading = 0
    
            # regex validation
            float_regex = QtCore.QRegExp("[0-9-.]*")
            float_regex.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
            self.validator = QtGui.QRegExpValidator(float_regex, self)
    
            # size policy
            sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
            sizePolicy.setHorizontalStretch(1)
            sizePolicy.setVerticalStretch(1)
            sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
            self.setSizePolicy(sizePolicy)
    
            # alignment
            self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
    
            # font
            self.font = QtGui.QFont()
            self.font.setPointSize(self.min_text_size)
            self.font_metrics = QtGui.QFontMetrics(self.font)
            self.setMinimumHeight(self.font.pointSize() * 1.1)
            self.setMaximumHeight(self.max_text_size * 1.1)
            self.setFont(self.font)
    
            min_font = QtGui.QFont()
            min_font.setPointSize(self.min_text_size)
            self.min_font_metrics = QtGui.QFontMetrics(min_font)
    
            self.line_edit = self.findChild(QtGui.QLineEdit)
            self.line_edit.installEventFilter(self)
    
            self.setValue(-1)
            self.setDecimals(3)
            self.scale_text()
    
        def setFont(self, font=None, size=8):
            if isinstance(font, QtGui.QFont):
                self.font = font
    
            size = int(size)
            size = max(size, self.min_text_size)
            size = min(size, self.calculate_max_font())
            self.font.setPointSize(size)
    
            self.font_metrics = QtGui.QFontMetrics(self.font)
            QtGui.QDoubleSpinBox.setFont(self, self.font)
    
        def getFont(self):
            return self.font
    
        def setValue(self, value):
            QtGui.QDoubleSpinBox.setValue(self, value)
            self.setMinimumWidth(self.get_minimum_width())
            self.scale_text()
    
        def get_minimum_width(self):
            width = self.min_font_metrics.width(self.text())
            s = "0" * (self.leading + self.decimals() + 1)
            min_calc_width = self.min_font_metrics.width(s)
            return max(width, min_calc_width)
    
        def get_width(self):
            width = self.font_metrics.width(self.text())
            s = "0" * (self.leading + self.decimals() + 1)
            min_calc_width = self.font_metrics.width(s)
            return max(width, min_calc_width)
    
        def calculate_max_font(self, lower=8, upper=300):
            mid = lower + ((upper - lower) // 2)
            mid_font = QtGui.QFont()
            mid_font.setPointSize(mid)
            metrics = QtGui.QFontMetrics(mid_font)
    
            v_factor = 1
            h_factor = 1
    
            r_width = float(self.rect().width())
            t_width = float(metrics.width(self.text()))
    
            r_height = float(self.rect().height())
            t_height = float(metrics.height())
    
            if r_width > 0 and t_width > 0:
                h_factor = r_width / t_width
    
            if r_height > 0 and t_height > 0:
                v_factor = r_height / t_height
    
            if v_factor > 1.2 and h_factor > 1.2:
                return self.calculate_fit(mid, upper)
            elif v_factor < 1 and h_factor < 1:
                return self.calculate_fit(lower, mid)
    
            factor = min(h_factor, v_factor)
    
            if factor < 1 or factor > 1.3:
                new_size = int(mid * factor)
                if new_size % 2 != 0:
                    new_size -= 1
                return new_size
    
            return mid
    
        def scale_text(self):  # resize text to fit within space
            h_factor = 1
            v_factor = 1
    
            r_width = float(self.rect().width())
            t_width = float(self.get_width())
    
            r_height = float(self.rect().height())
            t_height = float(self.font_metrics.height())
    
            if t_width > r_width and t_height > r_height:
                self.setFont(size=self.min_text_size)
                return
    
            if r_width > 0 and t_width > 0:
                h_factor = r_width / t_width
    
            if r_height > 0 and t_height > 0:
                v_factor = r_height / t_height
    
            factor = min(h_factor, v_factor)
    
            if factor < 1 or factor > 1.3:
                new_size = int(self.font.pointSize() * factor)
                if new_size % 2 != 0:
                    new_size -= 1
                self.setFont(size=new_size)
    
        def resizeEvent(self, *args, **kwargs):
            QtGui.QDoubleSpinBox.resizeEvent(self, *args, **kwargs)
            self.scale_text()
    
    def main():
        app = QtGui.QApplication(sys.argv)
        tbl = TestTable()
    
        columns = [u"Column 1", u"Column 2", u"Column 3", u"Column 4"]
        rows = [u"Row 1", u"Row 2", u"Row 3", u"Row 4", u"Row 5", u"Row 6"]
    
        tbl.set_header_labels(columns, "horizontal")
        tbl.set_header_labels(rows, "vertical")
    
        tbl.set_box_column(0, "text", "False")
        tbl.set_box_column(1, "number", -1)
        tbl.set_box_column(2, "text", "True")
        tbl.set_box_column(3, "number", 1)
    
        tbl.show()
        tbl.scale_to_data()
        tbl.clamp_font_table()
        app.exec_()
    
    main()
    

    【讨论】:

      猜你喜欢
      • 2021-03-14
      • 1970-01-01
      • 2013-02-07
      • 1970-01-01
      • 1970-01-01
      • 2012-03-31
      • 1970-01-01
      • 2016-05-07
      相关资源
      最近更新 更多