Tkinter: Одновременно прокручивающиеся текстовые поля в конечном итоге теряют выравнивание

#python #tkinter

Вопрос:

Я пытаюсь реализовать простой шестнадцатеричный просмотрщик, используя три Text() поля, которые настроены на одновременную прокрутку.

Однако, похоже, что происходит какой-то «дрейф», и в какой-то момент первая коробка теряет выравнивание с двумя другими. Я, кажется, не могу понять, почему.

 import tkinter as tk
import os

def chunker(seq, size: int):
    return (seq[pos:pos   size] for pos in range(0, len(seq), size))

TAG_JUSTIFY_RIGHT = 'justify_right'

class Application(tk.Frame):
    BYTES_PER_ROW = 16

    def __init__(self, parent = None):
        tk.Frame.__init__(self, parent)
         
        self.textbox_address = tk.Text(self, width = 17, padx = 10, wrap = tk.NONE, bd = 0)
        self.textbox_address.pack(side = tk.LEFT, fill=tk.Y, expand = False)

        self.textbox_address.tag_configure(TAG_JUSTIFY_RIGHT, justify = tk.RIGHT)

        self.textbox_hex = tk.Text(self, width = 47, padx = 10, wrap = tk.NONE, bd = 0)
        self.textbox_hex.pack(side = tk.LEFT, fill = tk.Y, expand = False)

        self.textbox_ascii = tk.Text(self, width = 17, padx = 10, wrap = tk.NONE, bd = 0)
        self.textbox_ascii.pack(side = tk.LEFT, fill = tk.Y, expand = False)

        self.textboxes = [self.textbox_address, self.textbox_hex, self.textbox_ascii]

        self.scrollbar = tk.Scrollbar(self)
        self.scrollbar.pack(side = tk.RIGHT, fill = tk.Y, expand = False)

        self.scrollbar['command'] = self._on_scrollbar
        for textbox in self.textboxes:
            textbox['yscrollcommand'] = self._on_textscroll

        self.pack(fill = tk.BOTH, expand = True, side = tk.RIGHT)
     
    def _on_scrollbar(self, *args) -> None:
        for textbox in self.textboxes:
            textbox.yview(*args)

    def _on_textscroll(self, *args) -> None:
        self.scrollbar.set(*args)
        self._on_scrollbar('moveto', args[0])

    def _populate_address_area(self, num_bytes: int) -> None:
        num_lines = num_bytes // self.BYTES_PER_ROW
        chars_per_byte = 2
        format_pad_len = 8 * chars_per_byte

        for i in range(num_lines   1):
            base_address = format(i * self.BYTES_PER_ROW, 'X').rjust(format_pad_len, '0')
            self.textbox_address.insert(tk.END, f"{base_address}n")

        self.textbox_address.tag_add(TAG_JUSTIFY_RIGHT, 1.0, tk.END)
        self.textbox_address.config(state = tk.DISABLED)

    def populate_hex_view(self, byte_arr: bytes) -> None:
        self._populate_address_area(len(byte_arr))
        
        for chunk in chunker(byte_arr, self.BYTES_PER_ROW):
            hex_format = chunk.hex(" ")
            self.textbox_hex.insert(tk.END, f"{hex_format}n")

            ascii_format = "".join([chr(i) if 32 <= i <= 127 else "." for i in chunk])
            self.textbox_ascii.insert(tk.END, f"{ascii_format}n")

        self.textbox_hex.config(state = tk.DISABLED)
        self.textbox_ascii.config(state = tk.DISABLED)
 
if __name__ == "__main__":
    root = tk.Tk()
    root.title('Hex View')
    app = Application(parent = root)
    app.populate_hex_view(bytearray(os.urandom(0x4000)))
    root.mainloop()

 

Сначала все выровнено правильно:

введите описание изображения здесь

Однако через некоторое время наблюдается видимое несоответствие между первым столбцом и двумя другими:

введите описание изображения здесь

Сначала я подумал, что это может быть связано с высотой линий, но установка spacing1 spacing2 , и spacing3 не помогла. Проблема также сохраняется, если я заменю все символы, не являющиеся пробелами, одним символом, таким как » O » (чтобы убедиться, что все символы имеют одинаковую высоту).

Как я могу гарантировать, что все три поля будут выровнены?

Комментарии:

1. Я провел некоторое тестирование, и textbox_address у 1026 меня есть строки, в то время как у обоих textbox_ascii и textbox.hex есть 1025 строки. Я дам вам знать, если выясню, почему это происходит, но, по крайней мере, есть причина вашей проблемы.

Ответ №1:

Внутри _populate_address_area есть for петля: for i in range(num_lines 1): . Это и есть причина проблемы. Использование num_lines 1 добавляет слишком много линзе textbox_address . Чтобы исправить это, есть два варианта: удалить 1 или использовать for i in range(1, num_lines 1): . В любом случае, textbox_address будет иметь правильное количество строк.

Комментарии:

1. Спасибо! Не могу поверить, что это было так просто. Для полноты картины я добавлю, что мне пришлось настроить num_lines его так, чтобы он содержал дополнительную строку на случай num_bytes , если она не кратна BYTES_PER_ROW .