Python Forum
[Tkinter] define entry via class on a loop
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] define entry via class on a loop
#1
I have defined an entry via the following class:

class Entry(object):

    def __init__(self):#, foo1, foo2, nac, root, row): #*args, **kwargs):
        self.foo1 = tk.StringVar(value='default text')
        self.foo2 = tk.StringVar(value='default text')
        self.nac = tk.IntVar()
        self.root = None
        self.row = None

        self.ck1 = tk.Checkbutton(self.root, variable=self.nac,
                                  command=self.naccheck)
        self.ck1.grid(row=self.row, column=0,  sticky='ne')
        self.ent1 = tk.Entry(self.root, width=20, background='white',
                             textvariable=self.foo1, state='normal')
        self.ent1.grid(row=self.row, column=1,  sticky='ns')
        self.ent2 = tk.Entry(self.root, width=20, background='white',
                             textvariable=self.foo2, state='disabled')
        self.ent2.grid(row=self.row, column=2,  sticky='nw')
        print(self.row)

    def naccheck(self):
        if self.nac.get() == 0:
            self.ent2.configure(state='disabled')
        else:
            self.ent2.configure(state='normal')

    def get_on_click(self):
        print(self.ent1.get(), self.ent2.get())
now I want to define the entry voer a loop:

import scripts.modules as md
import tkinter as tk

if __name__ == '__main__':
    main_root = tk.Tk()
    width = 300
    height = 40
    main_root.geometry('{}x{}'.format(width, 10 * height))
    main_root.configure(background='cyan')

    top_frame = tk.Frame(main_root, bg='green', padx=10, pady=5, width=width, height=100)
    top_frame.grid(row=0, columnspan=width, sticky='ns')

    input_frame_left = tk.Frame(main_root, bg='red', padx=10, pady=5, width=width,  height=100)
    input_frame_left.grid(row=1, columnspan=width, sticky='ns')
    #
    # print(type(main_root))

    for i in range(10):
        gui_entry = md.Entry()
        gui_entry.root = top_frame
        # gui_entry. = 'aaaaaaaaaa'
        gui_entry.row = i

    button = tk.Button(input_frame_left, text="click me", command=gui_entry.get_on_click)
    button.grid(row=10, columnspan=3)

    main_root.mainloop()
why do the entry appear on different lines? what am I doinig wrong?    
Reply
#2
Let me start by saying that calling your class "Entry" is a bad idea. When I look at the code I will wonder "Is this a you Entry or a tk Entry?". Plus it isn't an Entry. I don't know what it is, but I am sure you can come up with a more descriptive and less confusing name.

That is not the only confusing name. What the heck is foo1, foo2, chk1, ent1 and ent2? Are any of those variable names supposed to mean anything? I don't know what your class is meant to do. Where is the doc string? The variable names for it's parts should provide some clue, but they don't.

I am surprised that the controls appear in the window at all. Actually I am surprised that your program doesn't crash. You are never telling your widgets what window they are in. I guess that tkinter just shoves them in the main window when this happens.

gui_entry is not a tkinter widget. "gui_entry.root = top_frame" does nothing to the windows or any of the widgets that have been created. It isn't the correct order for setting the parent window anyway. You need to specify the parent window when you make the widget.

You also never tell the controls where they are supposed to be. Entry.row is None when you place them in the grid. Again I am surprised this doesn't make the program crash. When you later set Entry.row it does nothing to place the widgets.

I would make Entry a proper widget, subclassing tk.Frame. You would then make it and place it just like regular entry. In the example below I removed a bunch of stuff to make it shorter and focus in on the important details. However, I don't think you are using grid properly, so read up on tkinter geometry managers.
import tkinter as tk

class CheckEntry(tk.Frame):
    '''I am a frame that contains stuff.  I am in need of a better description'''
    def __init__(self, parent, *args, foo1='default_text', foo2='default text', **kwargs):
        bg = parent['bg']
        super().__init__(parent, *args, bg=bg, **kwargs)
        self.foo1 = tk.StringVar(value=foo1)
        self.foo2 = tk.StringVar(value=foo2)
        self.nac = tk.IntVar()
        self.ck1 = tk.Checkbutton(self, bg=bg, variable=self.nac, command=self.naccheck)
        self.ck1.grid(row=0, column=0)
        self.ent1 = tk.Entry(self, width=20, textvariable=self.foo1, state='normal')
        self.ent1.grid(row=0, column=1)
        self.ent2 = tk.Entry(self, width=20, textvariable=self.foo2, state='disabled')
        self.ent2.grid(row=0, column=2)
 
    def naccheck(self):
        '''I am a method that does something.  I need a better doc string and a better name'''
        if self.nac.get() == 0:
            self.ent2.configure(state='disabled')
        else:
            self.ent2.configure(state='normal')
 
    def get_on_click(self):
        '''From my name you would think I got called when the checkbutton was clicked,
        but I have nothing to do with that.  I need a better name too
        '''
        print(self.ent1.get(), self.ent2.get())

