Kivy: set widget disabled property in python code - python

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.

Related

Kivy custom widget behaviour

I'm new to programming and playing with Kivy to learn. I came across something weird so have created this small example to demonstrate.
In my text.kv file I have this:
#:kivy 2.0.0
<smallLabel#Label>:
font_size: 40
<bigLabel#Label>:
font_size: 60
BoxLayout:
orientation:'vertical'
padding: 20
spacing: 5
smallLabel:
text: 'Stays the same'
bigLabel:
id: changes
text: 'changes'
And in my python file this:
from kivy.app import App
class TestApp(App):
pass
if __name__ == "__main__":
TestApp().run()
When I run it I get this:
File "/home/marty/Python/datatut/test.kv", line 15
text: 'Stays the same'
^
SyntaxError: invalid syntax
Now, if I change the case of my widget so the first letter is upper-case it works:
<SmallLabel#Label>:
font_size: 40
<bigLabel#Label>:
font_size: 60
BoxLayout:
orientation:'vertical'
padding: 20
spacing: 5
SmallLabel:
text: 'Stays the same'
bigLabel:
id: changes
text: 'changes'
Notice that I only changed the SmallLabel. I left the bigLable as lowercase.
If I do this the other way around, that is leave smallLabel but make BigLabel
It fails with same error.
Why do I need to capitlise the name of my widget and why only the first one?
I did notice in all of the examples that I've seen the first letter is always capitlised for the custom widget name but have not seen that this is a requirement, and if it is, then why does the second widget work if the first one is capitalised?
The KV lang loader needs to be able to distinguish between properties of your widget, and sub widgets, and as you can see both kind are simply a line of text ended by a :, but there is a trick, by assuming that your classes follow PEP8 convention of Capitalized classes and snake_case attributes, it’s able to make the guess correctly.
This is (i agree a bit lightly) indicated in https://kivy.org/doc/stable/guide/lang.html#instantiate-children which states
Note
Widget names should start with upper case letters while property names
should start with lower case ones. Following the PEP8 Naming
Conventions is encouraged.
I assume the second one doesn’t need this guess, as it’s unindented after the first block, so it can’t be an attribute of the parent class (attributes must be before children declaration), so it can only be a child widget, and this rule is not needed in this situation, but i couldn’t find how this happen from a quick glance at the code

Kivy (Android): ScrollView.scroll_to() doesn't show widget hidded by keyboard

when a user click on a widget on my application, the keyboard that appears can hide it if the widget is too low.
I tried ScrollView.scroll_to() to ask for a focus on the widget, it works but it doesn't take the keyboard in the equation.
Before I click on "Raison Sociale".
After I click on "Raison Sociale
So the widget is hidded by the keyboard.
I don't know if a function like this already exist.
If widget.hiddedByKeyboard():
widget.show_taking_consideration_of_the_keyboard()
There is also the problem of the widgets at the end of the Screen, if we try to show them by scrolling down, the ScrollView will try to go up again even if that will hide the widget.
Ahh the classic, the keyboard covers the widget problem. I encountered this and here's what I did to solve it:
In my KV file I had something like this:
<MainWindow>:
... [A BUNCH OF WIDGETS] ...
ScrollView:
BoxLayout:
orientation: 'vertical'
size_hint: 1, None
height: self.minimum_height
... [A BUNCH OF WIDGETS] ...
Widget: # The important widget
id: buffer
size_hint: (1, None)
height: root.buffer
I added at the bottom of the ScrollView a blank Widget whose height was defined by a NumericProperty in the top level widget (Window - a BoxLayout).
In the Python file I had:
from kivy.core.window import Window # Need to import this
class MainWindow(BoxLayout):
buffer = NumericProperty()
Then when I requested the keyboard, I made sure to set buffer = Window.keyboard_height. This adjusted the height of blank Widget to the height of the keyboard, essentially creating extra room for the keyboard. This gives the user extra scrolling room to position the keyboard.
You can combine this implementation with the ScrollView.scroll_to() method to create a really nice effect. Essentially pass ScrollView.scroll_to(text_input_widget) when the user clicks on the TextInput. This will automatically scroll the ScrollView down so that the TextInput is at the top and the keyboard is below it.
This probably isn't the cleanest implementation (I could probably do it better now), but the concept of creating a blank Widget whose size is the height of the keyboard would be the same.
I found a better way, this attribute will do all the job.
from kivy.core.window import Window
Window.softinput_mode = "below_target"

Changing screens in Python without Screen Manager Class

I'm using gestures in all of my screens, and I cannot use a screen manager class to manage my screens, or so I believe. I can navigate the .kv file by using manger.current = 'some_screeen' but cannot in the .py file.
I've been trying Runner().ids.manager.current = 'some_screen' in the .py file but it doesn't work. There isn't even an error thrown. The screen doesn't change at all.
Essential Code (for the sake of brevity):
class Runner(gesture.GestureBox):
pass
MyApp(App):
def build(self):
return Runner()
Then in the KV file, I'm creating the screen manager.
<Runner>:
ScreenManager:
id: manager
Screen:
name: 'main_screen'
Button:
on_press:
manager.current = 'screen1'
Screen:
name: 'screen1'
Button:
on_press:
manager.current = 'home_screen'
I've been trying Runner().ids.manager.current = 'some_screen' in the .py file but it doesn't work. There isn't even an error thrown. The screen doesn't change at all.
It works fine, it just doesn't do what you believe. When you write Runner() you get a new instance of the Runner class, with its own children including its own ScreenManager. This one has nothing to do with the one you're displaying in your gui. When you set its current property the ScreenManager will dutifully change the screen, it's just you have no way to see that.
What you actually want is to change the current property of the widget that you are displaying in your gui. The best way to do this depends on the context, which you have omitted (always try to provide a full runnable example, it isn't clear what your failing code looked like). However, in this case the Runner instance is your root widget which is accessible with App.get_running_app().root, so you can write App.get_running_app().root.ids.manager.current = 'some_screen'. Again, there might be neater ways to do it depending on how you structure your code, but this is always an option.

Automatic 'focus' of TextInput Kivy

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

Tab/Enter (and other keystrokes) handling in Kivy's TextInput widgets

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

Categories