tkinter text entry validation - python

I'm trying to validate the entry of text using Python/tkInter
def validate_text():
return False
text = Entry(textframe, validate="focusout", validatecommand=validate_text)
where validate_text is the function - I've tried always returning False and always returning True and there's no difference in the outcome..? Is there a set of arguments in the function that I need to include?
Edit - changed from NONE to focusout...still not working

You should register your validate function.
def validate_text():
return False
textframe.register(validate_text)
text = Entry(textframe, validate="focusout", validatecommand=validate_text)

I think the only thing your missing is an invalidcommand (or invcmd). What are you expecting validatecommand (or vcmd) to do if it returns false? According to the Tk Manual (see below), if vcmd returns False and validate is not set to none then invcmd will be called. The typical command for invcmd is Tkinter.bell, which makes a ding sound. Also note that vcmd and invcmd are very touchy, and will turn validate to 'none' if they encounter an exception, if the widget is changed in anyway inside the vcmd or invcmd functions or if vcmd does not return a valid Tcl boolean. In particular textvariable is notorious for causing issues, and a section in Entry called valdation specifically deals with that.
Here are the relevant portions from Tk Command Entry (same for Spinbox). See below for more references.
Command-Line Name: -validatecommand or -vcmd
Database Name: validateCommand
Database Class: ValidateCommand
Specifies a script to eval when you want to validate the input into the entry widget. Setting it to {} disables this feature (the default). This command must return a valid Tcl boolean value. If it returns 0 (or the valid Tcl boolean equivalent) then it means you reject the new edition and it will not occur and the invalidCommand will be evaluated if it is set. If it returns 1, then the new edition occurs. See Validation below for more information.
Command-Line Name: -invalidcommand or -invcmd
Database Name: invalidCommand
Database Class: InvalidCommand
Specifies a script to eval when validateCommand returns 0. Setting it to {} disables this feature (the default). The best use of this option is to set it to bell. See Validation below for more information.
Have a look at this SO answer, the Tk commands and epydoc-Tkinter for more references.
There are so many duplicates to this question.
Python tkInter Entry fun
Restricting the value in Tkinter Entry widget

focusout means validatecommand will be only be invoked when you take the focus out from the entrywidget.
You could try 'key' for validating while typing.
Tcl manual:
Validation works by setting the validateCommand option to a script which will be evaluated according to the validate option as follows:
none
Default. This means no validation will occur.
focus
validateCommand will be called when the entry receives or loses focus.
focusin
validateCommand will be called when the entry receives focus.
focusout
validateCommand will be called when the entry loses focus.
key
validateCommand will be called when the entry is edited.
all
validateCommand will be called for all above conditions.

"Note that this option [validatecommand] is only used if the validate option is not NONE"
From http://effbot.org/tkinterbook/entry.htm#entry.Entry.config-method

Related

How to capture keypress event in Tkinter?

