Search code examples
pythontkintergridfocusframe

PYTHON TKINTER set focus to next row instead of next widget


I have frames in a grid with widgets in them. These frames get destroyed and new ones are created. I want to move between these with keyboard. The Problem: every time there is a new frame it gets treated by focus next as the last one. The Question: how to focus by row instead.

    def create_a_line(i):
        p=1 #sometimes there are more widgets in a row, but here just take 1
        bframe = Frame(root)
        bframe.grid(row=i, column=0)
        m = Button(bframe, text=h[i], , takefocus = 1)
        m.bind('<Right>', lambda event, i=i: focus_next(i, p))
        m.bind('<Double-1>', lambda event, i=i: double_click_text(i)) 
        m.grid(row=0, column=p)
        
    def double_click_text(i)
        for widget in root.grid_slaves(row=i):
           widget.destroy()
        #change h[i] i.e. text of the button to something else
        create_a_line(i) #with new text but same row
    
    def focus_next(i, p):  #this is what im struggling with
          event.widget.tk_focusNext().focus() #this is how it is right now
#this is how i would like it to be, but no idea how:
#        if in row=i other widgets with column >p: sometimes there will be more than one widget in a row
#          event.widget.tk_focusNext().focus()
#        else:
#          set_focus(first widget in grid row=i+1)
    
    for i in range(0, 5) #the end number is based on number of lines f.e. 5
        create_a_line(i)

Solution

  • For lack of a better solution this is how I solved it based on comments:

    labellsall=[]
    h = [] 
    def ad_to_labelsall(i,  p , z):
        ag = ["#"+str(i)+"i@", p, z]  #made i more complicated so I dont find random numbers
        labelsall.append(ag)
    
    def del_from_labelsall(i):
        agi = ("#"+str(i)+"i@")
        ngip = [e for e, s in enumerate(labelsall) if agi in s]
        for index in sorted(ngip, reverse=True):
           del labelsall[index]
    
    def create_a_line(i):
        p=1 
        bframe = Frame(root)
        bframe.grid(row=i, column=0)
        m = Button(bframe, text=h[i], , takefocus = 1)
        m.bind('<Right>', lambda event, i=i: focus_next(event, i))
        m.bind('<Double-1>', lambda event, i=i: double_click_text(i)) 
        m.grid(row=0, column=p)
        ad_to_labelsall(i,  p , z=m)
    
    def double_click_text(i)
        del_from_labelsall(i)
        for widget in root.grid_slaves(row=i):
           widget.destroy()
        #some other code change h[i] i.e. text of the button to something else
        create_a_line(i) #with new text but same row
    
    def focus_next(event, i):
        grid_info = event.widget.grid_info()
        p = grid_info["column"]
        agi = ("#"+str(i)+"i@") 
        ngip = [e for e, s in enumerate(labelsall) if agi in s]
        for index in sorted(ngip):
           the_row = labelsall[index]
           if int(the_row[1]) > p:   #check if other widget is in the row before jumping to next row
              the_row[2].focus_set() #if other widget found focus it
              return("break")
           else:
              pass
        i_next = i+1
        try:
          agi_next = ("#"+str(i_next)+"i@")
          ngip_next = [e for e, s in enumerate(labelsall) if agi_next in s]
          labelsall[ngip_next[0]][2].focus_set()
        except:
          agi_next = ("#"+str(i_next+1)+"i@") #sometimes there are rows with no widgets, so just try the next one
          ngip_next = [e for e, s in enumerate(labelsall) if agi_next in s]
          labelsall[ngip_next[0]][2].focus_set()
       return("break")