Search code examples
pythontkintertreeview

only first tag_configure gets executed on click event within treeview widget/tkinter


I've been stuck on this for the last few hours and can't seem to figure out why the code behaves as it does hence asking for any help. Basically I have a treeview widget in tkinter python 3.6 with 60000+ items. I've created a function onTripleClick that 'ideally' should highlight the first clicked item with certain style (using tags) and WHEN next item is tripleClicked, the previously styled item should go back to original state (using another tag) while the newly clicked should assume the styled state. For some reason ONLY the first tag_configure gets executed - IF i comment out the first tag_configure, then the 2nd one gets executed as well - I need them both to get executed when the IF condition is met for the second tag_configure. Any advice is greatly appreciated!

EDIT: As requested - I've included a minimal version of working code, the idea as explained above is to highlight a triple left clicked row with one style(trClicked tag) and when another row is triple clicked, the previously clicked item returns back to original state(normal tag) while the new one assumes the style(trClicked tag). Run code as is, then comment out the first tree.tag_configure() bit on line 9 - and rerun. This time the 2nd tree.tag_configure(){line14} gets executed -- I need them both to be executed simultaneously. Thank You!

from tkinter import *
from tkinter import ttk

def onTripleClick(event):
    global selectedSigName, selectedSigPath, trClickedItemiid, toBeClearedItemiid
    itemSelection = tree.selection()[0] 
    
    trClickedItemiid = tree.focus()
    tree.tag_configure('trClicked'+str(trClickedItemiid), background='light green', foreground='black', font=( 'Helvetica' ,8, 'bold', 'italic')) # font=(family, size, weight, slant, underline, overstrike) 
    # tree.tag_bind('trClicked'+str(trClickedItemiid),'<1>', trClickedItemiid)
    prevTrClicked.append(trClickedItemiid)
    if len(prevTrClicked) > 1:
        toBeClearedItemiid = prevTrClicked.pop(0) # pop the 0th index and pass it to normal
        tree.tag_configure('normal'+str(toBeClearedItemiid), background='pink', foreground='black', font=( 'Courier' ,8, 'normal', 'roman'))
        # tree.tag_bind('normal'+str(toBeClearedItemiid),'<1>', toBeClearedItemiid)


# Create main root object of TK class
root = Tk()
root.title('MyTreeview')
root.geometry("700x500")

# create frame to house treeview AND scrollbar
frame = Frame(root)
frame.pack(pady=5)

tree = ttk.Treeview(frame, height=20, selectmode="browse")
tree.pack(side=LEFT)
tree['columns'] = ("Column1", "Column2", "Column3")

#Format Columns
tree.column("#0", width=10, minwidth=10) # this is where the plus icon will live
tree.column("Column1", anchor=W, width=150)
tree.column("Column2", anchor=W, width=300)
tree.column("Column3", anchor=W, width=120)

# Create headings
tree.heading("#0", text="", anchor=W)
tree.heading("Column1", text="Column1", anchor=W)
tree.heading("Column2", text="Column2", anchor=W)
tree.heading("Column3", text="Column3", anchor=W)

# to be used by TrClicked Function for helping clear older clicked items
global prevTrClicked
prevTrClicked = [] #

rows= [
    ['TopMostParent1', '2ndParent-ColE', 'ColF-3rdParent'],
    ['TopMostParent2', '5thParent-ColE', 'ColF-1stParent'],
    ['TopMostParent3', '4thParent-ColE', 'ColF-2ndParent'],
    ['TopMostParent4', '2ndParent-ColE', 'ColF-3rdParent'],
    ['TopMostParent4', '4thParent-ColE', 'ColF-1stParent'],
    ['TopMostParent6', '3rdParent-ColE', 'ColF-2ndParent'],
    ['TopMostParent5', '3rdParent-ColE', 'ColF-1stParent'],
    ['TopMostParent4', '3rdParent-ColE', 'ColF-3rdParent'],
    ['TopMostParent2', '3rdParent-ColE', 'ColF-3rdParent']
]
count=0
for row in rows:
    tree.insert(parent='', index='end', iid=count, text='', tags=('trClicked'+str(count), 'normal'+str(count)), values=(row[0], row[1], row[2]))
    count+=1


tree.bind("<Triple-1>", onTripleClick)
# tree.tag_bind('trClicked'+str(trClickedItemiid),'<1>', trClickedItemiid)
# tree.tag_bind('normal'+str(toBeClearedItemiid),'<1>', toBeClearedItemiid)

root.mainloop()


