Как выделить текст в текстовом виджете tkinter
Я хочу знать, как изменить стиль определенных слов и выражений на основе определенных шаблонов.
Я использую Tkinter.Text
Виджет, и я не уверен, как это сделать (та же идея подсветки синтаксиса в текстовых редакторах). Я не уверен, даже если это правильный виджет для использования для этой цели.
3 ответа
Это правильный виджет для использования в этих целях. Основная идея заключается в том, что вы назначаете свойства тегам и применяете теги к диапазонам текста в виджете. Вы можете использовать текстовый виджет search
Команда, чтобы найти строки, которые соответствуют вашему шаблону, который вернет вам достаточно информации, примените тег к диапазону, который соответствует.
Пример применения тегов к тексту см. В моем ответе на текстовое поле "Расширенный Tkinter"?, Это не совсем то, что вы хотите сделать, но показывает основную концепцию.
Ниже приведен пример того, как вы можете расширить класс Text, чтобы включить метод для выделения текста, который соответствует шаблону.
В этом коде шаблон должен быть строкой, он не может быть скомпилированным регулярным выражением. Кроме того, шаблон должен соответствовать правилам синтаксиса Tcl для регулярных выражений.
class CustomText(tk.Text):
'''A text widget with a new method, highlight_pattern()
example:
text = CustomText()
text.tag_configure("red", foreground="#ff0000")
text.highlight_pattern("this should be red", "red")
The highlight_pattern method is a simplified python
version of the tcl code at http://wiki.tcl.tk/3246
'''
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
def highlight_pattern(self, pattern, tag, start="1.0", end="end",
regexp=False):
'''Apply the given tag to all text that matches the given pattern
If 'regexp' is set to True, pattern will be treated as a regular
expression according to Tcl's regular expression syntax.
'''
start = self.index(start)
end = self.index(end)
self.mark_set("matchStart", start)
self.mark_set("matchEnd", start)
self.mark_set("searchLimit", end)
count = tk.IntVar()
while True:
index = self.search(pattern, "matchEnd","searchLimit",
count=count, regexp=regexp)
if index == "": break
if count.get() == 0: break # degenerate pattern which matches zero-length strings
self.mark_set("matchStart", index)
self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.tag_add(tag, "matchStart", "matchEnd")
Ответ Брайана Окли очень помог мне в настройке выделения во многих текстовых виджетах. Благодаря им я теперь могу понять, как работает подсветка.
Недостатки
Единственным недостатком, который я обнаружил, была разница между синтаксисом регулярных выражений, используемым tcl/tk, и синтаксисом регулярных выражений Python. Синтаксис регулярных выражений tcl/tk близок к обычному синтаксису регулярных выражений Python, но это не то же самое . Из-за этой проблемы многие из доступных приложений для тестирования регулярных выражений нельзя было использовать для написания регулярных выражений для метода поиска tkinter.
Решение
Я попытался включить стандартную библиотеку регулярных выражений Python в виджет tkinter Text.
import re
import tkinter as tk
...
def search_re(self, pattern):
"""
Uses the python re library to match patterns.
pattern - the pattern to match.
"""
matches = []
text = textwidget.get("1.0", tk.END).splitlines()
for i, line in enumerate(text):
for match in re.finditer(pattern, line):
matches.append((f"{i + 1}.{match.start()}", f"{i + 1}.{match.end()}"))
return matches
возвращаемое значение представляет собой список кортежей, содержащих начальный и конечный индексы совпадений. Пример:
[('1.1', '1.5'), ('1.6', '1.10'), ('3.1', '3.5')]
Теперь эти значения можно использовать для выделения шаблона в текстовом виджете.
Ссылка
Виджет CustomText
Это оболочка для виджета Text tkinter с дополнительными методами выделения и поиска с помощью библиотеки регулярных выражений. Это основано на коде Брайана, спасибо им.
import re
import tkinter as tk
class CustomText(tk.Text):
"""
Wrapper for the tkinter.Text widget with additional methods for
highlighting and matching regular expressions.
highlight_all(pattern, tag) - Highlights all matches of the pattern.
highlight_pattern(pattern, tag) - Cleans all highlights and highlights all matches of the pattern.
clean_highlights(tag) - Removes all highlights of the given tag.
search_re(pattern) - Uses the python re library to match patterns.
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
# sample tag
self.tag_config("match", foreground="red")
def highlight(self, tag, start, end):
self.tag_add(tag, start, end)
def highlight_all(self, pattern, tag):
for match in self.search_re(pattern):
self.highlight(tag, match[0], match[1])
def clean_highlights(self, tag):
self.tag_remove(tag, "1.0", tk.END)
def search_re(self, pattern):
"""
Uses the python re library to match patterns.
Arguments:
pattern - The pattern to match.
Return value:
A list of tuples containing the start and end indices of the matches.
e.g. [("0.4", "5.9"]
"""
matches = []
text = self.get("1.0", tk.END).splitlines()
for i, line in enumerate(text):
for match in re.finditer(pattern, line):
matches.append((f"{i + 1}.{match.start()}", f"{i + 1}.{match.end()}"))
return matches
def highlight_pattern(self, pattern, tag="match"):
"""
Cleans all highlights and highlights all matches of the pattern.
Arguments:
pattern - The pattern to match.
tag - The tag to use for the highlights.
"""
self.clean_highlights(tag)
self.highlight_all(pattern, tag)
Пример использования
В следующем коде используется вышеуказанный класс и показан пример его использования:
import tkinter as tk
root = tk.Tk()
# Example usage
def highlight_text(args):
text.highlight_pattern(r"\bhello\b")
text.highlight_pattern(r"\bworld\b", "match2")
text = CustomText(root)
text.pack()
text.tag_config("match2", foreground="green")
# This is not the best way, but it works.
# instead, see: https://stackoverflow.com/a/40618152/14507110
text.bind("<KeyRelease>", highlight_text)
root.mainloop()
Вы используете tkinter.ttk? Если это так, сначала импортируйте tkinter.ttk, а затем tkinter в свой список операторов импорта. Это сработало для меня. Вот картинка