1

I'm using Python 3.8.1 with tkinter version 8.6.

I have a GUI class, Pressureinput, which takes in input for a pressure sensor simulator. I want the entry to be in units of kPa (native units of the sensor) but I also want the user to know what the psi equivalent is. So, when the user updates the kpa value, I want the psi value to update, but I don't want the user to be able to update the psi value manually. I'm using an entry box for both. They start with a default of 242 kPa.

I'm trying to use validate="focusout" to trigger an event after the kpa entry box loses focus.

Here's my code so you can see what I'm trying to do. Basically, if they enter anything that's not a positive, even integer, I want it to automatically round the value in the entry box and then I also want it to update the psi equivalent.

I realize the method I'm using with my pressurevalid function won't work because the entrybox objects, kpa and psi are immutable and it won't change the original objects.

Note that I've set up the StringVar variables psitext and kpatext. Every time I try to use them in my pressurevalid function, however, I get errors saying they don't exist.

Everything else I've tried ends up with errors that won't run, and I think this at least illustrates what I want to do:

import tkinter as tkGUI

#global constants for conversion
global psi2kpa
global kpa2psi
psi2kpa = 6.894757
kpa2psi = 1 / psi2kpa

class Pressureinput(tkGUI.Frame):

    def __init__(self,parent):
            tkGUI.Frame.__init__(self,parent)
            self.parent = parent
            self.initialize()

    def initialize(self):

            kpatext = tkGUI.StringVar()
            psitext = tkGUI.StringVar()

            self.IDlabel = tkGUI.Label(self,text="Sensor ID (hex):")
            self.IDlabel.grid(row=0, column=0)
            self.ID = tkGUI.Entry(self)
            self.ID.insert(0,"AABBCCDD")
            self.ID.grid(row=0, column=1)

            self.kpalabel = tkGUI.Label(self,text="Pressure (kPa):")
            self.kpalabel.grid(row=1, column=0)
            self.kpa = tkGUI.Entry(self)
            self.kpa.insert(0,242)
            self.kpa.grid(row=1, column=1)

            self.psilabel = tkGUI.Label(self,text="Pressure (PSI):")
            self.psilabel.grid(row=2, column=0)
            self.psi = tkGUI.Entry(self, textvariable=psitext)
            self.psi.insert(0,float(self.kpa.get())*kpa2psi)
            self.psi.grid(row=2, column=1)
            self.psi.config(state='disabled') #state = 'normal' to restore

            vpressure = self.register(self.pressurevalid(self.kpa,self.psi))
            self.kpa = tkGUI.Entry(self, textvariable=kpatext, validate="focusout", validatecommand=vpressure)

            self.sendbutton = tkGUI.Button(self,text="Send Transmission",state="disabled",command=self.send_data)
            self.sendbutton.grid(row=9,columnspan=2)

    def pressurevalid(self,kpa,psi):
            if len(kpa.get()) < 1:
                    kpa.delete(0,tkGUI.END)
                    kpa.insert(0,"0");
            elif 2*int(round(float(kpa.get())) / 2) != int(kpa.get()):
                    kpa.delete(0,tkGUI.END)
                    kpa.insert(0,2 * int(round(float(kpa.get()))) / 2)

            psi.config(state='normal')
            psi.delete(0,tkGUI.END)
            psi.insert(0,float(kpa.get())*kpa2psi)
            psi.config(state='disabled')
            return True

    def send_data(self):
            ID = int(self.ID.get(),16)
            pressure = int(self.kpa.get())
            if pressure >= 510:
                    pressure = 255
            else:
                 pressure = int(round(pressure/2))

            sendstring =  str(ID) + "," + str(function_code) + "," + str(pressure)
            print (sendstring)
Izaak van Dongen
  • 2,450
  • 13
  • 23
Trashman
  • 1,424
  • 18
  • 27

1 Answers1

2

Since you are using a StringVar for the entries, you can set up a trace on the variable to call a function whenever the value changes. This will constantly keep the value updated rather than waiting for a focus-out event.

First, you need to convert the variables into attributes of the class rather than making them local variables:

self.kpatext = tkGUI.StringVar()
self.psitext = tkGUI.StringVar()

You'll also have to adjust other places which reference these variables:

