我是小鱼,今天是2022年2月10日。
之前用python在做扫雷游戏时,发现总是拿不准部件大小与字体之间的关系,为此,当时还特地写了一篇BLOG《Tkinter中的标签(Lable)与按钮(Button)的大小问题》,扫雷写完后我有时间慢慢回头再来看这个问题,发现当时的理解有很多是错误的。于是那篇文章我删除了,我准备重新再整理一下。
无论是标签还是按钮,在Tk中都被称为部件。以标签为例,在Tkinter.Lable的类中有width和height这两个属性。文档说明是说这两个值是用来设置标签的宽度与高度的,单位为像素。如果标签中放置的是图片的话,的确是这个样子,但如果标签中是文本的话,这两个值就与文本本身有关了。是文本的基本像素的倍数。文本的基本像素是与字体有关的,与部件应当无关。而字体的大小,粗细,倾斜都会影响字体的基本像素的大小。这才是导致我当时总是拿不准标签与按钮的大小的主要原因。
这里要在引入Tkinter中对字体的封装的类,Tkinter.Font——参见说明文档
tkinter.font --- Tkinter 字体封装
TK将字体封装在类当中,为了方便我们的使用。因此与字体有关的参数都可以从这个类的两个主要方法中获取出来,一个是measure, 另一个是metrics
measure方法是:返回以当前字体格式化时文本将在指定显示上占用的空间量
用法是 ziti.measure('8') 前面的字体是从封装的字体库中实例化的类,后面的8可以是任意内容,即为你想知道的这些内容在显示器上需要占多少个像素的宽度(见下面图片)
metrics有4个属性值,说明文档中有说明,这里我解释一下前面3个 'ascent', 'descent', 'linespace'
说明文档中:ascent -- 基线和最高点之间的距离; descent -- 基线和最低点之间的距离; linespace -- 所需最小垂直间距(在两个该字体的字符间,使得这两个字符在垂直方向上不重叠)
其实linespace = ascent + descent, 那么基线是什么,见下面的图片
上图是我用截屏获得的一个标签,这个标签的bd,padx,pady属性我都设置成了0,标签的width和height我都设置成1。标签上的字体是Arial, 字号是12号,粗体,不倾斜。 图中每个方块即是1个像素,红色方块是我用像素笔后面画上去的,为了方便看空白处的像素数。所谓的基线就是字符的下底边,desent是下底边到部件下底边的距离,这个距离与部件无关,是font的要求(明显的,descent是font.metrics中的属性);相应的ascent就是基线到部件上底边的距离。linespace是desent与ascent的和,当部件的height为1的时候,部件高度(WidgetHeight - WH)就等于linespace。
另外,上图中的字符宽就是measure方法所获得的值,字符的宽与字体粗细是否倾斜都没有关系。
我发现了一个有趣的现象,正常的理解,在字符的宽度方向上并没有像行间距(linespace)这种东西的,应该就是一个字一个字的紧挨着,正常理解部件宽度(WidgetWidth - WW)应该就等于字符宽(font.measure)。但是上图中不是,明显在右边多了一个像素的距离,也就是说WW比measure大了1。而这个值与字体的粗细,倾斜都会有关系。之前我一直拿不准的原因就在这里,不同的字号情况是不同的,而且我一直找不出它的规律来。我直接统计了Arial字体从5号到48号字的所有属性值,可以看下面的代码
# _*_ coding : UTF-8 _*_
# @time: 2022/1/27 16:24
# @file: test_tkLabel.py
# @Author:yyh
# @Software:PyCharm
import tkinter as tk
import tkinter.font as tkf
from utilit import log
class FrameSet:
def __init__(self, fram):
# self.fm = fram
fram['height'] = 80
fram['width'] = 240
fram['bd'] = 3
fram['bg'] = '#808080'
fram['relief'] = 'sunken'
fram.pack(padx=4, pady=0, anchor='w')
fram.pack_propagate(0)
class LabelSet:
def __init__(self, labl, zity):
self.lb = labl
self.lb['text'] = '8'
self.lb['font'] = zity
self.lb['image'] = ''
self.lb['height'] = 1
self.lb['width'] = 1
self.lb['bd'] = 0
self.lb['relief'] = 'solid'
self.lb['padx'] = 0
self.lb['pady'] = 0
self.lbwid = self.lb.winfo_reqwidth()
self.lbhei = self.lb.winfo_reqheight()
self.zihao = zity.measure('tkf')
def update_lb(self):
self.lb.pack()
def log_lb_info(self):
log('宽、高 = {}、{}', format(self.lbwid, self.lbhei))
ck = tk.Tk()
fm = tk.Frame(ck)
lb = tk.Label(fm)
ck.title('测试')
ck.geometry('240x240')
print("字号 字符号 ascent descent linespace 部件宽 部件高 weight slant underline overstrike")
for n in range(5, 49):
for weight in ('normal', 'bold'):
for slant in ('roman', 'italic'):
ziti = tkf.Font(family='Arial', size=n, weight=weight, slant=slant, underline=0, overstrike=0)
frame = FrameSet(fm)
label = LabelSet(lb, ziti)
# label.update_lb()
print(n, ziti.measure('8'), end=' ')
for met in ('ascent', 'descent', 'linespace',):
print(ziti.metrics(met), end=' ')
print(lb.winfo_reqwidth(), lb.winfo_reqheight(), end=' ')
for art in ('weight', 'slant', 'underline', 'overstrike',):
print(ziti.cget(art),end=' ')
print(' ')
ck.mainloop()
我用上述代码自动输出,并且存到excel中。对于粗细,正斜分别输出,每个字号输出4行,上面那张截图即为12号字体的涂红那行的截图,下面第二张图片是12号字体的4个截图
从表格中可以看出来,倾斜与加粗不仅会影响WW,而且还会影响WH。但是它们不会影响字符自身的宽度,即measure。
当时测试时也测试了下划线与删除线,但是我发现那两个符号对WW与WH没有影响,所以后面就不再累赘输出了。
我就不贴上我的excel文件了,如果感兴趣的朋友可以复制我的代码到然后自己输出。从excel文件中可以获取一些信息,如下:
我暂时把部件宽度比字符宽度大的那1个像素称为宽度边距(wisent),对于越是大的字号,这个wisent也会出现越大的现象,并且在同一个字号中,因为粗细、正斜的不同,wisent有可能会出现不同(大部分情况下同一个字号的wisent都一样)
==================分割线==================
下面,再用一张图来说明一下部件的属性中width,height,bd,padx,pady的关系。我还是以Label为例。
图中是bd =1,padx =2, pady =2的样子。当部件属性width和height分别都是1的时候,能获得最基本的部件宽与部件高(WWB, WHB)
整个部件包含内边距(padx,pady)和包含边框(bd)后的总的部件宽与高(WW,WH)的公式应当如下:
WW = width * WWB + 2 * padx + 2 * bd
WH = height * WHB + 2 * pady +2 * bd
上面已经说了,WHB可以直接从字体中获得,ziti.mertics('linespace')
而WWB却不一定是 ziti.measure('8')的值,所以还是需要通过读取部件宽度的方式来获得
即使用某个字体,然后定义部件的width和height分别为1,bd,padx,pady=0,获得标签
然后,Label.winfo_reqwidth()的方法来获取宽度,如果想要加粗或者倾斜文字的话,最好也获取一下加粗与倾斜后的 winfo_reqwidth 的值来作为WWB
最后,我再有一点补充。之前我非常执着的认为WWB是与字符宽(measure)和字号有一个函数关系的,即 Z = f(X,Y) 这样的函数关系。这也是当时使我一直陷入一个僵局,然后执着的找多元函数回归拟合的方法。但是当我把字体正斜也放入考虑的范畴之后,我就发现,相同的字符宽与字号也能出现不同的WWB。12号字就是一个最好的例子,7号字也有这样的现象。于是才释然,不在执着用计算获得某个字号WWB的值。而是生成部件后从部件信息(winfo)中去获取。