如何在Tkinter画布中为子字符串添加颜色

4
我正在编写一个程序,涉及在Tkinter画布上的create_text()框中显示文本,而该框需要在循环中显示。每个单词都将被显示,然后由下一个单词替换。有点像闪卡。
我需要给每个单词的中间靠近的一个字母着色,以便当用户阅读单词时,他们的眼睛会聚焦在单词的中心位置。因此,如果len(i)=1,则颜色i[0],如果len(i)>=2且<=5,则颜色i[1],依此类推。这需要使用Canvas完成,并且需要使用
canvas.create_text(text = i[focus_index],fill = 'red') 

结果应该像这样打印

exaMple

(但显然,"m"将被着为红色,而不是大写)

你需要创建自己的方法来实现这个,因为create_text只接受单一颜色作为参数。我建议你寻找在画布上测量文本大小的方法,然后编写一个方法将字符串分解并分别用不同的颜色创建每个部分。 - James Kent
如果只是为了告诉用户要盯着哪里,为什么不在文本后面放一个彩色点呢? - tobias_k
所以这是一个速读程序,有点像模仿Spritz小部件。速读策略涉及将注意力集中在单词的中间,并通过外围信息将整个单词处理到大脑中。我需要用不同颜色的字母呈现具有这种“焦点”的单词,该字母位于单词中间。我避免使用“highlight”一词,因为Tkinter中有一个highlight()方法,它执行其他操作。 - zachcapp
你必须使用画布的原因是什么?你能使用标签或文本小部件吗? - Bryan Oakley
颜色焦点是我的导师提出的一个挑战。我的程序运行良好,但我真的想实现这个挑战,而这也是他希望我们完成的方式。 - zachcapp
2个回答

3
我假设你想要类似于这样的效果?
目前这是我能找到的最接近的东西。它创建了三个文本框,并使用“anchor”属性来使它们保持在正确的位置。但对于非常宽或窄的字母,它效果不太好。它并不完美,但可能是一个开始。
import Tkinter as tk

root = tk.Tk()

c = tk.Canvas(root)
c.pack(expand=1, fill=tk.BOTH)

words = '''I am writing a program that involves displaying some text in a create_text() box on a Tkinter canvas, within a loop. Each word is displayed, then replaced by the next. Sort of like flash cards. I need to color one letter of each word, close to the middle of the word, so that when the user is reading the words their eyes focus on the middle of the word. So if len(i)=1, color i[0], if len(i)>= 2 and <= 5, color i[1], and so on. It needs to be done using the Canvas, and using canvas.create_text(text = i[focus_index],fill = 'red') The result should print like this exaMple (but obviously "m" would be colored red, not be uppercase)'''
words = words.split()

def new_word(i):
    if i == len(words):
        i = 0

    word = words[i]
    middle = (len(word)+1)//2
    c.itemconfigure(t1, text=word[:middle-1]+' ')
    c.itemconfigure(t2, text=word[middle-1:middle])
    c.itemconfigure(t3, text=word[middle:])

    root.after(100, lambda: new_word(i+1))


t1 = c.create_text(200,100,text='', anchor='e', font=("Courier", 25))
t2 = c.create_text(200,100,text='', anchor='e', font=("Courier", 25), fill='red')
t3 = c.create_text(200,100,text='', anchor='w', font=("Courier", 25))
new_word(0)

root.geometry('400x200+200+200')
root.mainloop()

好的,使用Bryan Oakley的评论中的链接,我进一步改进了代码,使它可以适用于任何字体,而不仅仅是等宽字体。该代码将彩色字母的中心保持在同一位置,并在正确的距离处放置单词的前部和后部。

import Tkinter as tk
import tkFont

root = tk.Tk()
c = tk.Canvas(root)
c.pack(expand=1, fill=tk.BOTH)

fn = "Helvetica"
fs = 24
font = tkFont.Font(family=fn, size=fs)