I have a spinbox that has a values ranges from 0-366, it means it only allowed a numeric data type and a backspace. Whenever a user type a character, it automatically deleted if it not a number type. I'm from a C# background and this is my first attempt in Python language. Here is my code.
def validate(event):
charPress=event.keysym
val=sbDays.get() #previous values
if not charPress.isdigit():
sbDays.config(textvariable=StringVar(windows).set(val))
sbDays=tk.Spinbox(frame,from_=0,to=366,borderwidth=sbBorderWidth)
sbDays.place(relx=initialX,rely=yDistance,relwidth=sbWidth)
sbDays.config(validate='all',validatecommand=(windows.register(validate),'% P'))
sbDays.update()
sbDays.bind('<Key>',validate)
From the code above, when I run it, it returns nothing. Since I'm from C# background. This is what I actually need. This is the C# keypress event
public static void TextBox_KeyPress_NumberBackspace(object sender, KeyPressEventArgs e)
{
char keyChar = e.KeyChar;
if (char.IsNumber(keyChar) || char.IsControl(keyChar))
e.Handled = false;
else
e.Handled = true;
}
tbDays.KeyPress += TextBox_KeyPress_NumberBackspace;
You're not using the validatecommand attribute properly. The function should never directly change the value. The job of the validatecommand function is to return either True or False. If it returns True the input will be allowed, otherwise the input will be rejected.
If it returns anything else besides True or False (including None) or if the function attempts to change the value, the validation function will be disabled.
You can configure tkinter to send the information you need to determine whether the data is valid or not. You should not be relying on the value returned by get(), since that will only return the previous value rather than the value being input from the user.
Since you're only wanting to allow integers, and you want to limit the value to a max of 366, the best solution would be to have tkinter pass in the value if the edit is allowed (using %P), which you can then use to determine whether or not it is valid.
You also should not bind to the <Key> event, it's unnecessary for the validation to function, and in fact would get in the way of it working.
The function would look something like this:
def validate(new_value):
if new_value == "":
# allow blank entry so user doesn't get frustrated
# when trying to delete the first character
return True
try:
value = int(float(new_value))
except ValueError:
return False
return value >= 0 and value <= 366
You should configure the validatecommand like the following (notice there's no space between % and P):
sbDays.configure(validate='all',validatecommand=(root.register(validate), '%P'))
You will probably need to add a little extra code elsewhere to handle the edge case where the user deletes everything in the widget and doesn't enter anything else. For example, you could use something like value=sbDays.get() or 0.

wx: validate() controls in a container stops after first invalid

i am validating user input controls such as textboxes and dropdowns. The problem i have is, that the validation (the containers.Validate()) stops after finding the first invalid control. I expected it to validate all controls, before it returns. I want to display the user all invalid controls at once, not one by one. Is there a way to do so?
(using wx.WS_EX_VALIDATE_RECURSIVELY will propagate to the children, but only if no other control was invalid before)
Edit:
Instead of overwriting Validate() for every control, i decided to have a separate function, which iterates over all children and validates them.
Note: This function will not be called by default OnOK event. To do so, the event handler must be rebound.
def ValidateRecursively(control):
'''Validate this control and its children recursively'''
validator = control.GetValidator()
# no validator -> valid
isValid = validator.Validate(control) if validator else True
for childControl in control.GetChildren():
# only validate enabled controls
if childControl.IsEnabled():
isValid &= ValidateRecursively(childControl)
return isValid
To do this you would need to override Validate() in your dialog and iterate over all controls yourself without stopping at the first invalid one, like the base class implementation does.
Unfortunately you will probably need to duplicate the code here as there is no way to reuse it. On the bright side, you can use it almost as is with just changing the return false lines to remember the error instead and return it at the end.

PyQT5: What's this boolean passed when a menu action is triggered?

So I'm relatively new at Python, and I've been trying to learn PyQt. I wanted to create a menu dynamically based on the contents of a list. I found an example which I adapted and it looked like this:
for someText in myList:
entry = QAction(someText,MainWindow)
self.myMenu.addAction(entry)
entry.triggered.connect(lambda menuItem=someText: self.doStuff(menuItem))
entry.setText(someText)
The menu was created but when a menu item was chosen doStuff() always got passed a value of False. So I changed the above to:
for someText in myList:
entry = QAction(someText,MainWindow)
self.myMenu.addAction(entry)
entry.triggered.connect(lambda bVal, menuItem=someText: self.doStuff(bVal,menuItem))
entry.setText(someText)
and sure enough everything now works as I'd like. I still get the False in bVal which I just ignore.
I've tried looking at the PyQt documentation but the reference section links to the C++ documentation and it's not obvious to me from that what's going on.
I'd like to understand what the boolean value is and why, in my case, it's always False. I've tried changing various things but I haven't managed to find a scenario where it's True.
Thanks
PyQT5.4, Python 3.4.2 on Windows.
The C++ documentation for the triggered signal shouldn't be too hard to understand:
void QAction::​triggered(bool checked = false)
...
If the action is checkable, checked is true if the action is checked, or false if the action is unchecked.
So the signal is emitted with a boolean parameter which indicates the action's 'checked' state, and this parameter overwrote the default value for your menuItem argument.

Tkinter Optionmenu callback not working

For some reason I can't get this optionmenu so call the callback function. Is there some special treatment those widgets require? (The function itself works and I can call it from i.e. a button.)
self.shapemenu=Tkinter.OptionMenu(self.frame,self.shape,"rectangle", "circular", command=self.setshape)
self.shape is a Tkinter.StringVar and obviously setshape is the callback function.
What am I doing wrong here?
The optionmenu is designed to set a value, not perform an action. You can't assign a command to it, and if you do, you will break its default behavior of setting the value -- it uses the command option internally to manage its values .
If you want something to happen when the value changes, add a trace on the StringVar.

Python: How to get an updated Entry text to use in a command binded to it?

Consider the following code:
text = Entry(); text.pack()
def show(e):
print text.get()
text.bind('<Key>', show)
Let's say I put the letters ABC in the Entry, one by one. The output would be:
>>>
>>> A
>>> AB
Note that when pressing A, it prints an empty string. When I press B, it prints A, not AB. If i don't press anything after C, it will never be shown. It seems that the Entry content is only updated after the binded command has returned, so I can't use the actual Entry value in that function.
Is there any way to get an updated Entry value to use inside a binded command?
You could replace the <Key> event with the <KeyRelease> event. That should work.
Here is a list of events: http://infohost.nmt.edu/tcc/help/pubs/tkinter/events.html#event-types
The reason for this has to do with Tk "bindtags". Bindings are associated with tags, and the bindings are processed in tag order. Both widget names and widget classes are tags, and they are processed in that order (widget-specific bindings first, class bindings second).
For that reason, any time you press a key your widget specific binding will fire before the class binding has a chance to modify the widget.
There are many workarounds. The simplest is to bind to <KeyRelease> since the class bindings happen on the key press. There are other solutions that involve either adding or rearranging bind tags, or using the built-in data validation features of the entry widget. Which is best depends on what you're really trying to accomplish.
For more information on the data validation functions, see this question: Interactively validating Entry widget content in tkinter
For a more comprehensive answer, see Tkinter: set StringVar after <Key> event, including the key pressed

Categories

Resources