Beginner: Adding x number of widgets to layout - python

Really easy I'm sure but I'm learning Python & kivy (as a hobbyist not professional).
I have made my first 'complex' kivy layout, and am now attempting to add python code to it, and I am fundamentally mis-understanding some things. I am keeping it all organised in seperate files where possible.
1. Within a GridLayout I have a ScrollView. All I want is to be able to add 'x' number of buttons to the ScrollView in it's python class.
all relavent files ('...' indicating I have trimmed to only the relevant parts)
seatingmanager.py:
...
Builder.load_file('timescroll.kv')
...
class SeatingManager(AnchorLayout):
pass
class SeatingManagerApp(App):
def build(self):
return SeatingManager()
seatingmanager.kv:
<SeatingManager>
...
AnchorLayout:
...
GridLayout:
...
TimeScroll:
size_hint: None None
height: 50
width: 500
2. This is creating an instance of the TimeScroll class? This is where to add specific attributes to this instance?
timescroll.kv:
#:import timescroll timescroll
<TimeScroll>
3. This is where I can add attributes to all TimeScroll instances? If I am not adding any is this file necessary (other than importing timescroll.py)?
timescroll.py:(where I presume my problems lay)
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
class TimeScroll(ScrollView):
def build(self):
layout = BoxLayout(orientation='vertical', size_hint_x=None,width=1500)
for i in range(10):
btn = ToggleButton(text=str(i), group='timeHeaders')
layout.add_widget(btn)
self.add_widget(layout)
return layout
4. Is the build method automatically called when an instance of this class is created? If not, why is it called automatically in the first file?
5. This code doesn't work, simply leaves the ScrollView blank, so I presume I am adding the BoxLayout to the ScrollView incorrectly or the build method isn't automatically called.