words = '''I am writing a program that involves displaying some text in a create_text() box on a Tkinter canvas, within a loop. Each word is displayed, then replaced by the next. Sort of like flash cards. I need to color one letter of each word, close to the middle of the word, so that when the user is reading the words their eyes focus on the middle of the word. So if len(i)=1, color i[0], if len(i)>= 2 and <= 5, color i[1], and so on. It needs to be done using the Canvas, and using canvas.create_text(text = i[focus_index],fill = 'red') The result should print like this exaMple (but obviously "m" would be colored red, not be uppercase)'''
words = words.split()

def new_word(i):
    if i == len(words):
        i = 0

    word = words[i]
    middle = (len(word)+1)//2

    front = word[:middle-1]
    letter = word[middle-1:middle]
    back = word[middle:]

    c.itemconfigure(t1, text=front)
    c.itemconfigure(t2, text=letter)
    c.itemconfigure(t3, text=back)
    c.coords(t1, 200-font.measure(letter)/2, 100)
    c.coords(t3, 200+font.measure(letter)/2, 100)

    root.after(100, lambda: new_word(i+1))


t1 = c.create_text(200,100,text='', anchor='e', font=font)
t2 = c.create_text(200,100,text='', anchor='c', font=font, fill='red')
t3 = c.create_text(200,100,text='', anchor='w', font=font)
new_word(0)

root.geometry('400x200+200+200')
root.mainloop()

是的,这个代码可以运行,但需要进行一些修改。现在我无法在呈现下一个单词之前删除t1、t2、t3。它们只是堆叠在一起,这是我在程序早期遇到的问题,但通过delete()方法已经解决了。 - zachcapp
这就是为什么我只创建一次它们,然后使用itemconfigure来替换文本而不是创建新的文本框。 - fhdrsdg
好的!很有道理。现在运行得很好。我保证最后一个问题...字体是Courier,所以它是等宽的,但似乎在红色字母的两侧有额外的空间,但当我删除每个子字符串中的' '空格时,它会将开头和结尾与中间字母重叠。如何修复这个问题,使整个单词的间距相等? - zachcapp
啊,有等宽字体确实很有帮助。我已经编辑了我的答案中文本框的位置。我相信这样做非常好。 - fhdrsdg
1
值得一提的是,tkinter有一种方法可以测量给定字体中字符串的渲染宽度,这样您就不必依赖于固定宽度字体。例如,请参见https://dev59.com/5lrUa4cB1Zd3GeqPlqLM#7210681 - Bryan Oakley
@BryanOakley 哦,是的,这很适合将文本框放在正确的位置,谢谢。我已经添加了使用 font.measure 的代码,使其适用于任何字体。 - fhdrsdg

2

在画布文本项中,您无法对单个字符应用格式。您需要为红色字符创建一个不同的项,并进行一些数学计算以将其覆盖在字符串上方。

如果您不必使用画布,则建议使用文本小部件,因为很容易对单个字符应用格式。以下是一个完整的工作示例:

import Tkinter as tk

words = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mi leo, vulputate a consectetur in, congue sit amet elit. Fusce lacinia placerat mi, vitae maximus leo congue sed. Donec non diam dapibus, fringilla risus at, interdum sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. 
'''.strip().split()

class Example(tk.Frame):
   def __init__(self, parent):
      tk.Frame.__init__(self, parent)
      self.text = tk.Text(self, wrap=None, font="Helvetica 24",
                          highlightthickness=0)
      self.text.pack(side="top", fill="x")

      self.text.tag_configure("center", justify="center")
      self.text.tag_configure("red", foreground="red")

      self.show_words(0)

   def show_words(self, index):
      self.show_word(words[index])
      next = index + 1 if index < len(words)-1 else 0
      self.after(200, self.show_words, next)

   def show_word(self, word):
      self.text.configure(state="normal")
      self.text.delete("1.0", "end")
      self.text.insert("1.0", word, "center")
      offset = len(word)/2
      self.text.tag_add("red", "1.0 + %sc" % offset)
      self.text.configure(state="disabled")

if __name__ == "__main__":
   root = tk.Tk()
   Example(root).pack(fill="both", expand=True)
   root.mainloop()

谢谢,我已经尝试过了,确实更容易。对于这个项目,我需要使用画布。 - zachcapp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接