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:

Now if we input some text and then hit Print 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: