Tkinter - использовать смещение символов/байтов в качестве индекса для текстового виджета

Я хочу удалить часть содержимого текстового виджета, используя только смещение символов (или, если возможно, байты).

Я знаю, как это сделать для строк, слов и т. д. Просмотрел много документации:

Вот пример мрэ:

import tkinter as tk

root = tk.Tk()

text = tk.Text(root)

txt = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Suspendisse enim lorem, aliquam quis quam sit amet, pharetra porta lectus.
Nam commodo imperdiet sapien, in maximus nibh vestibulum nec.
Quisque rutrum massa eget viverra viverra. Vivamus hendrerit ultricies nibh, ac tincidunt nibh eleifend a. Nulla in dolor consequat, fermentum quam quis, euismod dui.
Nam at gravida nisi. Cras ut varius odio, viverra molestie arcu.

Pellentesque scelerisque eros sit amet sollicitudin venenatis.
Proin fermentum vestibulum risus, quis suscipit velit rutrum id.
Phasellus nisl justo, bibendum non dictum vel, fermentum quis ipsum.
Nunc rutrum nulla quam, ac pretium felis dictum in. Sed ut vestibulum risus, suscipit tempus enim.
Nunc a imperdiet augue.
Nullam iaculis consectetur sodales.
Praesent neque turpis, accumsan ultricies diam in, fermentum semper nibh.
Nullam eget aliquet urna, at interdum odio. Nulla in mi elementum, finibus risus aliquam, sodales ante.
Aenean ut tristique urna, sit amet condimentum quam. Mauris ac mollis nisi.
Proin rhoncus, ex venenatis varius sollicitudin, urna nibh fringilla sapien, eu porttitor felis urna eu mi.
Aliquam aliquam metus non lobortis consequat.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean id orci dui."""

text.insert(tk.INSERT, txt)


def test_delete(event=None):
    text.delete() # change this line here

text.pack(fill = "both", expand=1)
text.pack_propagate(0)
text.bind('<Control-e>', test_delete)
root.mainloop()

Он отображает пример текста внутри переменной внутри текстового виджета. Я использую привязку одной клавиши, чтобы проверить некоторые из возможных способов сделать то, что я хочу, с этим фрагментом текста.

Я пробовал много вещей, как из документации, так и из собственного отчаяния:

  • text.delete(0.X): где X — любое число. Я подумал, что, поскольку строки были 1.0, возможно, использование 0.X будет работать только с символами. Он работает только с одним символом, независимо от того, что такое X (даже с большим числом).
  • text.delete(1.1, 1.3): Это действие на той же строке, потому что я пытался посмотреть, удалит ли он 3 символа в любом направлении на той же строке. Он удаляет 2 символа вместо 3, и делает это, пропуская один символ в начале первой строки и удаляя 2 символа после этого.
  • text.delete("end - 9c"): работать только в конце (последняя строка) и опустить 7 символов, начиная с EOF, а затем удалить один символ после этого.
  • text.delete(0.1, 0.2): Ничего не делает. Тот же результат для другой комбинации 0.X, 0.X.

Пример того, чего я пытаюсь достичь:

Использование приведенного выше примера текста заняло бы слишком много времени, поэтому давайте рассмотрим меньшую строку, скажем, «hello world». Теперь предположим, что мы используем индекс, начинающийся с 1 (не имеет значения, но упрощает объяснение), первый символ — «h», а последний — «d». Итак, скажем, я использую диапазон символов, например «2-7», это будет «ello w». Скажем, я хочу сделать "1-8"? -> "hello wo", а теперь начиная с конца, "11-2", "ello world".

Это в основном похоже на то, что делают f.tell() и f.seek(). Я хочу сделать что-то подобное, но используя только содержимое текстового виджета, а затем что-то сделать с этими диапазонами байтов/символов (в приведенном выше примере я их удаляю и т. д.).

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
0
302
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я подражал f.seek и объединил его с text.delete. Кажется, вам в основном не хватало того, что вам нужно учитывать курсор вставки. Смотрите комментарии в коде

def seek_delete(offset, whence):
    if whence == 0: #from the beginning
        start = '1.0'
        end = f'{start} +{offset} chars'
    elif whence == 1:# from insertion cursor
        current = 'insert'
        if offset >= 0:#positive offset
            start = current
            end = f'{start} +{offset} chars'
        else:#negative offset
            start = f'{current} {offset} chars'
            end = current
    elif whence == 2:#from the end
        start = f'end {offset} chars'
        end = 'end'
    text.delete(start, end)

Я тестировал его с разными значениями с этой привязкой:

text.bind('<Control-e>', lambda e:seek_delete(-2,1))

В качестве бонуса вы можете легко подражать f.tell следующим образом:

def tell(event):
    print(text.index('insert'))
start = 1.0 не правильно. Индексы — это строки, а не числа с плавающей запятой. Некоторые числа с плавающей запятой будут работать случайно, но многие будут вести себя не так, как вы ожидаете. Например, индекс 1.2 и 1.20 является одним и тем же символом, когда вы указываете его как число с плавающей запятой, но не когда он указан как строка.
Bryan Oakley 15.11.2022 21:10

@BryanOakley, ааа, понял. Тем не менее, я не принимаю входных данных, это кажется довольно вводящим в заблуждение. Спасибо.

Thingamabobs 15.11.2022 21:14

TL;DR

Вы можете использовать относительный индекс, аналогичный f.tell(), указав начальный индекс, а затем добавляя или удаляя строки или символы. Например, text.delete("1.0", "1.0+11c") ("1.0" плюс 11 символов)

Каноническая документация по индексам текстовых виджетов находится на справочных страницах tcl/tk в разделе под названием Индексы.


text.delete(0.X): где X — любое число. Я думал, что, поскольку строки были 1.0, возможно, использование 0.X будет работать только с символами. Он работает только с одним символом, независимо от того, что такое X (даже с большим числом).

Я не знаю, что вы подразумеваете под «поскольку строки были 1.0». Первая часть индекса — номер строки, вторая — номер символа. Строки начинают считать с 1, символы с нуля. Итак, первый символ виджета — "1.0". Первый символ строки 2 — "2.0" и т. д.

Но да, text.delete с одним индексом удалит только один символ. Это определенное поведение.

text.delete(1.1, 1.3): это действие в той же строке, потому что я пытался посмотреть, удалит ли он 3 символа в любом направлении в той же строке.

Метод удаления задокументирован для удаления от первого индекса до символа перед последним индексом:

"Удалить диапазон символов из текста. Если указаны и index1, и index2, то удалите все символы, начиная с символа, заданного index1, и заканчивая непосредственно перед index2"

text.delete("end - 9c"): работает только в конце (последняя строка) и пропускает 7 символов, начиная с EOF, а затем удаляет один символ после этого.

Да. Опять же, один индекс, присвоенный delete, удалит только один символ.

text.delete(0.1, 0.2): ничего не делает. Тот же результат для других комбинаций 0.X, 0.X.

0.1 — недопустимый индекс. Индекс — это строка, а не число с плавающей запятой, и первое число должно быть 1 или больше. Tkinter должен преобразовать это число в целое число, большее или равное 1. Таким образом, и 0.1, и 0.2 преобразуются в среднее значение "1.0". Как я уже говорил ранее, метод delete останавливается перед вторым индексом, поэтому вы удаляете все до символа "1.0".

Использование приведенного выше примера текста заняло бы слишком много времени, поэтому давайте рассмотрим меньшую строку, скажем, «hello world». Теперь предположим, что мы используем индекс, начинающийся с 1 (не имеет значения, но упрощает объяснение), первый символ — «h», а последний — «d». Итак, скажем, я использую диапазон символов, например «2-7», это будет «ello wo». Скажем, я хочу сделать "1-8"? -> "hello wo", а теперь начиная с конца, "11-2", "ello world".

Если "hello world" начинается с позиции символа "1.0", и вы хотите использовать относительный индекс для удаления символов диапазона, вы можете удалить его с помощью чего-то вроде text.delete("1.0", "1.0+11c") ("1.0" плюс 11 символов)

Ответ принят как подходящий

Основываясь на моем собственном неустанном тестировании и других ответах здесь, мне удалось найти решение.

import tkinter as tk
from tkinter import messagebox  # https://stackoverflow.com/a/29780454/12349101

root = tk.Tk()

main_text = tk.Text(root)

box_text = tk.Text(root, height=1, width=10)
box_text.pack()

txt = """hello world"""

len_txt = len(
    txt)  # get the total length of the text content. Can be replaced by `os.path.getsize` or other alternatives for files

main_text.insert(tk.INSERT, txt)


def offset():
    inputValue = box_text.get("1.0",
                              "end-1c")  # get the input of the text widget without newline (since it's added by default)

    # focusing the other text widget, deleting and re-insert the original text so that the selection/tag is updated (no need to move the mouse to the other widget in this example)
    main_text.focus()
    main_text.delete("1.0", tk.END)
    main_text.insert(tk.INSERT, txt)


    to_do = inputValue.split("-")

    if len(to_do) == 1:  # if length is 1, it probably is a single offset for a single byte/char
        to_do.append(to_do[0])

    if not to_do[0].isdigit() or not to_do[1].isdigit():  # Only integers are supported
        messagebox.showerror("error", "Only integers are supported")
        return  # trick to prevent the failing range to be executed

    if int(to_do[0]) > len_txt or int(to_do[1]) > len_txt:  # total length is the maximum range
        messagebox.showerror("error",
                             "One of the integers in the range seems to be bigger than the total length")
        return  # trick to prevent the failing range to be executed

    if to_do[0] == "0" or to_do[1] == "0":  # since we don't use a 0 index, this isn't needed
        messagebox.showerror("error", "Using zero in this range isn't useful")
        return  # trick to prevent the failing range to be executed

    if int(to_do[0]) > int(to_do[1]):  # This is to support reverse range offset, so 11-2 -> 2-11, etc
        first = int(to_do[1]) - 1
        first = str(first).split("-")[-1:][0]

        second = (int(to_do[0]) - len_txt) - 1
        second = str(second).split("-")[-1:][0]
    else:  # use the offset range normally
        first = int(to_do[0]) - 1
        first = str(first).split("-")[-1:][0]

        second = (int(to_do[1]) - len_txt) - 1
        second = str(second).split("-")[-1:][0]

    print(first, second)
    main_text.tag_add("sel", '1.0 + {}c'.format(first), 'end - {}c'.format(second))


buttonCommit = tk.Button(root, text = "use offset",
                         command=offset)
buttonCommit.pack()
main_text.pack(fill = "both", expand=1)
main_text.pack_propagate(0)
root.mainloop()

Теперь вышеописанное работает, как описано в примере «hello world» в моем посте. Это не 1:1 клон/эмуляция f.tell() или f.seek(), но я чувствую, что это близко.

В приведенном выше примере не используется text.delete, а вместо этого выделяется текст, так что это визуально менее запутанно (по крайней мере, для меня).

Он работает со следующим типом смещения:

  • обратный диапазон: 11-2 -> 2-11, поэтому порядок не имеет значения
  • нормальный диапазон: 2-11, 1-8, 8-10...
  • одиночное смещение: 10 или 10-10, чтобы он мог поддерживать один символ/байт

Теперь главное, что я заметил, это то, что '1.0 + {}c', 'end - {}c', где {} — диапазон, работает, опуская заданный диапазон.

Если бы вы использовали 1-3 в качестве диапазона строки hello world, было бы выбрано ello wor. Можно сказать, что он опустил h и ld\n с добавлением новой строки Tkinter (которую мы игнорируем в приведенном выше коде, если только она не является частью переменной общей длины). Правильным смещением (или, по крайней мере, таким, как в примере, который я привел в посте выше) будет 2-9.

P.S: Для этого примера необходимо нажать на кнопку после ввода диапазона смещений.

Какой смысл удалять текст, а потом вставлять его заново? Кроме того, command=lambda: offset() можно более кратко выразить как command=offset

Bryan Oakley 18.11.2022 04:40

Я объяснил это в комментарии, в основном это сделано для того, чтобы пример был более интерактивным. Если бы я просто использовал focus, я бы заметил, что пользователю нужно будет переместить мышь, чтобы фокус (и, следовательно, тег/выделение) работал правильно. И да, я использовал лямбда только потому, что тестировал вещи, вы правы в этом вопросе :) @BryanOakley

Nordine Lotfi 18.11.2022 07:37

Другие вопросы по теме