I'm writing an app using Kivy framework and I stumbled upon a minor but annoying problem: I don't know how to handle Tab/Enter/Arrow keys in text fields so that pressing either of them would dispatch an event, eg. switch the focus (jump) to another TextInput or launch something like send_form()
Could anyone please shed some light on this issue?
Kivy 1.9 provides the ability to set write_tab: False on text inputs (see docs), causing the tab key to focus on the next focusable widget.
Kivy allows the Enter key to dispatch events by setting multiline: False and on_text_validate: root.foo().
So, to create a text input widget that has the desired Enter and Tab functionality, do as follows:
TextInput:
write_tab: False
multiline: False
on_text_validate: root.foo()
Just found this old question and figured I would contribute. I also needed tab / enter to go to the next field. I did what #tshirtman suggested. This is my custom TextInput class:
from kivy.uix.textinput import TextInput
class TabTextInput(TextInput):
def __init__(self, *args, **kwargs):
self.next = kwargs.pop('next', None)
super(TabTextInput, self).__init__(*args, **kwargs)
def set_next(self, next):
self.next = next
def _keyboard_on_key_down(self, window, keycode, text, modifiers):
key, key_str = keycode
if key in (9, 13) and self.next is not None:
self.next.focus = True
self.next.select_all()
else:
super(TabTextInput, self)._keyboard_on_key_down(window, keycode, text, modifiers)
This allows you to pass next when you instantiate the input, or alternatively call set_next on an existing input.
9 and 13 are the key codes for tab and enter.
Works well for me.
As suggested by Daniel Kinsman in his comment, you could subclass TextInput, add "previous" and "next" ObjectProperties for tab support (easy to set in kv using references to other widgets), and handle the keyboard events differently. There is no out of the box support for this right now, but if you want to work on such modification drop us a feature-request or comme discuss it in #kivy on freenode.
https://github.com/kivy/kivy/blob/master/kivy/uix/textinput.py#L1188
Maybe it would be even better to add such support on widget, and add some focus logic, so tab/enter have effects on any activable widget, and some widgets like slider use right/left/up/down keys too.
So there is still a lot to do in Kivy about that, and if you are interested in helping, you can really make it happen faster, we'll help you :)
[Insufficient points to just comment, so adding this here...]
It's crucial to note that the keyboard NEXT behavior only works easily if the next field is managed by the same keyboard layout. However, an advanced app will have:
username (qwerty)
password (password)
ID (numeric)
etc
So the approaches above really doesn't work out.
In the kv file:
MyTextInput:
next: idTheNextFieldBelowThis
In your MyTextInput class:
def insert_text(self, value, from_undo=False):
#
# Unfortunately the TextInput write_tab behavior only works if the next field is the same exact keyboard
# type.
#
if not value[-1:] == ' ':
return super(MyTextInput, self).insert_text(value, from_undo=from_undo)
r = super(MyTextInput, self).insert_text(value[:-1], from_undo=from_undo)
if self.next is not None:
self.next.focus = True
return r
Related
I am facing a problem with the Kivy widget Switch and was not able to find a solution. Each topic on the Internet deals with "Working with the active property", which is understandable to me. But I want to set/initializie the start-up active value depending on the current environment within the program.
In my case: I have a Wifi Power-Plug which can be already running. So in this case when the app starts I want the switch with active: True. If the plug is deactivated, the switch shall start with active: False
Normaly you can do this from the main.py with sth. like:
if (getWifiState) == "OFF":
self.ids[widgetName].active = False
else:
self.ids[widgetName].active = True
Generally spoken this works and changes the state.
But here the problem: as soon as you are changing the switch value this way it behaves as if you were clicking on the switch, because the default value = 0 → change to 1 → on_active: function() will be called. But I need a solution which allows me just to change the start value without running the on_active property.
Potential solution:
Probably I have to put logic into my .kv file so that during the switch initialisation the correct start parameter will be set. But why?
Or is there another way to do so?
Appreciate your help
Tried to put logic to my active property in .kv-File, but this did not work.
My solution:
import random
from kivy.app import App
from kivy.lang import Builder
kv = '''
BoxLayout:
Switch:
active: app.get_wifi_state()
on_active: print(self.active)
'''
class Test(App):
# method which return wifi status (replace implementation with your own)
def get_wifi_state(self):
return random.choice((True, False))
def build(self):
return Builder.load_string(kv)
Test().run()
Example:
.py code:
class PokemonWindow(Screen):
form_button = ObjectProperty(None)
type_grid = ObjectProperty(None)
def __init__(self, **kwargs):
super(PokemonWindow, self).__init__(**kwargs)
self.searched_pokemon_forms = ["Normal", "Purified", "Shadow"]
def create_form_buttons(self):
for text in self.searched_pokemon_forms:
self.form_button = Button(text=text, on_press=self.get_text)
self.type_grid.add_widget(self.form_button)
def get_text(self, *args):
return self.form_button.text
and the .kv code:
<PokemonWindow>:
name: "pokemonWindow"
type_grid: type_grid
MDGridLayout:
id: type_grid
rows: 2
cols: 4
size_hint_x: .8
size_hint_y: .05
pos_hint: {"x": .1, "y": .8}
Goal:
If I click one of the three buttons in this example I want to get the text of the specific button I clicked in return to use it for another method.
Problem:
The problem I am facing is, that I always get the same text, no matter which button I click. I think this is happening because the ObjectProperty gets overwritten in every iteration of the for loop.
Surely there is a smart way to solve this, but I can not figure it out at the moment. Can anybody please explain to me what would be the best way to achieve my goal?
Edit:
Okay, so since I propably won't be able to get the text of the button I will explain why I wanted the text. Maybe somebody knows a better way of approaching my problem.
There is a text input where you cant enter a pokemon name as a string and then click on a search button, that will search in a .json file for the entered string and return a list with sub_lists that stores information of this pokemon and its "form" (Shadow, Purified, Normal etc). The list will look like this:
self.information = [['Normal', 143, 'Snorlax', ['Normal']], ['Purified', 143, 'Snorlax', ['Normal']], ['Shadow', 143, 'Snorlax', ['Normal']]]
First value is the form, second value is the pokedex id, third value the name and fourth value a sub_list of the sub_list that contains the type of a pokemon.
Another list will be created (self.searched_pokemon_forms) that only stores the forms of the pokemon like shown further above. The length of this list defines the amount of buttons that will be created and each of those buttons will contain the form as text (string).
If I now click a button I want to be able to retrieve the information that I stored in the longer list (self.information) and use it to create a set of labels that display these informations, e.g.
Button with text (form) "Normal" was pressed and now I want to get the id (143), the name ("Snorlax") and the type (["Normal"]).
I hope this helps to clarify my goal.
Edit 2: I finally got it to work without putting my self.get_text() method into the App class, still it was good to know, so thank you #SHINJI.K.
I simply had to add the value to a list like this:
def get_text(self, button):
if len(self.text_list) != 0:
del self.text_list[:]
self.text_list.append(button.text)
Now I can always get the correct button text with self.text_list[-1].
self.form_button will always be "Shadow" since it is the last item in self.searched_pokemon_forms
Instead of using self.form_button, you might want to just modify get_text() to reference the clicked button:
def get_text(self, button):
return button.text
Once you click any button bound to get_text(), that button will be passed into the function as button. You can then access the text property of the clicked button using this reference.
It doesn't make sense though for the callback function to return the value since you have no way of retrieving it when it is called. I suggest that you simply do what you want to do with button.text within the callback function.
I have something like this in kivy lang file (pseudo code)
<RootWidget>:
Checkbox:
id: chkbox
TextInput:
id: in_text
text: ""
Button:
id: ok_btn
label: "Okay"
on_press: app.ok_pressed()
disabled: chkbox.active or len(in_text.text) > 8 and ...
The point is, the ok_btn needs to be enabled and disabled dynamically based on state of several other widgets.
This all works as expected, but now I have a problem. For complicated reasons, I need to create the button and insert it into the root widget in python rather than define it in a .kv file or string. I can't figure out what to do with the disabled property. If I set it as a property
btn = Button()
btn.disabled = ...
This only sets the initial state. I thought maybe
btn.bind(on_disabled=some_function)
but this is only to do something when the button is disabled, not to define when it should be disabled. Ditto on_state. I also tried
btn.bind(disabled=some_function)
some_function is never called
Thanks in advance for any pointer
It sounds like you have it backwards: it isn't the button's disabled property that you want to bind things to, but rather you want to bind to other things so that when they change the button's disabled property gets updated.
For instance, from your original example the autogenerated code is along the lines of chkbox.bind(active=lambda self: setattr(ok_btn, "disabled", self.active) (not actually this code, but something equivalent). You need to replicate this manually.
Of course you can abstract this in various ways. For instance, you could bind all the conditions you care about to update a property of your App class (so that it's always present to update, regardless of whether your button exists), then use a kv rule like disabled: app.that_property in your button. This is not the only option though.
Currently I am working with my own custom widget that consists of a QLineEdit and a QPushButton. A user is limited to entering an integer between 0 and 1000 in the text field. Upon clicking the button, the widget's custom clicked signal emits the value in the field.
Here is the code:
class ResizeWidget(QWidget):
clicked = pyqtSignal(int)
def __init__(self):
super().__init__()
#NumField and Button
self.field = QLineEdit()
self.field.setPlaceholderText("Resize Percentage [0,1000]")
self.resizeButton = QPushButton("Resize")
#Layout
self.lay = QHBoxLayout()
self.setLayout(self.lay)
#Add to Widget
self.lay.addWidget(self.field)
self.lay.addWidget(self.resizeButton)
#Field limits
self.field.setMaxLength(4)
self.field.setValidator(QIntValidator(0,1000))
#Connection
self.resizeButton.clicked.connect(self.onClick)
#pyqtSlot()
def onClick(self):
val = int(self.field.text())
self.clicked.emit(val)
Now what I'd like to add to the class is some way of allowing the user to press enter when the blinking cursor | sometimes called a 'caret' is in the text field.
I am able to find documentation on the mouse in general, mouseEvent and mousePressEvent as a method within QWidgets. But I can't find any documentation that refers to the blinking cursor within the text field.
I would like to add some sort of pseudocode like this within init():
if(cursor == inQLineEdit and pressedEnter):
self.onClick()
I know QLineEdit::returnPressed plays a major role in creating the correct function but I only want the enter key to be valid if the user is using the ResizeWidget. Not some other part of my GUI. I would think the enter key isn't binded to only 1 widget in my entire application but I'd be interested to find out.
It was as simple as adding the following line:
self.field.returnPressed.connect(self.onClick)
As long as the caret (blinking cursor) isn't in the text field, pressing the Enter key doesn't cause any reaction from my custom widget.
I have a textinput widget that looks like this:
<ReaderWidget>:
Label:
text: 'Please scan EBT card'
font_size: root.height/8
size: self.texture_size
bold: True
color: 0, 0.70, 0.93, 1
TextInput:
focus: True
password: True
multiline: False
cursor: 0, 0
The widget is dynamically added to the layout based on the user pressing a button in another widget. Currently the user has to point the mouse/finger into the text box before entering text, and I want the cursor to be in the text box ready to receive text without the user having to indicate by mouse press. Is there a way to do this?
It seems like focus : True should do it. But it doesn't seem to.
I know this is old but I found this question when I was trying to do something very similar. My code for adding the TextInput (and setting it's focus) was in the on_press handler for a button. A press would cause the TextInput to be added to the layout and it's focus set, but then it would lose focus when the button was released. Moving my code to on_release fixed the problem.
This worked for me in kivy 1.9.0:
def show_keyboard(event):
text_input.focus = True
Clock.schedule_once(show_keyboard)
If text_input.focus is set directly, it doesn't seem to work. Perhaps this is a bug in kivy?
I had the same issue, but not for a button, so the on_release was not an option.
If you want to do it with the on_touch_down method, you can focus the widget and add the current touch to be ignored for focusing:
def on_touch_down(self, touch):
self.focus = True
FocusBehavior.ignored_touch.append(touch)
Of course you also need to import FocusBehavior:
from kivy.uix.behaviors.focus import FocusBehavior
You can read more here: https://kivy.org/doc/stable/api-kivy.uix.behaviors.focus.html