1

I am a newbie on Python3 and trying tkinter for first time, I recently answered to Solving simple ODE system within tkinter GUI using https://www.geeksforgeeks.org/python-tkinter-validating-entry-widget/ tips.

Now I am banging my head about how the reg_str = mainwindow.register(callback_str) and e4.config(validate ="key", validatecommand =(reg_str, '%P')) works.

I googled and tried searches on stackoverflow but wasnt able to find an answer,

below my minimal reproducible example:

import tkinter as tk
from tkinter import IntVar,StringVar

###############################################################################
def callback_int(input,typez=None):
    # print(typez)
    
    if input.isdigit():
        # print(input)
        return True
                          
    elif input == "":
        # print(input)
        return True
  
    else:
        print(input)
        return False

def callback_str(input, typez=None):
    
    if len(input) >=1  and input[0] == '/':
        return False
    
    if all([s.isdigit() or s =='/' for s in input]) and input.count('/') <= 1:
        # print([s.isdigit() or s =='/' for s in input])
        # print(input)
        return True
                          
    elif all([s.isdigit() or s =='.' for s in input]) and input.count('.') <= 1:
        # print([s.isdigit() or s =='.' for s in input])
        # print(input)
        return True

    else:
        print('no valid input : ',input)
        return False

def mainwindow():

    mainwindow = tk.Tk()
    mainwindow.geometry('350x350')
    
    tk.Label(mainwindow, text="enter parameters below").grid(row=1)
    
    getN = IntVar()
    geti0 = IntVar()
    getr0 = IntVar()
    getbeta = StringVar(mainwindow, value='0')
    getgamma = StringVar(mainwindow, value='0')

    tk.Label(mainwindow, text="N").grid(row=2)
    tk.Label(mainwindow, text="i0").grid(row=3)
    tk.Label(mainwindow, text="r0").grid(row=4)
    tk.Label(mainwindow, text="beta").grid(row=5)
    tk.Label(mainwindow, text="gamma").grid(row=6)
    
    e1 = tk.Entry(mainwindow,textvariable = getN)
    e1.grid(row=2, column=1)
    e2 = tk.Entry(mainwindow,textvariable = geti0)
    e2.grid(row=3, column=1)
    e3 = tk.Entry(mainwindow,textvariable = getr0)
    e3.grid(row=4, column=1)
    e4 = tk.Entry(mainwindow,textvariable = getbeta)
    e4.grid(row=5, column=1)
    e5 = tk.Entry(mainwindow,textvariable = getgamma)
    e5.grid(row=6, column=1)
    
    reg_int = mainwindow.register(callback_int)
    reg_str = mainwindow.register(callback_str)
    
    
    e1.config(validate ="key", validatecommand =(reg_int, '%P'))
    e2.config(validate ="key", validatecommand =(reg_int, '%P'))
    e3.config(validate ="key", validatecommand =(reg_int, '%P'))
    e4.config(validate ="key", validatecommand =(reg_str, '%P'))
    e5.config(validate ="key", validatecommand =(reg_str, '%P'))
    
    solve = tk.Button(mainwindow, text='solve!', command=lambda: [values()]).grid(row=7, column=1, sticky=tk.W, pady=4)
 
    
    def values():
        
        readN = getN.get()
        readi0 = geti0.get()
        readr0 = getr0.get()
        readbeta = eval(getbeta.get(),{"builtins": {}})
        readgamma = eval(getgamma.get(), {"builtins": {}})
        

        
        intN = int(readN)
        inti0 = int(readi0)
        intr0 = int(readr0)
        intbeta = float(readbeta)
        intgamma = float(readgamma)
        
        
        print(intN,'\n',inti0,'\n',intr0,'\n',intbeta,'\n',intgamma)

    
 
    mainwindow.mainloop()
    
mainwindow()

the program opens a widget:

enter image description here

that ask for inputs.

first 3 Entry row are validated by callback_int that allows only int digits while last 2 Entry rows use callback_str that allow both floats like 1.5 or 0.5 and fractions like 1/7 or 5/7.

My question (sorry it took so long) is:

is there a way to pass function arguments to callback like callback(typez = str) so that I could shorten my code using only one callback function that acts differently for my int and string values ? If I use reg_int = mainwindow.register(callback_int(input,typez='popo')) I got

line ...
if input.isdigit():

AttributeError: 'function' object has no attribute 'isdigit'

I am guessin the problem is in :

reg_int = mainwindow.register(callback_int(input,typez='popo')) #AttributeError: 'function' object has no attribute 'isdigit'
reg_str = mainwindow.register(callback_str('pipi'))

but wasnt able to find anythiong useful , or maybe my approach is in the wrong direction

