Search code examples
pythontkintercustomtkinter

Customtkinter: why does this event.widget lose the proper grid information?


In an UI I'm trying to make I want to make an interactive matrix where some data should be entered by the user. Apart from the data value Entries, each column and row has an Entry with a placeholder text where the user should enter the name of the column (e.g. price) and row (e.g. a company). In order to generate the matrix I have this class method

import customtkinter as ctk
from tkinter import messagebox



class AK_Matrix_Frame(ctk.CTkFrame):

    def __init__(self, root):
        
        #initialize the frame class-------------------
        super().__init__(root)
        

        #Set frame properties-------------------------
        self.width = 200
        self.height = 400
        self.grid(row=1,  column=0,  padx=10,  pady=5)


        #Parameters-----------------------------------
        self.m = 2
        self.n = 3

        #initialize and set up reference dict----------
        self.AK_Widget_Matrix_dict ={}
        self.gen_matrix()
        
        #Base Button-----------------------------------
        read_button = ctk.CTkButton(self, text = "Read", command = self.read_matrix)
        read_button.pack(pady=12, padx = 10)

        root.mainloop()


    def gen_matrix(self):
        

        matrix_label = ctk.CTkLabel(self, text = "Anbieter-Kategorien Matrix", font= ('Ariel', 18))
        matrix_label.pack(pady=12, padx = 10)
        
        self.matrix_frame = ctk.CTkFrame(self, width=200, height=200)
        self.matrix_frame.pack( padx=10,  pady=5,  expand=True)
        

        self.AK_Widget_Matrix_dict[(0,0)] = ctk.CTkLabel(self.matrix_frame, text = "A\K", font= ('Ariel', 14))
        self.AK_Widget_Matrix_dict[(0,0)].grid(row = 0,  column = 0,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')
        

        for i in range(self.m):
            
            
            self.AK_Widget_Matrix_dict[(i+1,0)] = ctk.CTkEntry(self.matrix_frame, placeholder_text = "Anbieter{a}".format(a = i+1), font= ('Ariel', 14))
            self.AK_Widget_Matrix_dict[(i+1,0)].grid(row = i+1,  column = 0,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')
            self.AK_Widget_Matrix_dict[(i+1,0)].bind("<Return>", self.replace_matrix_entry_w_label)

            for j in range(self.n):


                if i == 0:
                    self.AK_Widget_Matrix_dict[(0,j+1)] = ctk.CTkEntry(self.matrix_frame, placeholder_text = "Kategorie{k}".format(k = j+1), font= ('Ariel', 14))
                    self.AK_Widget_Matrix_dict[(0,j+1)].grid(row = 0,  column = j+1,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')
                    self.AK_Widget_Matrix_dict[(0,j+1)].bind("<Return>", self.replace_matrix_entry_w_label)

                self.AK_Widget_Matrix_dict[(i+1,j+1)] = ctk.CTkEntry(self.matrix_frame, font= ('Ariel', 14))
                self.AK_Widget_Matrix_dict[(i+1,j+1)].grid(row = i+1,  column = j+1,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')



    def read_matrix(self):

        pass


    def replace_matrix_entry_w_label(self, event):
        print(event.widget.grid_info())
        i = event.widget.grid_info()["row"]
        j = event.widget.grid_info()["column"]

        print(event.widget)

        print("Row, Column:",i,j)

        txt = event.widget.get()

        print("Event widget contains:",txt)

        event.widget.destroy()

        self.AK_Widget_Matrix_dict[(i , j)] = ctk.CTkLabel(self.matrix_frame, text = txt, font= ('Ariel', 14))
        self.AK_Widget_Matrix_dict[(i , j)].grid(row = i,  column = j,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')




AK_Matrix_Frame(ctk.CTk())


The matrix displays without problem and all entries and labels are placed in the correct location. But when the class method self.replace_matrix_entry_w_label is called, the grid information is transmitted falsely.

And this is the output for any fringe column or row entry I enter text and press return:

{'in': <customtkinter.windows.widgets.ctk_entry.CTkEntry object .!ak_matrix_frame.!ctkframe.!ctkentry2>, 'column': 0, 'row': 0, 'columnspan': 1, 'rowspan': 1, 'ipadx': 0, 'ipady': 0, 'padx': 6, 'pady': (2, 3), 'sticky': 'nesw'}
Row, Column: 0 0
Event widget contains: 23def

So the text one writes in is read correctly, but the row and column are wrong (always 0,0 no matter where the widget is located). I had the code almost identically with tkinter instead off customtkinter, and then it worked.

Why is the row and column in grid_info() not correct?

I tried accessing the bound widgets event.widget.grid_info() in order to get row and column position and use that to replace the Entry with a Label. What actually happens is that the row and column values are always 0,0, no matter which entry in the matrix I select. Since the text written in the Entry is actually correct I don't understand where the problem is.


Solution

  • The problem arises because customtkinter's CTkEntry widget internally creates a tkinter Entry as an attribute of itself.

    event.widget does not refer to the CTkEntry that has a row and column number in your grid, but instead refers to the Entry that this CTkEntry contains.

    Your code in the function replace_matrix_entry_w_label asks for the position of Entry in its parent. But the parent is a CTkEntry widget and the only location is (0,0). The code works if you ask for the grid_info() of the master of Entry, that is of CTkEntry. Like this:

    def replace_matrix_entry_w_label(self, event):
       
            i = event.widget.master.grid_info()["row"]
            j = event.widget.master.grid_info()["column"]
    
            print("Row, Column:",i,j)
    
            txt = event.widget.get()
    
            event.widget.master.destroy()
    
            self.AK_Widget_Matrix_dict[(i , j)] = ctk.CTkLabel(self.matrix_frame, text = txt, font= ('Ariel', 14))
            self.AK_Widget_Matrix_dict[(i , j)].grid(row = i,  column = j,  padx = 5,  pady = 5,  sticky='w'+'e'+'n'+'s')
    
    
    

    For the same reason as described above, if you don't destroy event.widget.master you will still have an empty shell of CTkEntry where just the internal Entry is destroyed, but the object still lives on your UI.