if __name__ == '__main__':
    main_root = tk.Tk()
    main_root.configure(background='cyan')
    top_frame = tk.Frame(main_root, bg='green')
    top_frame.pack()
    input_frame_left = tk.Frame(main_root, bg='red', padx=10, pady=5)
    input_frame_left.pack()
    for i in range(10):
        gui_entry = CheckEntry(top_frame, foo1='Hello', foo2=str(i))
        gui_entry.pack()
    button = tk.Button(input_frame_left, text="click me", command=gui_entry.get_on_click)
    button.pack()
    main_root.mainloop()
drSlump and ndc85430 like this post
Reply
#3
first of all thanks for the feedback. I have two questions:
- why are you using
super().__init__(parent, *args, bg=bg, **kwargs)
what does it mean?

The second question require some preface:
I have a dictionary whose labels must be the entry labels and whose values are supposed to be the initial values of the entry. This is the reason I have a loop to create the entry because I loop on the dictionary. I am struggling with the following problem:

The button click me is supposed to launch a simulation whose input parameter comes from the entry, so once I click the button I need to get the current values of the dictionary. Any suggestion?
Reply
#4
What don't you understand about "super().__init__(parent, *args, bg=bg, **kwargs)"? Is this a "I don't understand subclassing" question or a "I don't understand super()" question or a "I don't understand this particular argument" question?

So your Entry class should have been named DictionaryEntry. I am going to assume that you want to use the DictionaryEntry class to display/edit an entry in a dictionary. DictionaryEntry has a label that displays the key name and a tk.Entry that displays/edits the value. When you change the value in the tk.Entry the DictionaryEntry object updates the entry value in the dictionary.

If that is correct, something like this should work.
import tkinter as tk
from tkinter import messagebox

class DictionaryEntry(tk.Frame):
    '''Use me to display/edit a dictionary entry.  Change entry value by typing in the Entry
    control and pressing <Return> or changing focus.
    
    I retain the type of the entry value.  If the initial value is an int, I verify the Entry value
    is an int and display a message if it is not.
    '''
    def __init__(self, parent, dictionary, key, bg=None, **kwargs):
        if bg is None:
            bg = parent['bg'] 
        super().__init__(parent, bg=bg, **kwargs)
        self.dictionary = dictionary
        self.key = key
        value = dictionary[key]
        self.type = type(value)
        tk.Label(self, text=key, width=10).grid(row=0, column=0, sticky='E')
        self.variable = tk.StringVar(self, str(value))
        entry = tk.Entry(self, textvariable=self.variable, width=10)
        entry.grid(row=0, column=1)
        entry.bind('<Return>', self._accept)
        entry.bind('<FocusOut>', self._accept)

    def _accept(self, event):
        '''Accept value change when return is pressed or losing focus'''
        try:
            value = self.type(self.variable.get())
            self.dictionary[self.key] = value
        except ValueError:
            messagebox.showinfo('Value Error', f'{self.variable.get()} is not a {self.type.__name__}')
        self.variable.set(str(self.dictionary[self.value]))           

if __name__ == '__main__':
    dictionary = {'A':1, 'B':2.0, 'C':'string'}

    root = tk.Tk()
    for key in dictionary:
        DictionaryEntry(root, dictionary, key).pack(padx=10, pady=5)
    tk.Button(root, text="click me", command=lambda: print(dictionary)).pack(padx=10, pady=5)
    root.mainloop()
Reply
#5
thanks, this is what I ment to do. However I have noticed that entry.bind('<FocusOut>', self._accept) an error when I run the code. Furthermore, the entries do not update the dictionary
Reply
#6
I should leave that as an exercise for the reader. But since you were so nice.

This code has an error.
        self.variable.set(str(self.dictionary[self.value]))
It should be:
        self.variable.set(str(self.dictionary[self.key]))
Sorry
Reply
#7
Ahhh...so tk.Frame is the parent class...now I got it! right? P.S. the dictionary is still not updated using the entry
Reply
#8
I copied the last full program, changed the one line in _accept() and it works fine for me. I type a value in the Entry followed by <Return> or <Tab>. When I press the "click me" button the dictionary prints out with the updated values.

