Reputation: 191
I'm creating an app using kivy. So I don't think I'm understanding something correctly regarding the on_focus behavior of the FloatInput.
Let me first describe what I'm trying to do here. I'm trying to activate a pop-up numpad when the user clicks on a TextInput (this numpad just has buttons for numbers 0-9 and an "enter" button). Next, once the user provides a number in the pop-up numpad and hits an "enter" button, the pop-up should close and the text field of the TextInput should be updated to the user entered number.
However, I'm running into a problem. Essentially, I have a form with multiple TextInputs constructed as described above (I'll refer to them hereafter as "FloatInputs" b/c that's what I've named them). The first time I enter values into one of the FloatInputs, everything works as expected. But the problem occurs whenever I try to enter a number into a second FloatInput. Specifically, the on_focus function is called twice somehow and the user must enter the value into the pop-up twice as a result (this is obviously not ideal as it would serve to frustrate the user).
I apologize if this is a stupid question, but I've been puzzled by it for quite a while. I think the heart of the problem lies in the order of the focusing of the TextInput and the popup widgets, but I could be wrong here. If someone could please offer suggestions as to how I could fix this bug I'd greatly appreciate it.
Here is the relevant python code:
class FloatInput(TextInput):
def on_focus(self, instance, value):
print 'ON FOCUS CALLED - THIS SHOULD ONLY BE CALLED ONCE!'
# Adding this helped part of the problem (however, on_focus is still being called twice???)
self.unfocus_on_touch = False
prompt_content = BoxLayout(orientation='vertical') # Main layout
num_pad = NumPadWidget()
prompt_content.add_widget(num_pad)
def my_callback(instance):
self.text = num_pad.ids.num_label.text
self.popup.dismiss()
# Now define the popup, with the content being the layout:
self.popup = Popup(id='num_pad_popup', title='Enter value', content=prompt_content, size_hint=(0.8,0.5), autodismiss=False)
num_pad.ids.enter_btn.bind(on_press=my_callback)
# Open the pop-up:
self.popup.open()
And here is the relevant kv code:
<NumPadButton@Button>:
font_size: '16dp'
bold: True
<NumPadWidget>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint_y: 1
id: num_label
Label:
id: num_label
text: ''
BoxLayout:
size_hint_y: 5
orientation: 'vertical'
BoxLayout:
NumPadButton:
text: '7'
on_press:
num_label.text = num_label.text + '7'
NumPadButton:
text: '8'
on_press:
num_label.text = num_label.text + '8'
NumPadButton:
text: '9'
on_press:
num_label.text = num_label.text + '9'
BoxLayout:
NumPadButton:
text: '4'
on_press:
num_label.text = num_label.text + '4'
NumPadButton:
text: '5'
on_press:
num_label.text = num_label.text + '5'
NumPadButton:
text: '6'
on_press:
num_label.text = num_label.text + '6'
BoxLayout:
NumPadButton:
text: '1'
on_press:
num_label.text = num_label.text + '1'
NumPadButton:
text: '2'
on_press:
num_label.text = num_label.text + '2'
NumPadButton:
text: '3'
on_press:
num_label.text = num_label.text + '3'
BoxLayout:
NumPadButton:
text: '0'
on_press:
num_label.text = num_label.text + '0'
NumPadButton:
text: '.'
on_press:
num_label.text = num_label.text + '.'
NumPadButton:
text: 'del'
on_press:
num_label.text = num_label.text[:-1]
BoxLayout:
BoxLayout:
size_hint_x: 1
NumPadButton:
text: 'C'
on_press:
num_label.text = ''
BoxLayout:
size_hint_x: 2
NumPadButton:
id: enter_btn
text: 'Enter'
Upvotes: 2
Views: 3137
Reputation: 174
A bit late to the party, but...
The solution is to test the value. If it's True, it's a focus event. If it's False, it's a defocus event.
def on_focus(self, instance, value):
if value:
print("focusing in...")
else:
print("defocusing")
And here's how to do it in a .kv file:
TextInput:
on_focus:
if self.focus: print("focusing in...")
else: print("defocusing")
Upvotes: 1
Reputation: 61
For what it's worth, the on_focus() method of TextInput looks like this:
def on_focus(self, instance, value, *largs):
If value is True, it's a focus event. If it's False, then it's a defocus/blur event. I really don't know why they used one handler for both events but they implemented it this way. I think anyone else would have implemented this as on_focus()/on_blur().
This could be the reason why a method might be firing off twice.
Upvotes: 3
Reputation: 720
Make sure all of your TextInputs (in your case 'FloatInput') have the widget property unfocus_on_touch set to False as soon as possible in your code. That means in a kv file, factory builder string (below) or some wrapped __init__
class function. Setting this property in the on_focus event is too late and probably causes it to get fired twice.
Builder.load_string('''
<RootWidget>:
TextInput:
unfocus_on_touch: False
id: id_textbox
text: 'hello world'
multiline: False
font_size: 20
''')
Upvotes: 0
Reputation: 13
I had a similar problem when trying to call up a modal after clicking on a text input. It would fire twice if I tried using on_focus
. What worked for me was to set is_focusable = False
and use the on_touch_down
event to fire the function.
Upvotes: 0
Reputation: 191
Okay after playing with this a bit more I found a solution to achieve the functionality that I wanted. However, to be clear this does resolve why the on_focus behavior is acting in the manner that I've described in my original post.
Here is a work around in case anyone is interested:
class FloatInput(TextInput):
def __init__(self, **kwargs):
super(FloatInput, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
prompt_content = BoxLayout(orientation='vertical') # Main layout
num_pad = NumPadWidget()
prompt_content.add_widget(num_pad)
def my_callback(instance):
self.text = num_pad.ids.num_label.text
self.popup.dismiss()
# Now define the popup, with the content being the layout:
self.popup = Popup(id='num_pad_popup', title='Enter value', content=prompt_content, size_hint=(0.8,0.5), autodismiss=False)
num_pad.ids.enter_btn.bind(on_press=my_callback)
# Open the pop-up:
self.popup.open()
Specifically, this solution works by using on_touch_down instead of looking for the focus of the TextInput. Additionally, looking for the collision with "if self.collide_point(touch.x, touch.y)" prevents an error where all widgets of the same type respond to the user touch.
I hope this helps someone!
Upvotes: 1