pippo1980
  • 2,181
  • 3
  • 14
  • 30
  • Try using `reg_int = mainwindow.register(lambda input, typez="popo": callback_int(input, typez=typez))` instead of what you have in your question: `reg_int = mainwindow.register(callback_int(input,typez='popo'))`. – Sylvester Kruin Sep 09 '21 at 17:34
  • thanks a lot, I'll let you know – pippo1980 Sep 09 '21 at 17:36
  • found use of lambda here: https://stackoverflow.com/questions/4140437/interactively-validating-entry-widget-content-in-tkinter – pippo1980 Sep 09 '21 at 17:40

2 Answers2

2

This uses the lambda statement to pass either "str" or "int" to the typez argument in callback(), so that callback() knows what type the input is. Here is the full code based on my first comment above:

import tkinter as tk
from tkinter import IntVar,StringVar

###############################################################################
def callback(input,typez=None):
    
    if typez == "int":
        if input.isdigit():
            # print(input)
            return True
                            
        elif input == "":
            # print(input)
            return True
    
        else:
            print(input)
            return False

    if typez == "str":
        if len(input) >=1  and input[0] == '/':
            return False
        
        if all([s.isdigit() or s =='/' for s in input]) and input.count('/') <= 1:
            # print([s.isdigit() or s =='/' for s in input])
            # print(input)
            return True
                            
        elif all([s.isdigit() or s =='.' for s in input]) and input.count('.') <= 1:
            # print([s.isdigit() or s =='.' for s in input])
            # print(input)
            return True

        else:
            print('no valid input : ',input)
            return False

def mainwindow():

    mainwindow = tk.Tk()
    mainwindow.geometry('350x350')
    
    tk.Label(mainwindow, text="enter parameters below").grid(row=1)
    
    getN = IntVar()
    geti0 = IntVar()
    getr0 = IntVar()
    getbeta = StringVar(mainwindow, value='0')
    getgamma = StringVar(mainwindow, value='0')

    tk.Label(mainwindow, text="N").grid(row=2)
    tk.Label(mainwindow, text="i0").grid(row=3)
    tk.Label(mainwindow, text="r0").grid(row=4)
    tk.Label(mainwindow, text="beta").grid(row=5)
    tk.Label(mainwindow, text="gamma").grid(row=6)
    
    e1 = tk.Entry(mainwindow,textvariable = getN)
    e1.grid(row=2, column=1)
    e2 = tk.Entry(mainwindow,textvariable = geti0)
    e2.grid(row=3, column=1)
    e3 = tk.Entry(mainwindow,textvariable = getr0)
    e3.grid(row=4, column=1)
    e4 = tk.Entry(mainwindow,textvariable = getbeta)
    e4.grid(row=5, column=1)
    e5 = tk.Entry(mainwindow,textvariable = getgamma)
    e5.grid(row=6, column=1)
    
    reg_int = mainwindow.register(lambda input, typez="int": callback(input, typez=typez))
    reg_str = mainwindow.register(lambda input, typez="str": callback(input, typez=typez))
    
    
    e1.config(validate ="key", validatecommand =(reg_int, '%P'))
    e2.config(validate ="key", validatecommand =(reg_int, '%P'))
    e3.config(validate ="key", validatecommand =(reg_int, '%P'))
    e4.config(validate ="key", validatecommand =(reg_str, '%P'))
    e5.config(validate ="key", validatecommand =(reg_str, '%P'))
    
    solve = tk.Button(mainwindow, text='solve!', command=lambda: [values()]).grid(row=7, column=1, sticky=tk.W, pady=4)
 
    
    def values():
        
        readN = getN.get()
        readi0 = geti0.get()
        readr0 = getr0.get()
        readbeta = eval(getbeta.get(),{"builtins": {}})
        readgamma = eval(getgamma.get(), {"builtins": {}})
        

        
        intN = int(readN)
        inti0 = int(readi0)
        intr0 = int(readr0)
        intbeta = float(readbeta)
        intgamma = float(readgamma)
        
        
        print(intN,'\n',inti0,'\n',intr0,'\n',intbeta,'\n',intgamma)

    
 
    mainwindow.mainloop()
    