You do have to either press entry or move the cursor to another entry (bind <Return> or bind <FocusOut>) to _accept the change and put the new value in the dictionary. Changes in the entry are ignored until one of those events occur.
Reply
#9
I have decided for a slightly different approach:

the class creates an object so I can create entry, radio button and so on via instances. Checkbox does not work anymore though. It seems I cannot access the variable self.variable defined in the method create_entry from the method _accept

Here is he code, any suggestion?

class GuiEntry(tk.Frame):
    """Use me to display/edit a dictionary entry.  Change entry value by typing in the Entry
    control and pressing <Return> or changing focus.

    I retain the type of the entry value.  If the initial value is an int, I verify the Entry value
    is an int and display a message if it is not.
    """

    def __init__(self, container, bg=None, **kwargs):
        if bg is None:
            bg = container['bg']
        super().__init__(container, bg=bg, **kwargs)
        self.container = container

    def create_entry(self, key=None, values=[None]*2, row=None):
        self.key = key
        self.values = values
        self.row = row
        self.nac = tk.IntVar()
        self.variable = tk.StringVar(self, value=self.values[-2]), tk.StringVar(self, self.values[-1])

        # self.type_values = type(type_values[i]) for i in range(len(value))

        if isnumber(self.values[-1]):
            if isnumber(self.values[-2]):
                # self.entry_c1 = None
                # self.entry_c2 = None

                tk.Checkbutton(self.container, variable=self.nac, command=lambda: self.naccheck()). \
                    grid(row=self.row, column=0, sticky='ns')

                label = self.values[0] + ' (' + self.values[1] + ') '
                tk.Label(self.container, text=label, padx=10, pady=5).grid(row=self.row, column=1, sticky='nw')

                self.entry_c1 = tk.Entry(self.container, textvariable=self.variable[0], width=10,
                                         state='normal')
                self.entry_c1.grid(row=self.row, column=2)

                self.entry_c2 = tk.Entry(self.container, textvariable=self.variable[1], width=10,
                                         state='disabled')
                self.entry_c2.grid(row=self.row, column=3)

            else:
                # self.variable2 = tk.StringVar(self, value=self.values[-1])
                # self.entry_c3 = None
                tk.Label(self.container, text='SIMULATION PARAMETERS', padx=10, pady=5). \
                    grid(row=self.row, columnspan=4, sticky='ns')
                tk.Label(self.container, text=self.values[-2], padx=10, pady=5). \
                    grid(row=self.row + 1, columnspan=2, sticky='ns')

                self.entry_c3 = tk.Entry(self.container, textvariable=self.values[-1],
                                         width=10, state='normal')
                self.entry_c3.grid(row=self.row + 1, column=2, columnspan=2)

            if hasattr(self, 'entry_c1'):
                self.entry_c1.bind("<KeyRelease>", self._accept)
            if hasattr(self, 'entry_c2'):
                self.entry_c2.bind("<KeyRelease>", self._accept)
            if hasattr(self, 'entry_c3'):
                self.entry_c3.bind("<KeyRelease>", self._accept)

        else:
            tk.Label(self.container, text=self.values[0], padx=10, pady=5).grid(row=self.row, column=1, sticky='ns')
            tk.Label(self.container, text=self.values[1], padx=10, pady=5).grid(row=self.row, column=2, sticky='ns')
            tk.Label(self.container, text=self.values[2], padx=10, pady=5).grid(row=self.row, column=3, sticky='ns')

    def naccheck(self):
        if self.nac.get() == 0:
            # copy value from entry_c1 to entry_c2
            value = self.variable[0].get()
            self.variable[1].set(value)
            # update self.values[-1] as well
            self.values[-1] = value
            # then disable entry_c2
            self.entry_c2.configure(state='disabled')
        else:
            self.entry_c2.configure(state='normal')

    def _accept(self, event):  # finire!! update dictionary
        """Accept value change when return is pressed or losing focus"""
        try:
            if hasattr(self, 'entry_c1'):
                if isnumber(self.entry_c1.get()):
                    self.values[-2] = self.entry_c1.get()
            if hasattr(self, 'entry_c2'):
                if isnumber(self.entry_c2.get()):
                    self.values[-1] = self.entry_c2.get()
        except ValueError:
            pass

    def load_file(self):
        self.input_path = filedialog.askopenfile(filetypes=[('EXE files', '*.exe')]).name

    def load_file_button(self):
        tk.Label(self.container, text='SELECT THERMPROP .EXE FILE', padx=10, pady=5).grid(row=0, columnspan=2,
                                                                                          sticky='ns')
        tk.Label(self.container, text='aa', width=30, padx=10, pady=5).grid(row=1, column=0, sticky='nw')
        tk.Button(self.container, text="Open file", command=lambda: self.load_file()). \
            grid(row=1, column=1, sticky='ne')

    def RadioButton(self, title, label_1, label_2):
        self.title = title
        self.label_1 = label_1
        self.label_2 = label_2
        self.radio_variable = tk.StringVar(value='1')

        tk.Label(self.container, text=self.title, bd=5, anchor='n').grid(row=0, columnspan=2, sticky='ns')

        tk.Radiobutton(self.container, text=self.label_1, value='1', variable=self.radio_variable). \
            grid(row=1, column=0, sticky='ns')
        tk.Radiobutton(self.container, text=self.label_2, value='2', variable=self.radio_variable). \
            grid(row=1, column=1, sticky='ns')


