阻止文本在 Tkinter 中重叠 canvas

Stop text from overlapping in Tkinter canvas

我正在 tkinter canvas 中创建一个游戏,其中涉及生成文本(1 位或 2 位数字)并且我已经让它工作了,但我不知道如何显示它们他们不重叠。目前我有这个:

import tkinter as tk
from tkinter import font
import random
BOX_SIZE = 300
class Game(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.config(bg = "white")
        self.numBox = tk.Canvas(self, height = BOX_SIZE, width = BOX_SIZE , bg = "white", highlightthickness = 0)
        self.numBox.pack(expand = True)
        self.score = 0
        self.numberSpawn()
    def placeNumber(self, value):
        validSpawn = False
        attempts = 0
        maxAttempt = False
        while not validSpawn and not maxAttempt:
            attempts += 1
            if attempts > 20:
                maxAttempt = True
                attempts = 0
            size = random.choice([24,36,48,72])
            coord = [random.randint(40,BOX_SIZE - 40) for x in range(2)]
            self.numBox.update()
            pxSize = tk.font.Font(size = size, family = "Times New Roman").measure(value)
            if len(str(value)) == 1:
                secondCoords = [coord[0] + pxSize *2.5 , coord[1] + pxSize]
            else:
                secondCoords = [x + pxSize for x in coord]
            if not self.numBox.find_overlapping(*coord, *secondCoords):
                validSpawn = True
        if not maxAttempt:
            newTxt = self.numBox.create_text(*coord, font = ("Times New Roman",size), text = value)
    def numberSpawn(self):
        self.maxNum = random.randint(3,19)
        self.placeNumber(self.maxNum)
        for i in range(random.randint(4, 16)):
            num = random.randint(0, self.maxNum-1)
            self.placeNumber(num)
        
app = Game()
app.mainloop()

value是要显示的数字,BOX_SIZE是canvas的维度。在创建文本之前,我尝试使用 this to stop the text overlapping and 来查找文本的像素大小。尽管如此,文本仍然像这样重叠:

我不确定如何解决这个问题,或者为什么它不能正常工作。感谢任何帮助。

我认为问题在于:

  1. 你放弃得太早了,而且
  2. 当您达到尝试次数上限时,无论是否重叠,您都可以添加文本

您应该大大增加尝试次数(可能是几百次),然后如果次数超过最大值,则不应绘制文本。

我认为更好的策略可能是先绘制文本项,然后使用该方法的 bbox 计算该项实际占用的 space 量。然后,使用它来查找重叠的项目。 just-created项会一直重叠,但如果重叠数大于1,则选择新的随机坐标。

例如,可能是这样的:

def placeNumber(self, value):
    size = random.choice([24,36,48,72])
    coord = [random.randint(40,BOX_SIZE - 40) for x in range(2)]
    newTxt = self.numBox.create_text(*coord, font = ("Times New Roman",size), text = value)

    for i in range(1000):  # 1000 is the maximum number of tries to make
        bbox = self.numBox.bbox(newTxt)
        overlapping = self.numBox.find_overlapping(*bbox)
        if len(overlapping) == 1:
            return

        # compute new coordinate
        coord = [random.randint(40,BOX_SIZE - 40) for x in range(2)]
        self.numBox.coords(newTxt, *coord)

    # delete the text since we couldn't find a space for it.
    self.numBox.delete(newTxt)

当没有太多可用空间时,这两种算法都会变慢 space。当我创建一个包含 100 个数字的 1000x1000 canvas 时,它在不到一秒的时间内以零重叠排列它们。

这里有一个解决方案:

import tkinter as tk
from tkinter import font
import random

def checkOverlap(R1, R2):
    if (R1[0]>=R2[2]) or (R1[2]<=R2[0]) or (R1[3]<=R2[1]) or (R1[1]>=R2[3]):
         return False
    else:
          return True

def go():
    validSpawn = False
    while not validSpawn:
        value = random.randint(1,99)
        size = random.choice([24,36,48,72])
        coord = [random.randint(40,500 - 40) for x in range(2)]
        new_number = canvas.create_text(*coord, font = ("Times New Roman",size),text=value)
        new_box = canvas.bbox(new_number)
        canvas.itemconfigure(new_number, state='hidden')
        validSpawn = True
        for i in canvas.items:
            this_box = canvas.bbox(i)
            if checkOverlap(this_box, new_box):
                validSpawn = False
                break
    canvas.itemconfigure(new_number, state='normal')
    canvas.items.append(new_number)

root = tk.Tk()

canvas = tk.Canvas(root, width = 500, height = 500, bg='white')
canvas.items = []
canvas.pack()
btn = tk.Button(root, text="Go", command=go)
btn.pack()

root.mainloop()

我没有让它尝试弄清楚项目有多大,而是让它绘制它、测量它、隐藏它,然后寻找重叠部分,然后删除它或根据结果。您将需要在其中添加最大尝试次数,否则屏幕上的数字越多,它就会开始变慢。它不应该在函数中间绘制一个框架,这样用户在进行测量时永远不会在那里看到它。

我还让它保存了一个数组,其中包含保存到屏幕上的所有数字,这样我就可以遍历它们和 运行 我自己的重叠函数。这正是我喜欢的方式,您可以返回使用 find_overlapping 它应该仍然有效。