Is the build method automatically called when an instance of this class is created? If not, why is it called automatically in the first file?
The build method is never called for widgets, unless you do so yourself. You should use the __init__ method, as per normal python convention (and don't forget to call super).
The App class has a build method that is called to start the user's own code, but the App is not a widget, and this is the only place kivy will automatically run a build method.

Related

How to Update Label in kivy On Clicking a Button

I am Really Tired of Searching Related to This Topic. And never Getting Something Great Answer...
I am sorry to say that, but then you should question your search skills. Please provide next time a minimal example of what you already tried, because then I could help you with proper understanding the mechanisms. Of course, I could simply give you a solution, but this would not help you at all, because often there are different ways to get to the same result but in some situations, you should prefer one method to the other ones.
Back to your problem
Here is a simple example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
import random
class MyApp(App):
def build(self):
self.layout = BoxLayout()
self.btn = Button(text="press", on_press=self.my_btn_method)
self.lbl = Label(text="")
self.layout.add_widget(self.btn)
self.layout.add_widget(self.lbl)
return self.layout
def my_btn_method(self, instance):
self.lbl.text = str(random.randint(0,100))
MyApp().run()
Explanation:
Every Button class and every class that inherits from the Button class has a on_press method.
You have basically two possibilities, either you write your own custom button class an overwrite the on_press method within this class or you write a custom method and assign this method to the buttons attribute on_press as I did in this example. When you do this, your custom method has to take the button instance as an attribute.
To change the labels text I simply assigned a new string to the labels text attribute. I really hope, this helps you with your problem. If you have some struggle just write a comment and I try to help you.
There is also an on_release method which you can use in the same way as the on_press method.

How to animate a button on kivy when the cursor is over it?

I'm kinda new to Kivy and I was looking for a way to animate the button when the cursor is over it.
I've tried to manage a way to get the mouse position and compare it with the button coordinates but no success at all.
This question has already been (mostly) answered at this post.There is a very nice example of this here by Olivier POYEN under LGPL license. Basically he has defined a class called HoverBehavior that you should inherit from to make a new class, e.g. HoverButton or HoverLabel (as his example shows). Then you have access to the on_enter and on_leave functions, which you can use to change the button's image, or change a label's text color, or whatever you need.
To answer your exact question, I would seek to understand the HoverBehavior class, then copy/paste it from the source above, then make a new class like so:
class HoverButton(Button, HoverBehavior):
def on_enter(self, *args):
self.background_normal = "some_image1.png" # Change the button's image when entered
def on_leave(self, *args):
self.background_normal = "some_other_image.png" # Change image when leaving
or you could use the kv language which looks even cleaner:
<HoverButton>:
background_normal: "some_image1.png" if self.hovered else "some_other_image.png"
just make sure you include a base class for the HoverButton in your python script if you use the 2nd option:
class HoverButton(Button, HoverBehavior):
pass

Kivy - Accessing id of widget outside of the root class

I wish to retrieve the id of a widget in order to access its text attribute, in a widget class outside of the root class. I understand in order to affect or retrieve items with an id from kv-lang, the following syntax may be used:
self.ids.some_id.some_attribute = new_value
This works perfectly fine when used in the root class, as access is available to all its children. However in a widget class, the class only refers to the widget declared, so any ids outside of it are out of scope.
<Root>:
...
SomeButton:
...
TextInput:
id: some_id
What doesn't work:
class SomeButton(Button):
def on_press(self):
print(self.ids.some_id.text)
As I mentioned, this is understandable. But I don't know then what is used in this instance. Any assistance would be appreciated :)
The issue is that ids are local to the rule, not to the widget.
here your rule is declared for <Root>, so to access it you have to use a reference to this widget, not to the button.
If you want to give a reference to some_id to button, you can add a property to your button.
class SomeButton(Button):
target = ObjectProperty()
def on_press(self):
print self.target.text
and link them together in kv.
<Root>:
...
SomeButton:
target: some_id
TextInput:
id: some_id
I'm new in kivy but i think TextInput is not a child widget of a SomeButton but you are trying to access it from Button anyway. That's your problem.
Try self.parent.ids.some_id.text

Kivy: How to attach a callback to a widget created in kvlang

When you want to attach a callback to a kivywidget, for example a textinput you can use the bind() function. Example from Kivy docs for a textinput:
def on_text(instance, value):
print('The widget', instance, 'have:', value)
textinput = TextInput()
textinput.bind(text=on_text)
But how do I attach it to an element that was created in the kvlang file?
Get a reference to the element, then call bind as normal. For instance, for the root widget of the application you can use App.get_running_app().root.bind, or for others you can navigate the widget tree via kv ids.
You can call the bind() on the widget referenced by self.ids['id_from_kvlang']. However this cannot be done on class level, you need to operate on the instance. So you need to put it in a function of the class.
The __init__ function is called at the instantiation of the object so you can put it there. However you need to schedule it, so it won't happen instantly, the widgets you are binding to are not there yet so you have to wait a frame.
class SomeScreen(Screen):
def __init__(self,**kwargs):
#execute the normal __init__ from the parent
super().__init__(**kwargs)
#the callback function that will be used
def on_text(instance, value):
print('The widget', instance, 'have:', value)
#wrap the binding in a function to be able to schedule it
def bind_to_text_event(*args):
self.ids['id_from_kvlang'].bind(text=update_price)
#now schedule the binding
Clock.schedule_once(bind_to_text_event)

Must Kivy properties be used in all classes?

Classic common-sense programming says to separate the GUI code from the core processing. I started this way in Kivy, but ran into a problem in my first-round prototype.
deck.py
class Card:
def __init__(self, suit, value):
self.name = "%s %s" % (suit, value)
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from deck import Card
class CardDisplay(BoxLayout):
card = ObjectProperty(Card("Default", 0))
class BoardDisplay(BoxLayout):
board = [[Card("Player1", 1),
Card("Player1", 2),
Card("Player1", 3),
Card("Player1", 4)],
[Card("Player2", 1),
Card("Player2", 2),
Card("Player2", 3),
Card("Player2", 4)]]
class GameApp(App):
pass
if __name__ in ("__main__", "__android__"):
GameApp().run()
game.kv
BoardDisplay:
orientation: "vertical"
BoxLayout:
CardDisplay:
card: root.board[0][0]
CardDisplay:
card: root.board[0][1]
CardDisplay:
card: root.board[0][2]
CardDisplay:
card: root.board[0][3]
BoxLayout:
CardDisplay:
card: root.board[1][0]
CardDisplay:
card: root.board[1][1]
CardDisplay:
card: root.board[1][2]
CardDisplay:
card: root.board[1][3]
<CardDisplay>:
Label:
text: root.card.name
Running this, I get an 8-card display as expected, but all of the cards are "Default 0". I think this is because I am using root.card.name, which is not a StringProperty, but just an attribute of the card class. However... what is the better way to do this? Am I really supposed to inherit from Widget (or something like it) in every class that contains something I'll want to display (in this case, Card)? Or is there a binding method I am failing to understand? I read through the Kivy docs and could swear it mentioned a problem just like this, but I wasn't able to find the reference again...
The problem is that root.card.name isn't a Property, so when you assign it (text: root.card.name) Kivy doesn't know to bind to anything. Binding happens automatically in kv, but it's not perfect. So here's an easy fix:
<CardDisplay>:
Label:
text: root.card and root.card.name
The result of the expression root.card and root.card.name will be the value of root.card.name, assuming root.card is assigned. But when Kivy reads that assignment, it sees that you are using root.card and will bind appropriately.
The key to using properties is knowing when you want to be notified about updates. You don't need root.card.name to be a StringProperty unless you want to know when that property is updated. In other words, if you change the Card instance used by CardDisplay, then it will update the Label. However, if you were to just update the name attribute of a Card, the Label would not update.
However, this applies equally to the board attribute on BoardDisplay. Updating this attribute will not update the display, since board isn't a property. But Kivy can handle lists of lists and provide notifications on updates:
board1 = ListProperty([Card("Player1", i) for i in range(4)])
board2 = ListProperty([Card("Player2", i) for i in range(4)])
board = ReferenceListProperty(board1, board2)
This way you will get notifications all around.
Oh, one thing I forgot to mention: if you do need to use properties on a non-Widget (like Card), you can extend from EventDispatcher to get properties to work. Kivy isn't just a UI, it's a framework. It's ok to use Kivy in non-UI code. If you have used data binding in .NET, you can think of Widget as a Control or UIElement and EventDispatcher as DependencyObject.

Categories

Resources