if __name__ == '__main__':

    dictionary = create_input_dictionary()

    root = tk.Tk()
    type_radio_button_frame = tk.Frame(root, padx=10, pady=5)
    input_data_frame = tk.Frame(root, padx=10, pady=5)
    unit_radio_button_frame = tk.Frame(root, padx=10, pady=5)
    load_file_frame = tk.Frame(root, padx=10, pady=5)

    # place the container within the main window using grid
    type_radio_button_frame.grid(row=0, column=0, sticky='ns')
    input_data_frame.grid(row=1, column=0, sticky='ns')
    unit_radio_button_frame.grid(row=2, column=0, sticky='ns')
    load_file_frame.grid(row=3, column=0, sticky='ns')

    gui_entry = GuiEntry(type_radio_button_frame)
    gui_entry.RadioButton('TYPE OF ANALYSIS', 'One-side heating', 'Uniform heating')

    gui_entry = GuiEntry(unit_radio_button_frame)
    gui_entry.RadioButton('OUTPUT UNIT', 'Unit 1', 'Unit 2')

    gui_entry = GuiEntry(input_data_frame)
    row_dict = 0
    for key_dict, values_dict in dictionary.items():

        gui_entry.create_entry(key_dict, values_dict, row_dict)
        dictionary.update({key_dict: gui_entry.values})
        row_dict += 1

    del dictionary['Header geometry']
    del dictionary['Header water']
    gui_entry = GuiEntry(load_file_frame)
    gui_entry.load_file_button()

    root.mainloop()
Reply
#10
I don't understand you comments about the checkbutton and vairables.

The docstring for GuiEntry does not describe what GuiEntry is. Is this because you are lazy or because you cannot come up with a sentence or two that accurately describes a GuiEntry? I'm guessing the prior, but the latter applies. This is not a class, it is a mess. A class should do one thing. If you want to do two different things you should have two classes. If the two different thing share some commonality you put the commonality in a base class.

What does a GuiEntry with 2 Entry widgets have in common with a GuiEntry with 1 Entry widgets and a GuiEntry with 3 labels and a GuiEntry with a RadioButton? Anything? Where is the kitchen sink?

If these things share some substantial commonality, you should make a base class that implements the commonality and subclasses for two entries with a checkbox and a different subclass for one entry and yet another subclass for for radio cluster and another for the file load button (does it have anything in common with the others, or is this the kitchen sink?).

And it appears that GuiEntry is not a tk.Frame anymore. You are putting the widgets in the container, not the GuiEntry. If GuiEntry doesn't need to do Frame things anymore, don't make it a frame.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] Binding Entry box to <Button-3> created in for loop iconit 5 4,967 Apr-22-2020, 05:47 AM
Last Post: iconit
  Transfer Toplevel window entry to root window entry with TKinter HBH 0 4,463 Jan-23-2020, 09:00 PM
Last Post: HBH
  [Tkinter] Setting Binding to Entry created with a loop? p_hobbs 1 2,066 Nov-25-2019, 10:29 AM
Last Post: Larz60+
  [Tkinter] how to get the entry information using Entry.get() ? SamyPyth 2 3,493 Mar-18-2019, 05:36 PM
Last Post: woooee
  [Tkinter] Bringing function out of class into main loop zukochew 1 2,677 Jul-30-2018, 06:43 PM
Last Post: Axel_Erfurt
  How to define a method in a class 1885 2 4,657 Oct-29-2017, 02:00 AM
Last Post: wavic

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020