mainwindow()
Sylvester Kruin
  • 3,294
  • 5
  • 16
  • 39
  • wow that was fast exercise, what happened to the second answer talking about e5.config(validate ="key", validatecommand =(reg_str, '%P')) to e5.config(validate ="key", validatecommand =(reg_str, '%P', arguments)) ??? is it not workable, was good learning to me – pippo1980 Sep 09 '21 at 17:46
  • I'll try in the next days and approve it, thanks – pippo1980 Sep 09 '21 at 17:47
  • @pippo1980: What's wrong with `e5.config(validate ="key", validatecommand =(reg_str, '%P'))`? You just want to know how it works? – Sylvester Kruin Sep 09 '21 at 17:50
  • nope, for a few seconds another answer appeared on my screen talikng about passing args after (reg_str, '%P' , int) wasnt able to catch it entirely (dont know if it was a good or bad answer but was like a different approach from your one, so just wondering what happened to that post. It's -I am finding difficulties to get google leading me to official tkinter documentation explaining this callbacj stuff – pippo1980 Sep 09 '21 at 17:58
  • Here is some official tkinter documentation that I've found useful in my explorations with tkinter. I hope this link still exists! [tkinter tutorial docs](https://tkdocs.com/tutorial/onepage.html) – Sylvester Kruin Sep 09 '21 at 18:01
  • Hi code is fine as long as reg_int = mainwindow.register(lambda input, typez="str": callback(input, typez=typez)) ,reg_str = mainwindow.register(lambda input, typez="int": callback(input, typez=typez)) changes to reg_int = mainwindow.register(lambda input, typez="int": callback(input, typez=typez)) reg_str = mainwindow.register(lambda input, typez="str": callback(input, typez=typez)). Edit queque is Full, just send me a comment after You make the edit and I'll accept your answer, thanks again – pippo1980 Sep 09 '21 at 20:51
  • @pippo1980: Edited the question. Is this what you meant? – Sylvester Kruin Sep 09 '21 at 21:02
  • Yep, sure. Found the other way too, posted as answer, not sure there is more. – pippo1980 Sep 09 '21 at 21:05
0

Hi thanks to the mysterious poster that deleted a very well explained answer (Bryan Oakley) I was able to provide another way to solve my problem . Here is the code:

import tkinter as tk
from tkinter import IntVar,StringVar

###############################################################################

def callback(input,typez=None):
    
    if typez == "int":
        if input.isdigit():
            # print(input)
            return True
                            
        elif input == "":
            # print(input)
            return True
    
        else:
            print(input)
            return False

    if typez == "str":
        if len(input) >=1  and input[0] == '/':
            return False
        
        if all([s.isdigit() or s =='/' for s in input]) and input.count('/') <= 1:
            # print([s.isdigit() or s =='/' for s in input])
            # print(input)
            return True
                            
        elif all([s.isdigit() or s =='.' for s in input]) and input.count('.') <= 1:
            # print([s.isdigit() or s =='.' for s in input])
            # print(input)
            return True

        else:
            print('no valid input : ',input)
            return False

def mainwindow():

    mainwindow = tk.Tk()
    mainwindow.geometry('350x350')
    
    tk.Label(mainwindow, text="enter parameters below").grid(row=1)
    
    getN = IntVar()
    geti0 = IntVar()
    getr0 = IntVar()
    getbeta = StringVar(mainwindow, value='0')
    getgamma = StringVar(mainwindow, value='0')

    tk.Label(mainwindow, text="N").grid(row=2)
    tk.Label(mainwindow, text="i0").grid(row=3)
    tk.Label(mainwindow, text="r0").grid(row=4)
    tk.Label(mainwindow, text="beta").grid(row=5)
    tk.Label(mainwindow, text="gamma").grid(row=6)
    
    e1 = tk.Entry(mainwindow,textvariable = getN)
    e1.grid(row=2, column=1)
    e2 = tk.Entry(mainwindow,textvariable = geti0)
    e2.grid(row=3, column=1)
    e3 = tk.Entry(mainwindow,textvariable = getr0)
    e3.grid(row=4, column=1)
    e4 = tk.Entry(mainwindow,textvariable = getbeta)
    e4.grid(row=5, column=1)
    e5 = tk.Entry(mainwindow,textvariable = getgamma)
    e5.grid(row=6, column=1)
    
    reg_int = mainwindow.register(callback) 
    reg_str = mainwindow.register(callback)
    
    
    e1.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
    e2.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
    e3.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
    e4.config(validate ="key", validatecommand =(reg_str, '%P', 'str'))
    e5.config(validate ="key", validatecommand =(reg_str, '%P', 'str'))
    
    solve = tk.Button(mainwindow, text='solve!', command=lambda: [values()]).grid(row=7, column=1, sticky=tk.W, pady=4)
 
    
    def values():
        
        readN = getN.get()
        readi0 = geti0.get()
        readr0 = getr0.get()
        readbeta = eval(getbeta.get(),{"builtins": {}})
        readgamma = eval(getgamma.get(), {"builtins": {}})
        

        
        intN = int(readN)
        inti0 = int(readi0)
        intr0 = int(readr0)
        intbeta = float(readbeta)
        intgamma = float(readgamma)
        
        
        print(intN,'\n',inti0,'\n',intr0,'\n',intbeta,'\n',intgamma)

    
 
    mainwindow.mainloop()
    
mainwindow()

It doesnt use lambda, but just passes the arg as in https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/entry-validation.html :

To set up this callback, you would use these two options in the Entry constructor:

self.w = Entry(self, validate='all',validatecommand=(okayCommand, '%d', '%i', '%S'), ... here  my extra args ...)

Like:

e1.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
e2.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
e3.config(validate ="key", validatecommand =(reg_int, '%P', 'int'))
e4.config(validate ="key", validatecommand =(reg_str, '%P', 'str'))
e5.config(validate ="key", validatecommand =(reg_str, '%P', 'str'))
Sylvester Kruin
  • 3,294
  • 5
  • 16
  • 39
pippo1980
  • 2,181
  • 3
  • 14
  • 30