1

I've created three rows of text widget which contains four columns using 'for' loop as follows. enter image description here

My Goal is to append each and every text widget's column values in different lists like
list1 =[],list2=[],list3=[]and list4=[]

So binding a function with a tab key and using lambda I have appended the value to its specific lists. Which works fine so far. enter image description here

enter image description here



Now the problem is if I entered a wrong value by mistake (example the value in second row second column - 764) it also gets added to the list since 'tab' is binded.

Since I'm creating these lists for some calculation purpose It would definitely ruin my calculation. We cant expect users to enter without mistake.

I want a way to clear the text as well as change its value in the list where it was stored earlier. simply as it get changed in the text widget it must be changed in the list.

MY CODE:

from tkinter import *

list1 = []
list2 = []
list3 = []
list4 = []

def testfunction(var):
    print(var)
    list1.append(var)

def testfunction2(var):
    print(var)
    list2.append(var)

def testfunction3(var):
    print(var)
    list3.append(var)

def testfunction4(var):
    print(var)
    list4.append(var)


root = Tk()

for _ in range(3):
    t = StringVar()
    t1 = IntVar()
    t2 = IntVar()
    t3 = IntVar()

    ent = Entry(root, textvariable=t)
    ent.bind("<Tab>",(lambda event, ent=ent:testfunction(ent.get())))
    ent.grid(row=_, column=0)

    ent2 = Entry(root, textvariable=t1)
    ent2.bind("<Tab>",(lambda event, ent2=ent2:testfunction2(ent2.get())))
    ent2.grid(row=_, column=1)

    Rat = Entry(root, textvariable=t2)
    Rat.bind("<Tab>",(lambda event, Rat=Rat:testfunction3(Rat.get())))
    Rat.grid(row=_, column=2)

    amt = Entry(root, textvariable=t3)
    amt.bind("<Tab>",(lambda event, amt=amt:testfunction4(amt.get())))
    amt.grid(row=_, column=3)


root.mainloop()
Ethan Field
  • 4,646
  • 3
  • 22
  • 43
Sundararajan
  • 544
  • 2
  • 9
  • 25
  • Maybe subclassing `Entry` to give it the desired behavior? – Reblochon Masque Sep 14 '17 at 04:37
  • 1
    This is a fundamentally broken user interface, requiring the user to fill in the fields in a particular order - that is NOT how anybody expects a GUI to work! You need to save all those `textvariable`s in lists, so that you can grab all of them when it's time to perform a calculation, entirely independent of the order in which values were typed into them. – jasonharper Sep 14 '17 at 04:37
  • @jasonharper I understand your point. can you provide me example in this case? how effectively I can make it user friendly? – Sundararajan Sep 14 '17 at 06:54

1 Answers1

4

I suggest that you use tkinter.Entry widgets instead of tkinter.Text. These support a textvariable optional parameter, which can receive a variable, that will then be traceable.

The variable is an instance of the tkinter.Variable class. More precisely, it will be one of the tk.StringVar class, that extends the former. It will provide us a handle on the content of the entry: it will always be automatically updated, so as to hold the value of the current input.

So here I go: first, I create a matrix of variables, and a matrix of entries. For the sake of simplicity and clarity, I chose to present you a verbose and explicit version, rather than nested list comprehensions.

import tkinter as tk

root = tk.Tk()

variables = []
for i in range(3):
    row = []
    for j in range(4):
        variable = tk.StringVar()
        row.append(variable)    
    variables.append(row)

entries = []
for i in range(3):
    row = []
    for j in range(4):
        entry = tk.Entry(root, textvariable=variables[i][j])
        entry.grid(row=i, column=j)
        row.append(entry)
    entries.append(row)

At this point, we could call root.mainloop(), and observe the same grid of entries as you produced.

Now, let's define a get_values function: it will help us fetch the inputs of the entries. The value that a tkinter.Variable instance wraps is accessible through the tkinter.Variable.get method.

def get_values():
    values = []
    for j in range(4):
        column = []
        for i in range(3):
            column.append(entries[i][j].get())
        values.append(column)

    return values

Finally, let's create a button, in order to allow us to print the current values:

button = tk.Button(root, text="Print values", command=lambda: print(get_values()))
button.grid(row=3, column=0, columnspan=4)

Now, let's call root.mainloop(), and see what happens. We have a matrix of entries, with a Print values button:

entries matrix

Now if we input some text and then hit Print values:

Inputing values

Here is what comes out in the console:

[['hello', '', ''], ['', 'stuff', ''], ['', '', 'blah'], ['foo', '', '']]

At every moment, and not only when hitting the button, the get_values function will return us the current content of the entries matrix. As I said, it is automatically kept up-to-date, so you don't have to worry with bindings. The obvious advantage to this, is that a user will not have to type his input in a specific order, and will not be forced to switch entries by pressing tab.


Edit

Now you want to display the result of column 2 times column 3 in the fourth column. Since we already have a handle on the entry variables, it's pretty direct to edit their text. All we have to do is track the event of an entry being edited, through the tk.Variable.trace method. I first define a build_callback function, that takes the two source cells' variables and the target cell's variable as parameters.

def build_trace(source1, source2, target):
    def callback(*_):
        value1 = source1.get()
        value2 = source2.get()
        try:
            value1 = float(value1)
            value2 = float(value2)
            target.set(value1*value2)
        except:
            target.set("")

    return callback

This creates a callback that acquires the values of the two source cells, attempts to convert them to floating-point numbers, and write them into the target's variable. Tkinter's variables are such that if a widget has been provided a variable, then a write access to the latter will result in a visual change on the former. Therefore, target.set(value1*value2) will not only set the internal value of the target variable, but also make the associated entry to display the new value.

Now, we need to tell tkinter to call the right callback whenever one of the cells is modified. This is quite easy, but we need to be careful about the indices:

for i in range(3):
    variables[i][1].trace(
        'w',
        build_trace(variables[i][1], variables[i][2], variables[i][3]))
    variables[i][2].trace(
        'w',
        build_trace(variables[i][1], variables[i][2], variables[i][3]))

So what does this mean? This tells tkinter that whenever variables[i][1] is written ('w'), the callback build_trace(variables[i][1], variables[i][2], variables[i][3]) should be executed. The build_trace function is called, and its result is passed as second argument. This result is the internal callback function. Since callbacks are called with some arguments we don't really care about, I just absorb them by defining callback(*_), which basically means "take all the arguments, and put them in _, ie in the trashbin".

Now you're good to go, any change to the second or third column will result in a change on the fourth column.


A bit of self-advertising, but I think it's worth reading:

Right leg
  • 16,080
  • 7
  • 48
  • 81
  • Thanks for the detailed explanation. It works perfectly for this scenario. Thank you for guiding me in the right direction. – Sundararajan Sep 15 '17 at 09:00
  • what if i want to display the calculation of column 2 x column 3 in column 4? thats the reason I tried this method. is there any alternate for this? – Sundararajan Sep 15 '17 at 09:25
  • @Sundararajan Please check my edit, and the posts I linked to as well – Right leg Sep 15 '17 at 09:55
  • Thank you can you provide me some links for understanding trace method and callback. I'm pretty new to these topics. – Sundararajan Sep 16 '17 at 09:51