Solution

  • In your case, the order of the tags in the list matters

    If we change this

    
    tags=('trClicked'+str(count), 'normal'+str(count))
    
    

    to this

    
    tags=('normal'+str(count), 'trClicked'+str(count))
    
    

    After this replacement, the previous elements turn pink. You will see the opposite result: the element that turns pink will no longer turn green.


    Solution

    You are using the same style properties for both tags (background, foreground, font). In this case, a single trClicked tag is sufficient. You will only change the style property values based on the event.

    If you are using one tag ('trClicked'):

    
    tree.tag_configure('trClicked'+str(trClickedItemiid), background='light green', foreground='black', font=( 'Helvetica' ,8, 'bold', 'italic'))
    
    ...
    
    tree.tag_configure('trClicked'+str(toBeClearedItemiid), background='pink', foreground='black', font=( 'Courier' ,8, 'normal', 'roman'))
    
    

    Then, in general, everything works as it should.


    Complete example

    
    from tkinter import *
    from tkinter import ttk
    
    
    # to be used by TrClicked Function for helping clear older clicked items
    global clicked_queue
    clicked_queue = [] # last 3 items
    
    
    def onTripleClick(event):
        global clicked_queue
        # 'trClicked'+iid  - a unique tag for each item
    
        # the iid (string) of the item that currently has focus, or '' if no item has focus 
        current_item_iid = event.widget.focus()
        if not current_item_iid:
            return
        print(event.widget.item(current_item_iid))
        print("current:", current_item_iid) # iid=count   
    
        if clicked_queue:
            # get the previous item, the last one in the queue
            prev_item_iid = clicked_queue[-1]
            print("prev item:", prev_item_iid)
            
            if prev_item_iid == current_item_iid:
                return
            
            tree.tag_configure('trClicked'+prev_item_iid, background='pink',
                               foreground='black', font=( 'Courier' , 8, 'normal', 'roman'))
        
        tree.tag_configure('trClicked'+current_item_iid, background='light green',
                           foreground='black', font=( 'Helvetica' , 8, 'bold', 'italic'))
    
        # add current item to the right side of the queue
        clicked_queue.append(current_item_iid)
        print("new prev item:", current_item_iid)
        
        if len(clicked_queue) == 3:
            # get and remove an element from the left side of the queue
            prev_prev_item_iid = clicked_queue[0]
            clicked_queue.remove(prev_prev_item_iid)
            
            if prev_prev_item_iid == current_item_iid:
                return
            
            # return to the default style
            tree.tag_configure('trClicked'+prev_prev_item_iid, background='white',
                               foreground='black', font=( 'Courier' , 8, 'normal', 'roman'))
    
    
    # Create main root object of TK class
    root = Tk()
    root.title('MyTreeview')
    root.geometry("700x500")
    
    # create a consistent style for the background and font
    style = ttk.Style()
    style.configure('Treeview', background='white', foreground='black', font=( 'Courier' , 8, 'normal', 'roman'))
    
    # create frame to house treeview AND scrollbar
    frame = Frame(root)
    frame.pack(pady=5)
    
    tree = ttk.Treeview(frame, height=20, selectmode="browse")
    tree.pack(side=LEFT)
    tree['columns'] = ("Column1", "Column2", "Column3")
    
    #Format Columns
    tree.column("#0", width=10, minwidth=10) # this is where the plus icon will live
    tree.column("Column1", anchor=W, width=150)
    tree.column("Column2", anchor=W, width=300)
    tree.column("Column3", anchor=W, width=120)
    
    # Create headings
    tree.heading("#0", text="", anchor=W)
    tree.heading("Column1", text="Column1", anchor=W)
    tree.heading("Column2", text="Column2", anchor=W)
    tree.heading("Column3", text="Column3", anchor=W)
    
    
    rows= [
        ['TopMostParent1', '2ndParent-ColE', 'ColF-3rdParent'],
        ['TopMostParent2', '5thParent-ColE', 'ColF-1stParent'],
        ['TopMostParent3', '4thParent-ColE', 'ColF-2ndParent'],
        ['TopMostParent4', '2ndParent-ColE', 'ColF-3rdParent'],
        ['TopMostParent4', '4thParent-ColE', 'ColF-1stParent'],
        ['TopMostParent6', '3rdParent-ColE', 'ColF-2ndParent'],
        ['TopMostParent5', '3rdParent-ColE', 'ColF-1stParent'],
        ['TopMostParent4', '3rdParent-ColE', 'ColF-3rdParent'],
        ['TopMostParent2', '3rdParent-ColE', 'ColF-3rdParent']
    ]
    count = 1  # <- start at 1 or set iid=str(count), otherwise iid=0 will default to "I001"
    for row in rows:
        tree.insert(parent='', index='end', iid=count, text='', tags=('trClicked'+str(count),), values=(row[0], row[1], row[2]))
        count += 1
    
    tree.bind("<Triple-1>", onTripleClick)
    
    root.mainloop()