Nine Tails 1 Newbie Poster

I am trying to create a widget that will display information for a large number of items. This widget needs to have the following properties:

1.) Highlights a line on a left mouse click
2.) Has an accessor function that can be used to retrieve the line number that is currently highlighted
3.) Fields are aligned into columns

The problem is that the Listbox widget does not provide text formatting (which means that it solves 1 and 2 but not 3). I also can’t get away with creating a class of linked Listboxes which would be displayed side by side because I can’t find any way to right justify text in a Listbox consistenty (right justification is needed because dollar amounts look sloppy when left justified).

I came up with the idea of creating a Text widget that would contain lines of labels where each label corresponded to a field. Basically for each line window_create is used to add a Text widget of height one that acts as a line. Each entry is then properly formatted by using window_create on this new line to add each field individually (6 fields in all). This worked fine at first when I tried it on 73 lines, but when I tried it on the full list of 1,444 lines the computer completes the code but does not display anything. How can this be fixed? Just in case seeing the code is helpful I have attached it below (it is a little lengthy):

#Add the services to TSBox line by line
services_file = open(TSHEET_TABBED_FILE, 'r')
position_counter = 0
for service in services_file:
	#Skip header lines
	if position_counter < TSHEET_NUM_HDR_LINES:
		position_counter += 1
		continue

	#Store the service information
	service_fields = service.split('\t')
	services_ListOfLists.append(service_fields)

	#Add a new line to TSBox that shows the information for the service
	line_to_add = Text(TSBox, height=1, width=TSBOX_WIDTH)
	
	#Add each field as an individual label
	label_List = []
	service_fields_List = []
	for tsbox_field in TSBOX_HEADER:
		value = service_fields[TSBOX_POS_IN_TSHEET[tsbox_field]].strip()
		service_fields_List.append(value)

		#Create the label and add it
		label_width = TSBOX_FIELD_WIDTHS[tsbox_field]

		#Truncate the label if it is too long
		if len(value) > label_width:
			value = value[:(label_width + 1)]
		
		try:
			TSBOX_RJUST.index(tsbox_field)
			label_to_add = Label(line_to_add, text=value, justify=RIGHT, bg='white', anchor=W, width=label_width)
		except ValueError:
			#If field is not right justified then it must be left justified
			label_to_add = Label(line_to_add, text=value, justify=LEFT, bg='white', anchor=W, width=label_width)

		label_to_add.bind('<Double-Button-1>', update_selection)
		label_List.append(label_to_add)
		
		line_to_add.window_create(INSERT, window=label_to_add)
	
	labels_ListOfLists.append(label_List)
	services_ListOfLists.append(service_fields_List)
	TSBox.window_create(INSERT, window=line_to_add)

Dani AI

Generated

Short version for : embedding a Text widget per line and six Label widgets inside each line will scale very poorly. Each Label/Text is a real Tk window; creating thousands of them quickly consumes memory and X/Tk resources and can leave the UI unrendered even though the Python loop finishes. The symptom you describe (works at 73 lines, not at 1,444) is exactly what this approach risks. Run the program from a terminal and watch stderr for X/Tk errors to confirm.

Practical alternatives (faster to implement, less fragile):

  • Use ttk.Treeview — built for multi-column lists, selection, and per-column alignment (anchor='e' for right-align). It exposes selection accessors and handles thousands of rows far better than lots of widgets. See the ttk docs for details.

    Example:

    import tkinter as tk
    from tkinter import ttk
    
    root = tk.Tk()
    tree = ttk.Treeview(root, columns=('amt','desc'), show='headings')
    tree.heading('amt', text='Amount', anchor='e')
    tree.column('amt', anchor='e', width=80)
    tree.heading('desc', text='Description')
    tree.insert('', 'end', values=('123.45', 'Service X'))
    tree.pack(fill='both', expand=True)
    root.mainloop()
  • Use a single Text widget with tabs or a monospace font and tags instead of many embedded widgets. Insert one formatted line per record, set tab stops, and use tags to highlight a clicked line. To get the clicked line index:

    def on_click(event):
        idx = text.index('@%d,%d' % (event.x, event.y))
        line = int(idx.split('.')[0])
        text.tag_remove('sel', '1.0', 'end')
        text.tag_add('sel', f'{line}.0', f'{line}.end')

If the dataset will grow very large, consider virtualized rendering (only draw visible rows in a Canvas or repopulate a Treeview on scroll). For right-justified currency, prefer Treeview column anchors or format numbers with rjust on a monospace font. For reference: ttk.Treeview docs (https://docs.python.org/3/library/tkinter.ttk.html) and the Tk Text manual (https://www.tcl.tk/man/tcl8.6/TkCmd/text.htm).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.