Kivy custom widget behaviour - python

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

Related

How do you make a variable change from one class to another in Kivy?

I am building a simple game using Kivy. I have two classes, Result() and Game(), where both are inheriting from 'Screen'. The Game Class has a function that calculates the the result of the game and produces a message which I want to display on the Result Screen which contains a label and the screen gets displayed after the result is calculated.
My KV file:
<Result>:
BoxLayout:
orientation: "vertical"
Label:
text: "Result Screen"
Label:
id: result_label
Using the id of the Label, 'result_label', I thought using would work `
Result().ids.result_label.text = "message"
`
I tried confirming this using:
print(Result().ids)
And yes it tells me that indeed the id does exist and so i expected that the previous block of code would change the text of the label to "message", but it just shows a Label with no text, and there is no error. So how would you change the text of a label from outside the class?

Kivy: set widget disabled property in python code

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.

Using a RST Document in a ScrollView using Kivy

I am in a bit of a predicament. While working with Kivy's ScrollView Layout and (Current Experimental) reStructuredText renderer module, I ran into a slight problem. Whenever I run my code, my terminal spams me with:
[CRITICAL] [Clock] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute
Now, the application seems to run perfectly fine, until you get to the page with the rST Document inside a ScrollView Layout. That page does all kinds of odd things. The Main scroll view will slowly scroll down, forever, trailing off the page into whiteness, and the rST document is placed oddly, shifted slightly to the left.
When I remove the document though, the screen and application behave perfectly normal, running smoothly. Does anyone have any idea as to how I could fix this, to make the page work correctly? (Did I mention the rST Document was originally in a Carousel, but I took out the carousel to see if that was the problem.)
Here is the Kivy Language Code:
<Page>:
orientation: 'vertical'
ScrollView:
size_hint: (.99, .99)
StackLayout:
size_hint_y: None
id: content_layout
height: self.minimum_height
WrappedLabel:
text: "Test"
font_size: min(root.height, root.width)
RstDocument:
underline_color: 'blue'
text:("Some Text")
Could the problem be that rST Documents are based off of the ScrollView Layout by any chance?
Sometimes height: self.minimum_height and similar stuff are like shooting yourself in a foot. Definitely try to comment out these things first, because if you don't do something fancy, the sizing is the issue.
Now, why is it an issue? StackLayout has its minimum_height set from minimum_size, which is set I think somewhere here and has some initial value that is not zero.
Don't be confused though, minimum_height really defaults to zero at the beginning, but then it's recalculated probably on each added widget or so. If you add on_height: print(self.height) after your height: self.minimum_height, you'll see what I mean.
Why does it do it like that? Simple! You didn't set absolute size for those children (each child has size_hint == [1, 1]).
Also, ScrollView expects size bigger that the ScrollView if I remember correctly (so that it would scroll).
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<Test>:
orientation: 'vertical'
ScrollView:
size_hint: (.99, .99)
StackLayout:
size_hint_y: None
id: content_layout
height: self.minimum_height
on_height: print(self.height)
Label:
size_hint: None, None
size: 100, 30
text: "Test"
font_size: min(root.height, root.width)
RstDocument:
size_hint: None, None
size: 100, 1000
underline_color: 'blue'
text:("Some Text")
''')
class Test(BoxLayout): pass
runTouchApp(Test())
Remove size_hint and size from children and you have your too much iteration there.

Using external .kv file vs doing things internally?

I have noticed that most examples I foind online don't have an external .kv file. They define all the instances internally. However they also say that having an external .kv file is a good practice. Which is better to do? If having external .kvfiles are better, then how am I supposed to use the code which uses internal code and turn it into external .kv files?
For example, doing this ->
from kivy.app import App
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
class TutorialApp(App):
def build(self):
b = BoxLayout(orientation='vertical')
t = TextInput(font_size=150,
size_hint_y=None,
height=200,
text='default')
f = FloatLayout()
s = Scatter()
l = Label(text='default',
font_size=150)
t.bind(text=l.setter('text'))
f.add_widget(s)
s.add_widget(l)
b.add_widget(t)
b.add_widget(f)
return b
if __name__ == "__main__":
TutorialApp().run()
Instead of-
<ScatterTextWidget>:
orientation: 'vertical'
TextInput:
id: my_textinput
font_size: 150
size_hint_y: None
height: 200
text: 'default'
FloatLayout:
Scatter:
Label:
text: my_textinput.text
font_size: 150
"Internal" usage of kv is through Builder class, which allows you even to load external file. Those examples are worded in a Builder.load_string(...) way because it's way simpler to have a small example in one place in one file.
How to convert it to the external one? Simple, copy&paste the string from Builder.load_string() into a separate .kv file with a name of your class that inherits from App(your main class with build()) and that's it. It'll load the same thing from the external file.
Why it's better or worse? Isn't any of that actually. It's like comparing "java" and python style i.e. either putting everything out of your file and having basically this construction a'la java, where the main file contains this:
class This(something):
SpecialClass.doThis()
AnotherClass.doThat()
and other classes(or different things) are in separate files. Or this construction:
class Special(...):
...
class Another(...):
...
class This(something):
Special.do_this()
Another.do_that()
Both of them are useful and you'll find yourself working with a mix between them. It's about transparency and clearness of your code, but maybe you don't want to have a hundred of files... or a 2MB main.py, pretty much compromise of how do you decide to code.
Edit:
The python vs kv is a funny "fight", but except a for(and while?) loop you can pretty much do everything necessary inside kv in such an easy way! Kv is here to make writing easier e.g. remove too much stuff like add_widget() or basically making an empty class just to rename a widget or to change its size for using it in one place.
With python in a 500line file without kv you won't do that much as with 100 extra lines in kv. The documentation has important parts in python and maybe it's even targeted for users who can't/don't want to use kv. Also this and all examples highly depend on the author of an example and that particular part of the docs.
Which returns me back to the java vs python style of coding I mentioned before. It's pointless to do complicated stuff just because you think it'll feel/look better if you can do it cleaner and more readable i.e. don't just go one way if you have a tool that increase your speed of coding exponentially. Find the middle way.

How to make a widget span multiple columns/rows in gridlayout in kivy

I would like to create an app in kivy with widgets in a grid and I want to be able to set some widgets to be larger - to occupy more than one cell in a grid.
GridLayout seems the most appropriate, but it seems to not support placing widgets in more than one cell.
To be more specific I want behavour similar to the grid geometry manager from Tkinter, when setting columnspan or rowspan to more than 1.
Like this:
(widget)(widget)(widget)
( bigger widget )(widget)
...
I would prefer to be able to do this using existing kivy layouts instead of having to write my own layout class to handle this, but if no other option is possible, this is also ok.
Another option here is to have a GridLayout with 1 column and populate each row with a BoxLayout with orientation="horizontal". You can then format each BoxLayout (row) as you want.
For more info on the BoxLayout: http://kivy.org/docs/api-kivy.uix.boxlayout.html
I don't think a GridLayout is really suitable, it just isn't designed for quite that usage.
If I personally had to do this, I'd probably make my own Layout class, it wouldn't need a very complicated do_layout method.
One possible start point would be the SparseGridLayout I made a while ago. You could very easily add column and row span properties to it...actually, I'll probably add them myself now that you've given me the idea!
That might not be ideal if you have a big grid full of widgets, in which case something similar to a gridlayout might be better, or possibly a combination of multiple layouts if the spanning widgets are in a particular pattern.
Lets assume you have a GridLayout with two columns and you want to span the first row.You can add Two FloatLayout whereby the first FloatLayout will contain the widget you would like to span while the second Layout will have row and height values to zero.This would archive a span effect
here is an example of .kv span effect
GridLayout:
cols:2
FloatLayout: # The first FloatLayout in the first column in the gridLayout
size_hint:None,None
size: 0,50
BoxLayout:
size_hint: None,None
size: root.width-40,50
pos_hint: {'x':.5,'center_y':.5}
BoxLayout:
padding:0,0,5,0
Label:
id:lbl_unknown
text:'Accession number :'
TextInput:
text:''
FloatLayout: # The second FloatLayout in the second column of the gridLayout
size_hint:None,None
size:0,0
Label:
text:'Label 1:'
TextInput:
Label:
text:'Label 2:'
TextInput:
An easy work around is to pack BoxLayout objects into any type of parent layout you want using your better judgement to decide the orientation of each subsequent BoxLayout
(decided to use box layouts for almost all of my own project over any gridlayout)
This is my class:
class AuthPage(BoxLayout):
def __init__(self, **kwargs):
super(AuthPage, self).__init__(**kwargs)
self.orientation = 'vertical'
self.add_widget(Label(text='Authenticate'))
unameRow = BoxLayout(orientation='horizontal')
unameRow.add_widget(Label(text='User Name'))
unameRow.username = TextInput(multiline=False)
unameRow.add_widget(unameRow.username)
self.add_widget(unameRow)
pwordRow = BoxLayout(orientation='horizontal')
pwordRow.add_widget(Label(text='password'))
pwordRow.password = TextInput(password=True, multiline=False)
pwordRow.add_widget(pwordRow.password)
self.add_widget(pwordRow)
self.add_widget(Button(text='authenticate'))

Categories