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.
Related
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.
I have a QLineEdit in my project.
I want to use the QValidation on lineEdit.
#Create lineEdit
itemValue = QtWidgets.QLineEdit()
#Create валидатор
objValidator = QtGui.QDoubleValidator(self)
#setup range
objValidator.setRange(-10.0, 100.0, 5)
#lineEdit with validation
itemValue.setValidator(objValidator)
But it doesn't work well.I can type what i want, except symbols.
And range doesn't work!I can type 100500 or -100500, but i want, that user can enter numbers only in range.
How i should use range?
I need help:)
Thanks for your help, guys!
By default, a validator will not prevent values outside the range from being entered, and it won't prevent the user leaving the line-edit if the entered value is Invalid or Intermediate.
However, it does give you an opportunity to reject the input programmatically, because whenever the current value is unacceptable, the line-edit won't emit its editingFinished or returnPressed signals, and its hasAcceptableInput method will return False. In addition, if you subclass the validator, you can reimplement its fixup method to control the values that are entered.
However, as has been suggested already, a far better/simpler solution is to use a QDoubleSpinBox, since it cleans up the input automatically and provides a more user-friendly interface.
As an alternative you could use a QDoubleSpinBox.
has validator build in
prevents invalid input while typing
has build-in setRange()
and adds a little handle to change the value for more mouse oriented users.
Probably you are expecting something what should not happen.
In general when you have validator you should be able to type something in intermediate state what doesn't exactly meet limitation. But this should be fixed, when editor looses focus.
Why? Imagine you have 84 an you want correct this to -8.4. Many people will do this like that: add minus so now you have -84 which is not acceptable then add dot. If validator fixes this immediately it would be annoying for user.
So bottom line does this "problem" happen when editor looses focus?
In my Django form I need to perform different field values comparisons (+ some additional checks). If all of these conditions are met I want to raise error and inform user that he can't perform this operation. Right now I'm doing it in clean method and code looks like these:
if self.instance.foo != cleaned_data['foo'] and \
Bar.objects.filter(baz=self.instance.id).count():
cleaned_data['foo'] = self.instance.foo
raise forms.ValidationError("You can't change it")
That's working fine, but I'd also like to reject the change and restore previous value (before the update). The assignment cleaned_data['foo'] = self.instance.foo obviously is not working because cleaned_data is not being returned due to raising ValidationError. How can I achieve this?
It turned out that you can access fields' values via self.data. So I ended up doing self.data['foo'] = self.instance.foo and it's working now.
I have a QLineEdit in my project.
I want to use the QValidation on lineEdit.
#Create lineEdit
itemValue = QtWidgets.QLineEdit()
#Create валидатор
objValidator = QtGui.QDoubleValidator(self)
#setup range
objValidator.setRange(-10.0, 100.0, 5)
#lineEdit with validation
itemValue.setValidator(objValidator)
But it doesn't work well.I can type what i want, except symbols.
And range doesn't work!I can type 100500 or -100500, but i want, that user can enter numbers only in range.
How i should use range?
I need help:)
Thanks for your help, guys!
By default, a validator will not prevent values outside the range from being entered, and it won't prevent the user leaving the line-edit if the entered value is Invalid or Intermediate.
However, it does give you an opportunity to reject the input programmatically, because whenever the current value is unacceptable, the line-edit won't emit its editingFinished or returnPressed signals, and its hasAcceptableInput method will return False. In addition, if you subclass the validator, you can reimplement its fixup method to control the values that are entered.
However, as has been suggested already, a far better/simpler solution is to use a QDoubleSpinBox, since it cleans up the input automatically and provides a more user-friendly interface.
As an alternative you could use a QDoubleSpinBox.
has validator build in
prevents invalid input while typing
has build-in setRange()
and adds a little handle to change the value for more mouse oriented users.
Probably you are expecting something what should not happen.
In general when you have validator you should be able to type something in intermediate state what doesn't exactly meet limitation. But this should be fixed, when editor looses focus.
Why? Imagine you have 84 an you want correct this to -8.4. Many people will do this like that: add minus so now you have -84 which is not acceptable then add dot. If validator fixes this immediately it would be annoying for user.
So bottom line does this "problem" happen when editor looses focus?
This one's a structure design problem, I guess. Back for some advice.
To start: I'm writing a module. Hence the effort of making it as usable to potential developers as possible.
Inside an object (let's call it Swoosh) I have a method which, when called, may result in either success (a new object is returned -- for insight: it's an httplib.HTTPResponse) or failure (surprising, isn't it?).
I'm having trouble deciding how to handle failures. There are two main cases here:
user supplied data that was incorrect
data was okay, but user interaction will be needed () - I need to pass back to the user a string that he or she will need to use in some way.
In (1) I decided to raise ValueError() with an appropriate description.
In (2), as I need to actually pass a str back to the user.. I'm not sure about whether it would be best to just return a string and leave it to the user to check what the function returned (httplib.HTTPResponse or str) or raise a custom exception? Is passing data through raising exceptions a good idea? I don't think I've seen this done anywhere, but on the other hand - I haven't seen much.
What would you, as a developer, expect from an object/function like this?
Or perhaps you find the whole design ridiculous - let me know, I'll happily learn.
As much as I like the approach of handling both cases with specifically-typed exceptions, I'm going to offer a different approach in case it helps: callbacks.
Callbacks tend to work better if you're already using an asynchronous framework like Twisted, but that's not their only place. So you might have a method that takes a function for each outcome, like this:
def do_request(on_success, on_interaction_needed, on_failure):
"""
Submits the swoosh request, and awaits a response.
If no user interaction is needed, calls on_success with a
httplib.HTTPResponse object.
If user interaction is needed, on_interaction_needed is
called with a single string parameter.
If the request failed, a ValueError is passed to on_failure
"""
response = sumbit_request()
if response.is_fine():
on_success(response)
elif response.is_partial()
on_interaction_needed(response.message)
else:
on_failure(ValueError(response.message))
Being Python, there are a million ways to do this. You might not like passing an exception to a function, so you maybe just take a callback for the user input scenario. Also, you might pass the callbacks in to the Swoosh initialiser instead.
But there are drawbacks to this too, such as:
Carelessness may result in spaghetti code
You're allowing your caller to inject logic into your function (eg. exceptions raised in the callback will propagate out of Swoosh)
My example here is simple, your actual function might not be
As usual, careful consideration and good documentation should avoid these problems. In theory.
I think raising an exception may actually be a pretty good idea in this case. Squashing multiple signals into a single return value of a function isn't ideal in Python, due to duck typing. It's not very Pythonic; every time you need to do something like:
result = some_function(...)
if isinstance(result, TypeA):
do_something(result)
elif isinstance(result, TypeB):
do_something_else(result)
you should be thinking about whether it's really the best design (as you're doing).
In this case, if you implement a custom exception, then the code that calls your function can just treat the returned value as a HTTPResponse. Any path where the function is unable to return something its caller can treat that way is handled by throwing an exception.
Likewise, the code that catches the exception and prompts the user with the message doesn't have to worry about the exact type of the thing its getting. It just knows that it's been explicitly instructed (by the exception) to show something to the user.
If the user interaction case means the calling code has to show a prompt, get some input and them pass control back to your function, it might be ugly trying to handle that with an exception. Eg,
try:
Swoosh.method()
except UserInteraction, ex:
# do some user interaction stuff
# pass it back to Swoosh.method()?
# did Swoosh need to save some state from the last call?
except ValueError:
pass # whatever
If this user interaction is a normal part of the control flow, it might be cleaner to pass a user-interaction function into your method in the first place - then it can return a result to the Swoosh code. For example:
# in Swoosh
def method(self, userinteractor):
if more_info_needed:
more_info = userinteractor.prompt("more info")
...
ui = MyUserInteractor(self) # or other state
Swoosh.method(ui)
You can return a tuple of (httplib.HTTPResponse, str) with the str being optionally None.
Definitely raise an exception for 1).
If you don't like returning a tuple, you can also create a "response object" i.e. an instance of a new class ( lets say SomethingResponse ) that encapsulates the HTTPResponse with optional messages to the end-user( in the simplest case, just a str).