Python Forum
[Tkinter] Update variable using tkinter entry methon
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Update variable using tkinter entry methon
#1
Hallo everyone, I have the following code. The goal is to create a GUI that takes the entry values and update vriables value that I want to use lter in another script that I have already tested. At this stage I am simply printing the values from a button (RUN SIMULATION). It works but I have the following issues:
  • when I start the GUI the predefined value in the entry are like .!frame2.!entry
  • to print the entry value I need to call each dictionary item with the method get() (button RUN SIMULATION). Can I save directly the entry values in the dictianary without the need to use such a method in the button?
def create_geometry_input_dictionary(frame, var):
    temp = {'TITLE': ['Min', 'Step', 'Max'],
            'LABEL1': [StringVar(frame, value='25'),
                       StringVar(frame, value=var),
                       StringVar(frame, value=var)],
            'LABEL2': [StringVar(frame, value='19'),
                       StringVar(frame, value=var),
                       StringVar(frame, value=var)],
            'LABEL3': [StringVar(frame, value='0.8'),
                       StringVar(frame, value=var),
                       StringVar(frame, value=var)],
            'LABEL4': [StringVar(frame, value='2'),
                       StringVar(frame, value=var),
                       StringVar(frame, value=var)],
            'LABEL5': [StringVar(frame, value='0.1'),
                       StringVar(frame, value=var),
                       StringVar(frame, value=var)]}
    return temp

def create_fields(frame, fields_dict):
    for ind, key in enumerate(fields_dict):
        lab_temp = Label(frame, text=key)
        lab_temp.grid(row=ind, column=0, sticky=NS, padx=5, pady=5)
        for i, item in enumerate(fields_dict[key]):
            if ind == 0:
                lab_temp = Label(frame, text=item)
                lab_temp.grid(row=ind, column=i + 1, sticky=NS, padx=5, pady=5)
            else:
                val = Entry(frame, textvariable=item, width=15)
                val.grid(row=ind, column=i + 1, sticky="ns", padx=5, pady=5)
                fields_dict[key][i].set(val)

if __name__ == '__main__':
    root = Tk()
    root.title('Model Definition')
    width = 980
    height = 40
    root.geometry('{}x{}'.format(width, 10 * height))

    width_label = 30

    # create all of the main containers
    top_frame = Frame(root, width=width, height=40, pady=3)
    input_frame_left = Frame(root, width=width / 2, height=40, pady=3)
    input_frame_right = Frame(root, width=width / 2, height=40, pady=3)
    frame_file = Frame(root, width=width, height=40, pady=3)  # include file path
    output_frame_left = Frame(root, width=width / 2, height=40, pady=3)
    output_frame_right = Frame(root, width=width / 2, height=40, pady=3)
    button_frame = Frame(root, width=width, height=40, pady=3)

    # place the container within the main window using grid
    top_frame.grid(row=0, columnspan=2, sticky="n")
    input_frame_left.grid(row=1, column=0, sticky="nw")
    input_frame_right.grid(row=1, column=1, sticky="ne")
    frame_file.grid(row=2, columnspan=2, sticky="ns")
    output_frame_left.grid(row=3, column=0, sticky="w")
    output_frame_right.grid(row=3, column=1, sticky="e")
    button_frame.grid(row=4, columnspan=2, sticky="n")

    # create radio button TYPE of ANALYSIS
    label_top_frame = Label(top_frame, text='TYPE OF ANALYSIS', bd=5, anchor=N).grid(row=0, columnspan=2, sticky=N)
    type_analysis = StringVar(value='1')
    Radiobutton(top_frame, text='One-side heating', value='1', variable=type_analysis).grid(row=1, column=0, sticky=N)
    Radiobutton(top_frame, text='Uniform heating', value='2', variable=type_analysis).grid(row=1, column=1, sticky=N)

    lab = Label(input_frame_left, text='GEOMETRY DATA')
    lab.grid(row=0, column=0, sticky=N, padx=5, pady=5)
    lab = Label(input_frame_left, text='Max')
    lab.grid(row=0, column=1, sticky=N, padx=5, pady=5)
    lab = Label(input_frame_left, text='Step')
    lab.grid(row=0, column=2, sticky=N, padx=5, pady=5)
    lab = Label(input_frame_left, text='Min')
    lab.grid(row=0, column=3, sticky=N, padx=5, pady=5)

    dict_input_geometry = create_geometry_input_dictionary(input_frame_left, '0')
    dict_input_water = create_water_input_dictionary(input_frame_right, '0')

    create_fields(input_frame_left, dict_input_geometry)
    create_fields(input_frame_right, dict_input_water)

 input_path = StringVar(value=path_temp)  
    Label(frame_file, text='path').grid(row=0, columnspan=2)
    Label(frame_file, textvariable=input_path).grid(row=1, column=0, sticky='w') #  textvariable=input_path.get() does not work, why?
    Button(frame_file, text='Open file', command=lambda: load_file(input_path)).grid(row=1, column=1)
    Button(frame_file, text='Run simulation', command=lambda: print(dict_input_geometry['LABEL1'][0].get())).grid(row=1, column=2)
   
