【发布时间】: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()
【问题讨论】: