Textinput focus in Python-Kivy coding - python

A beginner of python/kivy coding. I'm trying to make a question/answer- type program.
Attached code shows a simplified example of my issue. It shows text input dialog and one button dialog alternately by remove_widget/add_widget.
My issue is, at first, text input dialog has focus in text input, but next time it appears, it loses its focus, despite stating self.gridlayout.txtinput.focus = True. Any idea how I can keep its focus?
I tried adding delay time, also tried adding txtinput.focus description in AnswerChoiceScreen's on_enter, but none of them worked.
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
sm = ScreenManager()
class QALayout1(BoxLayout):
pass
class QALayout2(BoxLayout):
pass
class AnswerChoiceScreen(Screen):
def __init__(self, **kwargs):
super(AnswerChoiceScreen, self).__init__(**kwargs)
self.gridlayout = None
self.gridlayout = QALayout2()
self.add_widget(self.gridlayout)
def _create_layout(self):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.add_widget(self.gridlayout)
def button1_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = QALayout2()
self.add_widget(self.gridlayout)
self.gridlayout.txtinput.focus = True
def buttonOK_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = QALayout1()
self.add_widget(self.gridlayout)
class myApp(App):
def build(self):
self.anschoi = AnswerChoiceScreen(name = 'anschoi')
sm.add_widget(self.anschoi)
sm.current = 'anschoi'
return sm
if __name__ == '__main__':
myApp().run()
my.kv
<AnswerChoiceScreen>:
BoxLayout:
orientation: 'vertical'
padding: 10,40,10,40
spacing: 40
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_press: root.parent.button1_clicked()
<QALayout2>:
txtinput: txtinput
orientation: 'vertical'
TextInput:
id: txtinput
text: ''
multiline: False
focus: True
ButtonOK:
id:ButtonOK
text: 'OK'
on_press: root.parent.buttonOK_clicked()
<Button0#Button>:
<Button1#Button>:
<ButtonOK#Button>:

Strange enough, all you need to do is to change
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_press: root.parent.button1_clicked()
to:
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_release: root.parent.button1_clicked()
Changing on_press to on_release. I believe it has to do with the focus of your TextInput being set on the on_touch_down (on_press) event, but then it loses focus due to the on_touch_up (on_release) event. So using the on_release avoids that problem. You can see this happen, by running your original code and pressing that OK button, but don't release it. The TextInput will have focus until you release your mouse button.
And you don't even need the line:
self.gridlayout.txtinput.focus = True

Related

How can I display the new value made by input for the next screen in Kivy

I have been trying to make this code work. Im using ScreenManager to manage my screen.
I want the Input I entered on the first screen to be displayed the next screen. But instead, it just shows the initial value, and it doesn't change to the Inputted value.
Here is the Code i have done
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: root.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = ObjectProperty()
prompt = ''
def submit(self):
prompt = self.promptObject.text
global result
result = prompt
sm.add_widget(NewScreen(name="Settings"))
sm.switch_to(sm.get_screen("Settings"))
NewScreen.display(self)
class NewScreen(Screen):
output = "testing testing"
def display(self):
self.output = result
print(result) #To test if it works
class TestApp(App):
def build(self):
global sm
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
if __name__ == '__main__':
TestApp().run()
I'm also thinking if i can instead Declare the Layout for the second screen later right before i call for the next Screen. Maybe that could work but if you have other method, it would be nice to see it.
thank you for the concise single-file example. this is a very helpful way to submit a kivy question. I have modified and tested the below app with various changes.
I changed the root.submit to app.submit. This is not strictly required, it is just a choice in this example to put the logic in the main app. it is also possible to use root.submit and put the logic in the widget but one would have to pass a reference to the screen manager into that widget in that case.
imported TextInput object instead of using ObjectProperty. when using an IDE it is helpful to declare objects with the specific type because it enables auto-complete
assigned the ScreenManager to self.sm so this object is available throughout the app.
finally, got rid of any reference to global. I think it is better to avoid use of this keyword and explicitly create the variable at the highest level where you need it and pass the value into the objects requiring it.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: app.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = TextInput()
class NewScreen(Screen):
output = StringProperty()
def __init__(self, **kw):
super().__init__(**kw)
def display(self, result):
# set the string property equal to the value you sent
self.output = result
print(result) # To test if it works
class TestApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# create screen manager with self so that you have access
# anywhere inside the App
self.sm = ScreenManager()
# create the main screen
self.menu_screen = MenuScreen(name='menu')
# this could be deferred, or created at initialization
self.settings_screen = NewScreen(name='Settings')
#
def submit(self):
prompt = self.menu_screen.promptObject.text
result = prompt
# optional, deferred creation
# self.settings_screen = NewScreen(name='Settings')
# add to the screen manager
self.sm.add_widget(self.settings_screen)
# enter the value into your other screen
self.settings_screen.display(result)
# switch to this screen
self.sm.current="Settings"
def build(self) -> ScreenManager:
# could create this screen right away, depending...
# self.sm.add_widget(self.settings_screen)
# of course you need the main screen
self.sm.add_widget(self.menu_screen)
# redundant, unless you create all screens at the beginning
self.sm.current = 'menu'
return self.sm
if __name__ == '__main__':
TestApp().run()

Popup - code conversion to KV and PY files

I would like to have code in KV and PY files that will display a YES/NO popup that I can instantiate anywhere in my app to pop the question.
Will a good kindly person help me to convert code I found on internet to KV file and PY file content.
I would like to be able to create an instance of this popup at any point in my app for display.
This is the sample I am working from:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
def __init__(self,**kwargs):
self.register_event_type('on_answer')
super(ConfirmPopup,self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480,400),
auto_dismiss= False)
self.popup.open()
def _on_answer(self, instance, answer):
print "USER ANSWER: " , repr(answer)
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
I have tried the following:
KV file:
<YesNoPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
and PY file:
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
class YesNoPopup(GridLayout):
text = StringProperty()
def __init__(self, **kwargs):
self.register_event_type('on_answer')
super(YesNoPopup, self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title='PIN',
content=content,
size_hint=(0.6, 0.4),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print
"USER ANSWER: ", repr(answer)
self.dismiss()
Then in the App
def pin_action(self):
pop = PopupTest()
pop.build()
But this only shows popup with Title and separator bar.
I will appreciate explanations of why you do what you do so that I as an 82 year old can learn and understand since my OO education is 22 years old and my Python Kivy is still in the learning stage.
Thanks a million
You can simplify your code by just calling the _on_answer() method directly from the on_release: of the Button. Here is a modified version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: app._on_answer(root, 'yes')
Button:
text: 'No'
on_release: app._on_answer(root, 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480, 400),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print("USER ANSWER: ", repr(answer))
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
Now that I understand your question, the main problem is just that the bind() method creates a WeakMethod for the bound method. In your code:
def pin_action(self):
pop = PopupTest()
pop.build()
An instance of PopupTest is created (which contains the bound _on_answer() method). But after the build() method of PopupTest is executed, the Popup is opened and the pop variable gets garbage collected. So the _on_answer() method is also garbage collected as a result. Since bind() creates a WeakMethod, there is no method left for the dispatched event to trigger.
There are two ways (that I know of) to fix this issue. One is to just modify the pin_action() method to retain a reference to PopTest so that is does not get garbage collected:
def pin_action(self):
self.pop = PopupTest()
self.pop.build()
Another way is to use fbind() instead of bind(), since fbind() keeps a reference to the bound method, so it cannot be garbage collected:
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
# content.bind(on_answer=self._on_answer)
content.fbind('on_answer', self._on_answer)

How to run checkbox widget using Popup in Kivy?

Newbie kivy user here. I have created 2 Kivy apps, and each works individually but I can't integrate one to the other. I am trying to run the CheckBoxesApp in MyApp using Popup(onButtonPress3). When I ran my code, I got the error "WidgetException: add_widget() can be used only with instances of the Widget class." Might be related to the fact that CheckBoxesApp uses App while MyApp uses MDApp? Any help appreciated >>
main.py
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
from kivy.uix.widget import Widget
import kivy
kivy.require('1.10.1')
from kivymd.app import MDApp
from kivy.uix.popup import Popup
FruitsSellected = []
class Checkboxes(Widget):
def checkboxes_click(self, instance, value, text):
if value:
print(f"Select {text}")
self.myvar = FruitsSellected
#Close
if text == 'FINISH':
print(FruitsSellected)
self.save()
App.get_running_app().stop()
return FruitsSellected
FruitsSellected.append(str(text))
else:
print(f"UnSelect {text}")
FruitsSellected.remove(str(text))
def save(self):
with open("allergenlist.txt", "w") as fobj:
fobj.write(str(self.myvar))
class CheckBoxesApp(App):
def build(self):
return Checkboxes()
class MyApp(MDApp):
def build(self):
# Define a grid layout for this App
self.layout = GridLayout(cols=1, padding=10)
# Don't worry about this button, just a place holder
self.button = Button(text="TAKE PICTURE")
self.layout.add_widget(self.button)
# Add a button
self.button2 = Button(text="SELECT")
self.layout.add_widget(self.button2) # Add a button
# Attach a callback for the button press event
self.button.bind(on_press=self.onButtonPress)
self.button2.bind(on_press=self.onButtonPress3)
return self.layout
#Place Holder don't worry about this either
def onButtonPress(self, button):
layout = GridLayout(cols=1, padding=10)
popupLabel = Label(text="TAKE PICTURE")
closeButton = Button(text="Close the pop-up")
layout.add_widget(popupLabel)
layout.add_widget(closeButton)
# Instantiate the modal popup and display
popup = Popup(title='TAKE PICTURE',
content=layout)
popup.open()
# Attach close button press
closeButton.bind(on_press=popup.dismiss)
def onButtonPress3(self, button):
layout = CheckBoxesApp()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
if __name__ == '__main__':
MyApp().run()
Checkboxes.kv
<Checkboxes>
# Create Box Layout
BoxLayout:
# set orientation and size
orientation: "vertical"
size: root.width, root.height
# Creating label
# create Grid Layout
GridLayout:
# Set number of column
cols:4
# create checkboxes
Label:
text: "Apple"
CheckBox:
# When check box selected, it will call checkboxes_click() method
on_active: root.checkboxes_click(self, self.active, "Apple")
Label:
text: "Bannana"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Bannana")
Label:
text: "Orange"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Orange")
Label:
text: "Grape"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Grape")
Label:
text: "Melon"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Melon")
Label:
text: "Peach"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Peach")
Label:
text: "Pineapple"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Pineapple")
Label:
text: "FINISH"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "FINISH")
The content of a Popup must be a Widget, but an App is not a Widget, so your code:
def onButtonPress3(self, button):
layout = CheckBoxesApp()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
will fail.
I suggest changing that code to:
def onButtonPress3(self, button):
# layout = CheckBoxesApp()
layout = Checkboxes()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
Of course, you must load the Checkboxes.kv file explicitly for this to work.

Kivy change keyboard layout for different text inputs

I am having a hard time changing between keyboard layouts in my app. In the below example I have two text fields and I want one to use a regular keyboard and the other to use a numberpad keyboard. When I click within one text field the keyboard changes, but focus is lost.
Is there a better way of changing a keyboard based on what field currently has focus?
from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'dock')
from kivy.app import App
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.properties import ObjectProperty
Window.size = (480, 600)
Window.borderless = False
KV = '''
AnchorLayout:
anchor_x: "center"
anchor_y: "center"
BoxLayout:
orientation: "vertical"
size_hint: 0.9, 0.5
MDTextField:
id: textField
hint_text: "Text field"
mode: "rectangle"
on_focus: app.changeKeyboard(textField, 'keyboard_text.json')
MDTextField:
id: numberField
hint_text: "Number field"
mode: "rectangle"
on_focus: app.changeKeyboard(textField, 'keyboard_number.json')
'''
class MyApp(MDApp):
textField = ObjectProperty()
numberField = ObjectProperty()
def build(self):
return Builder.load_string(KV)
def changeKeyboard(self, widget, layout):
self._keyboard = Window.request_keyboard(self._keyboard_closed, widget)
if self._keyboard.widget:
vkeyboard = self._keyboard.widget
vkeyboard.layout = layout
def _keyboard_closed(self):
print('My keyboard has been closed!')
self._keyboard = None
if __name__ == "__main__":
app = MyApp()
app.run()

SDL2 bug? App does not receive external keyboard input event. (Kivy/Python on iPhone)

I would like to add external keyboard operation to my app. Keyboard is working fine on the Mac/Kivy environment, but on iPhone (sent via toolchain.py), keyboard input event is lost after TextInput dialog is shown.
In the following example, MyLayout2 can be switched to MyLayout1 by 'Enter' key, but MyLayout1 cannot be switched to MyLayout2 by 'Enter'. I thought it might be due to the SDL2 version, so I updated it to the latest version 2.0.10, but no change. Is it a bug of SDL2?
Python code
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
sm = ScreenManager()
class MyLayout1(BoxLayout):
pass
class MyLayout2(BoxLayout):
pass
class MyScreen(Screen):
dialog_number = 0
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
self.gridlayout = None
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
Window.bind(on_key_up=self._keyup)
def _keyup(self,*args):
print(args[2])
if (args[2] == 40):
if self.dialog_number == 0:
self.button2_clicked()
elif self.dialog_number == 1:
self.button1_clicked()
def button1_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
self.dialog_number = 0
def button2_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout1()
self.add_widget(self.gridlayout)
self.dialog_number = 1
class myApp(App):
def build(self):
self.myscreen = MyScreen(name = 'myscreen')
sm.add_widget(self.myscreen)
sm.current = 'myscreen'
return sm
if __name__ == '__main__':
myApp().run()
Kivy code
<MyScreen>:
BoxLayout:
orientation: 'vertical'
padding: 10,40,10,40
spacing: 40
<MyLayout1>:
Button1:
id: btn1
text: '<MyLayout1> OK or ENTER key'
on_release: root.parent.button1_clicked()
<MyLayout2>:
txtinput: txtinput
orientation: 'vertical'
TextInput:
id: txtinput
text: ''
multiline: False
focus: True
button2:
id:Button2
text: '<MyLayout2> OK or ENTER key'
on_release: root.parent.button2_clicked()
<Button0#Button>:
<Button1#Button>:
<button2#Button>:

Categories