Reply
#2
This is pretty old, but just in case you haven't found answers.

The reason you are getting .!frame2.!entry in your Entry widgets is because you do this:
                val = Entry(frame, textvariable=item, width=15)
                val.grid(row=ind, column=i + 1, sticky="ns", padx=5, pady=5)
                fields_dict[key][i].set(val)
What is val? val is an entry. Remove the fields_dict[key][i].set(val) to fix the problem.

You do need the get, but you can hide it. I would make a class that hides the details of interacting with the "dictionary".
from tkinter import *

class DoubleEntry(Entry):
    '''A special Entry for GeometryMatrix.
    Verifies that input text is a number.
    Can set a command to call when the value changes.
    Set and get numeric value using "value" property.
    '''
    def __init__(self, *args, command=None, justify='right', **kwargs):
        super().__init__(*args, **kwargs)
        self.var = StringVar(self, '')
        self.config(textvariable=self.var, justify=justify)
        self.bind('<Return>', self._accept)
        self.bind('<FocusOut>', self._accept)
        self.value = 0.0
        self.command = command

    def __str__(self):
        '''Return the text value'''
        return self.var.get()

    def _accept(self, event):
        '''Accept value change when return is pressed of losing focus'''
        try:
            value = float(self.var.get())
        except ValueError:
            value = self._value
        changed = value != self._value
        self.value = value
        if changed and self.command:
            self.command(self)

    @property
    def value(self):
        '''Return the numeric value'''
        return self._value

    @value.setter
    def value(self, value):
        '''Set the numeric value'''
        try:
            self._value = float(value)
        except ValueError:
            pass
        self.var.set(str(self._value))

class GeometryMatrix(Frame):
    '''A matrix like thing of Entry controls'''
    def __init__(self, parent, rows, columns, ewidth=10, padding=1, command=None, **kwargs):
        super().__init__(**kwargs)
        self.rows = len(rows)
        self.columns = len(columns)
        self.command = command

        self.column_headers = [Label(self, text=text) for text in columns]
        for column in range(self.columns):
            self.column_headers[column].grid(row=0, column=column+1, padx=padding, pady=padding)

        self.entries = []
        self.row_headers = [Label(self, text=text) for text in rows]
        for row in range(self.rows):
            self.row_headers[row].grid(row=row+1, column=0, padx=padding, pady=padding)
            for column in range(self.columns):
                entry = DoubleEntry(self, width=ewidth, command=self._value_changed)
                entry.grid(row=row+1, column=column+1, padx=padding, pady=padding)
                self.entries.append(entry)

    def __len__(self):
        '''Return how many cells are in my matrix'''
        return self.rows * self.columns

    def _index(self, row, column=0):
        '''Private function.  Compute index for [row, column]'''
        return row * self.columns + column

    def _value_changed(self, cell):
        '''Private function called when a cell value is edited'''
        if (self.command is not None):
            self.command(self)

    def get(self, row, column):
        '''Return value at [row, column]'''
        return self.entries[self._index(row, column)].value

    def set(self, row, column, value):
        '''Set value at [row, column] = value'''
        self.variables[self._index(row, column)].value = value

    @property
    def values(self):
        '''Get list with all values'''
        return [entry.value for entry in self.entries]

    @values.setter
    def values(self, values):
        '''Set all values from list'''
        for entry, value in zip(self.entries, values):
            entry.value = value

    def __str__(self):
        '''Return a pretty string showing my values'''
        w1 = max((len(x['text']) for x in self.row_headers))
        w2 = max(self.entries[0]['width'], max((len(x['text']) for x in self.column_headers)))
        retval = '[' + ' '*w1 + ', '.join((f'{x["text"]:^{w2}}' for x in self.column_headers))
        for row in range(self.rows):
            vals = [f'{str(entry):>{w2}}' for entry in self.entries[self._index(row):self._index(row+1)]]
            retval += '\n ' + f'{self.row_headers[row]["text"]:>{w1}}' + ', '.join(vals)
        return retval + ']'

if __name__ == '__main__':
    root = Tk()
    input_geometry = GeometryMatrix(
        root,
        rows = [f'LABEL{i+1}' for i in range(5)],
        columns = ['Min', 'Step', 'Max'],
        command=print)
    input_geometry.values = [x**2 for x in range(3*5)]
    input_geometry.pack()

    root.mainloop()
