Home > OS >  Tkinter: Simultaneously-scrolling text boxes eventually lose alignment
Tkinter: Simultaneously-scrolling text boxes eventually lose alignment

Time:10-02

I'm trying to implement a simple HEX Viewer by using three Text() boxes which are set to scroll simultaneously.

However, it seems as though there's some kind of "drift" and at some point, the first box loses alignment with the other two. I can't seem to figure out why.

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()

At first, everything is aligned correctly:

enter image description here

However, after a while, there's a visible misalignment between the first column and the other two:

enter image description here

At first I thought that this might be related to line heights, but setting spacing1, spacing2, and spacing3 didn't help. The problem also persists if I replace all non-whitespace characters with a single character such as 'O' (to make sure all characters have the same height).

How can I ensure that all three boxes stay aligned?

CodePudding user response:

Inside _populate_address_area, there is a for loop: for i in range(num_lines 1):. This is the cause of the problem. Using num_lines 1 adds one too many linse to textbox_address. To fix this, there are two options: deleting the 1, or using for i in range(1, num_lines 1):. Either way, textbox_address will have the correct number of lines.

  • Related