Reputation: 701
I'm trying to make an input text that only accept float values. In addition, the value entered must be between two values.
I created a class that contain a 'validate' method. If the value is not between two values, a Popup is showed.
But I have a problem. The method is only called when the user hit 'Enter'. I tried call the method when the text is changed, but it is annoying for the user, because the Popup appears all the time while the user is entering the data.
There is another approach to do something like this?
Python file:
class BoundedInput(BoxLayout):
value = NumericProperty()
def validate(self, min_value, max_value):
status = min_value <= self.value <= max_value
if not status:
message = f'Value must be between {min_value} and {max_value}'
popup = Popup(title='Warning', content=Label(text=message),
size_hint=(None, None), size=(300, 200))
popup.open()
Kv file:
<NumericInput@TextInput>:
input_filter: 'float'
multiline: False
<BoundedInput>:
orientation: 'horizontal'
Label:
text: 'Value'
NumericInput:
text: str(root.value)
on_text_validate:
root.value = float(self.text)
root.validate(5, 100)
Upvotes: 4
Views: 5528
Reputation: 1
Modified version of eyllanesc's answer. This is version for int numbers only. But could be modified for float numbers.
from kivy.app import App
from kivy.base import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
Builder.load_string("""
<BoundedLayout>:
orientation: 'horizontal'
Label:
text: 'Value'
NumericInput:
min_value : 5
max_value : 100
hint_text : 'Enter values between {} and {}'.format(self.min_value, self.max_value)
""")
class NumericInput(TextInput):
min_value = NumericProperty()
max_value = NumericProperty()
def __init__(self, *args, **kwargs):
TextInput.__init__(self, *args, **kwargs)
self.input_filter = 'int'
self.multiline = False
def insert_text(self, string, from_undo=False):
if string.isnumeric():
old_text = self.text
if string != "":
TextInput.insert_text(self, string, from_undo=from_undo)
if not (self.min_value <= int(self.text) <= self.max_value):
self.text = old_text
class BoundedLayout(BoxLayout):
pass
class MyApp(App):
def build(self):
return BoundedLayout()
if __name__ == '__main__':
MyApp().run()
Now cursor position is taken into account.
Upvotes: 0
Reputation: 243887
A suitable approach could be filtering in addition to floating this also within the range for it we create a class that inherits TextInput
and overwrite the insert_text method:
from kivy.app import App
from kivy.base import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
Builder.load_string("""
<BoundedLayout>:
orientation: 'horizontal'
Label:
text: 'Value'
NumericInput:
min_value : 5
max_value : 100
hint_text : 'Enter values between {} and {}'.format(self.min_value, self.max_value)
""")
class NumericInput(TextInput):
min_value = NumericProperty()
max_value = NumericProperty()
def __init__(self, *args, **kwargs):
TextInput.__init__(self, *args, **kwargs)
self.input_filter = 'float'
self.multiline = False
def insert_text(self, string, from_undo=False):
new_text = self.text + string
if new_text != "":
if self.min_value <= float(new_text) <= self.max_value:
TextInput.insert_text(self, string, from_undo=from_undo)
class BoundedLayout(BoxLayout):
pass
class MyApp(App):
def build(self):
return BoundedLayout()
if __name__ == '__main__':
MyApp().run()
Upvotes: 4
Reputation: 5405
You could use Bubble, and leave it hanging as long the input is wrong.
You can even edit the lable to tell what is wrong.
Or change the text color if validated or not.
I made an example with both implemented.
from kivy.app import App
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import BooleanProperty
KV = """
<ValidateLabel>:
size_hint: (None, None)
size: (280, 60)
Label:
id: label
text: "Must be a float"
<MyInput>:
foreground_color: (0,1,0,1) if root.validated else (1,0,0,1)
FloatInput:
"""
class MyInput(TextInput):
validated = BooleanProperty(False)
class FloatInput(FloatLayout):
bubble_showed = True
def __init__(self, **kwargs):
super(FloatInput, self).__init__(**kwargs)
self.input = MyInput()
self.input.bind(text=self.validate)
self.add_widget(self.input)
self.bubble = ValidateLabel()
self.add_widget(self.bubble)
def validate(self, input, value, min_value=15., max_value=25.):
self.bubble.ids.label.text = "Number must be between {} and {}".format(min_value, max_value)
try:
print(min_value, max_value)
status = float(min_value) <= float(value) <= float(max_value)
except Exception as e:
status = False
self.bubble.ids.label.text = "Input must be a number"
if not status:
if not self.bubble_showed:
self.input.validated = False
self.add_widget(self.bubble)
self.bubble_showed = True
else:
print("bubble removed")
self.input.validated = True
self.remove_widget(self.bubble)
self.bubble_showed = False
class ValidateLabel(Bubble):
validated = False
class TestApp(App):
def build(self):
return Builder.load_string(KV)
TestApp().run()
Output:
Upvotes: 3