self.psi = tkGUI.Entry(..., textvariable=self.psitext, ...)
self.kpa = tkGUI.Entry(..., textvariable=self.kpatext, ...)

Next, set up a trace on self.kpatext right after you create the variables:

self.kpatext.trace("w", self.update_psi)

And finally, write the method self.update_psi. The following code will set the PSI to an empty string if the current value of kPa isn't able to be converted.

def update_psi(self, *args):
    try:
        psi = int(self.kpatext.get())*kpa2psi
        self.psitext.set(psi)
    except Exception as e:
        self.psitext.set("")

For more information on what the arguments to the trace function are, see What are the arguments to Tkinter variable trace method callbacks?. In this example we don't need them, but the function still must accept them.


Note, your code defines self.kpa twice -- once without using textvariable and once with. I don't understand why you're doing that given that the second one is never added to the screen with pack/place/grid. My solution works under the assumption that the original self.kpa is the one that you intend to use.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • If I add the trace, I assume I can remove the validate and validatecommand from my self.kpa definition? (Note: second definition of self.kpa was a lame attempt to circumvent some errors I was getting with the order my object were defined) – Trashman Jan 30 '20 at 16:23
  • @Trashman: yes, you can remove the validate function if you don't want to do any input validation. I would recommend keeping the validation, but use it only for validation -- for example, only allow digits and one decimal point. – Bryan Oakley Jan 30 '20 at 16:27
  • OK, the code works great for updating the psi value when kpa is altered. I modified it some to also change kpa value to a valid value (even integer only) and it works too good. Since it updates as soon as it's changed, I can't type a odd number at all. So, if I want 254 kPa, it will first type 244 then I have to use the cursor to change the middle 4 to 5. Is there anything I can do to alter the trace that will make it work like "focusout" and not correct until the user leaves the box? – Trashman Jan 30 '20 at 16:54
  • So, I've made two functions and am separately calling them now. I have update_psi using the trace as you suggested. I have re-added my vpressure (now as self.vpressure - I think my biggest problem originally was not using the "self" reference enough) function as a validate action on "focusout" - and it appears to work, except, it seems to only work once. The first time I enter data into the box, it fixes it. If I go back to the box again, I can enter anything I want and it won't validate/fix it again. – Trashman Jan 30 '20 at 17:39
  • @Trashman: tkinter will remove a validate function under certain conditions - specifically, when the validate function returns an error. Also, you shouldn't be changing the value within a validate function. – Bryan Oakley Jan 30 '20 at 17:52
  • OK, so that leads me to a conundrum. Trace changes my value too quickly and I can't use validate because I don't just want a check of validation, I want to change the value being validated if it isn't correct. Is there another option? Or a way to take a separate action once validation returns false? – Trashman Jan 30 '20 at 17:55
  • Tried using invalidcommand and created a new function, correctkpa which is called by invalidcommand. I made it so vpressure only checks the value and returns True or False, then attempted to change the value in correctkpa. Unfortunately, same result, so apparently invalidcommand cannot change the original value, either. – Trashman Jan 30 '20 at 18:16
  • @Trashman: the canonical explanation of how validation works is here: http://tcl.tk/man/tcl8.5/TkCmd/entry.htm#M7. It's written for tcl/tk, but it's an easy mental transformation to tkinter. – Bryan Oakley Jan 30 '20 at 18:20
  • Full disclosure: I've worked with Python/tkinter for all of this week. I'm modifying a script from someone else. I know other languages so I've tried to hop right in. To my credit this has been my only sticking point on the changes I've made. Looks like I need to call "after idle {%W config -validate %v}" in my correct_kpa function. I'm not sure where to fit this in in the syntax. Should I be using that within the self.register wrapper? Or do I use the self.register wrapper to pass %v and %W as parameters to correct_kpa and use python after_idle there? Do I send that command as a string? – Trashman Jan 30 '20 at 18:56
  • OK, got it figured out. In my correct_kpa function, I had to add: self.after_idle(lambda: self.kpa.config(validate='focusout')) , this re-enables the validation after it gets automatically disabled by tkinter due to my changing the original value. I think it might also work to do the change itself in the after_idle command, but I got it working this way first. This link helped me: http://stupidpythonideas.blogspot.com/2013/12/tkinter-validation.html – Trashman Jan 31 '20 at 16:10