With everything wrapped up in a class it is really easy to add things like converting the input to floats, binding a command to call with the values change, etc. It improved the value dictionary idea so much that I decided to add some new features to the Entry widget so it works better with numbers.
drSlump likes this post
Reply
#3
wow! that's great! I will have a look at it as it looks quite complicate. Could you give me some reference so I can understand what have you done?? :/
Reply
#4
I wrote a class (GeometryMatrix) that makes a bunch of entry widgets and treats them like they are one graphical control that is used to display/enter/edit a list of numbers. Using the class I can write a program that makes a 3x3 matrix of Entry controls with column headers 'A', 'B', and 'C' and row headers '1', '2', '3' with one command. I can also have the GeometryMatrix control call a function when you type a new value into one of the cells. This program prints the sum of values in the matrix.
import tkinter as tk
from geometrymatrix import GeometryMatrix

def sum_the_values(matrix):
    print(sum(matrix.values))

root = tk.Tk()
input_matrix = GeometryMatrix(root, rows=['1', '2', '3'], columns=['A', 'B', 'C'], command=sum_the_values)
input_matrix.pack()
root.mainloop()
The class takes care of setting and getting the values for the entries, and laying out the entries in a grid pattern with column and row headers.

With only a few more lines I can make a program with two of these controls and has them interact. In this example changing a value in the input matrix updates the output matrix to display the squares of the input matrix values.
import tkinter as tk
from geometrymatrix import GeometryMatrix

def square(src, dst):
    dst.values = [value**2 for value in src.values]

root = Tk()
output = GeometryMatrix(root, columns=['A', 'B', 'C'], rows=['1', '2', '3'])
input = GeometryMatrix(root, columns=['A', 'B', 'C'], rows=['1', '2', '3'], command=lambda x:square(x, output))
input.pack()
output.pack()

root.mainloop()
The Entry widget is awkward to use. It doesn't have a command. It doesn't work well with numbers. Getting and setting the value is more complicated than it needs to be. I decided to fix those problems by making an Entry control that is designed to fit into my solution. I gave it a command callback so I can have it notify the matrix control when a cell value is changed. To know when to call the command function I bound the key return event and focus out event to a method. The _accept() method validates the text (makes sure it is a number), gets the numeric value of the text, and calls the command callback function. I gave it a "value" property that gets and sets the numerical value for the control and updates the entry text to show the value.

If you don't like how tkinter (or any Python package) works, you can change it. You could do this with a bunch of convenience functions, but it is often easier and cleaner to modify the behavior of existing code by subclassing. I don't like how the Entry widget works so I wrote a subclass and replaced the parts I don't like. It bugs me that tkinter doesn't have a table class (they do have a tree class that can make table like things) so I created one. If you want to write anything significant in Python you really need to learn how Python classes work.
drSlump likes this post
Reply
#5
Thanks fot the explanation. I will play with it to understand better. Would you suggest me to use tkinter further or to move to Qt to develop my GUI?
Reply
#6
I don't think it matters much. Until you are more comfortable with classes and inheritance I think you would find Qt intimidating.
drSlump likes this post
Reply
#7
could you suggest me some reference focused on this specific topic? I have some ground knowledge of classes a OOP, but clearly not enough to face this example. Thanks
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  pass a variable between tkinter and toplevel windows janeik 10 2,142 Jan-24-2024, 06:44 AM
Last Post: Liliana
  tkinter - update/refresh treeview snakes 5 20,560 Dec-02-2023, 07:05 PM
Last Post: aynous19
  [Tkinter] Making entry global in tkinter with multiprocessing luckyingermany 2 2,284 Jan-21-2022, 03:46 PM
Last Post: deanhystad
  Can't get tkinter database aware cascading comboboxes to update properly dford 6 3,536 Jan-11-2022, 08:37 PM
Last Post: deanhystad
  Tkinter Exit Code based on Entry Widget Nu2Python 6 2,879 Oct-21-2021, 03:01 PM
Last Post: Nu2Python
  Tkinter | entry output. Sap2ch 1 1,951 Sep-25-2021, 12:38 AM
Last Post: Yoriz
  .get() from generated Entry widgets in tkinter snakes 4 4,158 May-03-2021, 11:26 PM
Last Post: snakes
  update text variable on label with keypress knoxvilles_joker 3 4,841 Apr-17-2021, 11:21 PM
Last Post: knoxvilles_joker
  [Tkinter] tkinter.Menu – How to make text-variable? Sir 3 5,546 Mar-10-2021, 04:21 PM
Last Post: Sir
  [Tkinter] tkinter global variable chalao_adda 6 10,855 Nov-14-2020, 05:37 AM
Last Post: chalao_adda

Forum Jump:

User Panel Messages

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