How do I dynamically resize the a label or button, in particular, the text_size and height, depending on the amount of text, at run-time?
I am aware that this question has already been answered in one way with this question:
Dynamically resizing a Label within a Scrollview?
And I reflect that example in part of my code.
The problem is dynamically resizing the labels and buttons at run-time. Using, for example:
btn = Button(text_size=(self.width, self.height), text='blah blah')
...and so on, only makes the program think (and logically so) that the "self" is referring to the class which is containing the button.
So, how do I dynamically resize these attributes in the python language, not kivy?
My example code:
import kivy
kivy.require('1.7.2') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
i = range(20)
long_text = 'sometimes the search result could be rather long \
sometimes the search result could be rather long \
sometimes the search result could be rather long '
class ButtonILike(Button):
def get_text(self):
return long_text
class HomeScreen(Screen):
scroll_view = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(HomeScreen, self).__init__(*args, **kwargs)
layout1 = GridLayout(cols=1, spacing=0, size_hint=(1, None), \
row_force_default=False, row_default_height=40)
layout1.bind(minimum_height=layout1.setter('height'),
minimum_width=layout1.setter('width'))
layout1.add_widget(ButtonILike())
for result in i:
btn1 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle',)#, \
#height=self.texture_size[1], text_size=(self.width-10, None))
btn1.height = btn1.texture_size[1]
btn1.text_size = (btn1.width-20, layout1.row_default_height)
btn1.text = long_text
btn2 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle')
btn2.bind(text_size=(btn2.width-20, None))
btn2.text = 'or short'
layout1.add_widget(btn1)
layout1.add_widget(btn2)
scrollview1 = self.scroll_view
scrollview1.clear_widgets()
scrollview1.add_widget(layout1)
class mybuttonsApp(App):
def build(self):
return HomeScreen()
if __name__ == '__main__':
mybuttonsApp().run()
And the kv file:
#:kivy 1.7.2
<ButtonILike>:
text_size: self.width-10, None
size_hint: (1, None)
height: self.texture_size[1]
text: root.get_text()
#on_release: root.RunSearchButton_pressed()
<HomeScreen>:
scroll_view: scrollviewID
AnchorLayout:
size_hint: 1, .1
pos_hint: {'x': 0, 'y': .9}
anchor_x: 'center'
anchor_y: 'center'
Label:
text: 'Button Tester'
ScrollView:
id: scrollviewID
orientation: 'vertical'
pos_hint: {'x': 0, 'y': 0}
size_hint: 1, .9
bar_width: '8dp'
You can see that I added the button from the kv file which displays all the behavior that I want at the top of the list. Resize your window while running it, and you can see the magic. And, of course, changing the text_size also makes it possible for me to align text.
I simply have not been able to achieve the same behavior on the python side. My app requires that the buttons be created at run-time. I think the answer might lie with "bind()", though admittedly, I'm not sure I used it correctly in my attempts or that I understand it fully. You can see that I tried with "btn2", which I thought would've thrown the text to the left (since halign defaults to left), but didn't seem to do anything.
I appreciate the help.
I think the best way is to set Label's/Button's size to texture_size:
Label:
text: "test"
size_hint: None, None
size: self.texture_size
canvas.before: # for testing purposes
Color:
rgb: 0, 1, 0
Rectangle:
pos: self.pos
size: self.size
My answer is slightly different from #martin's - I only want to modify the height.
def my_height_callback(obj, texture: Texture):
if texture:
obj.height = max(texture.size[1], 100)
class MyButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint = (1, None)
self.bind(texture=my_height_callback)
When the text is rendered the texture property of the button gets set. That texture's height is then pushed to the button's height via the callback. Calling max() allows for a minimum height to be set. This works fine with labels as well.
btn2.bind(text_size=(btn2.width-20, None))
As with your other question, the problem is that you have the syntax of bind wrong. You must pass a function, but you just wrote a tuple, and bind can't do anything useful with that - it certainly doesn't know you happened to write btn2.width there.
Also, the syntax is that bind calls the function when the given property changes. That's the opposite of what you want - you need to change the text_size when btn2.width changes, not call a function when text_size changes
I think something like the following would work. instance and value are the default arguments we ignored in the other question.
def setting_function(instance, value):
btn2.text_size = (value-20, None)
btn1.bind(width=setting_function)
I was looking to resize both the text_size width and height, the latter specifically with regard to the documented behaviour of kivy.Label that vertical alignment of text in a label cannot be achieved without doing this first. Further, I needed to do it in python, not .kv.
class WrappedVAlignedLabel(Label):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.bind(height=lambda *x:self.setter('text_size')(self, (self.width, self.height)))
strangely, binding on width instead of height would only set text_size[0], I guess due to some order of rendering self.height wasn't yet computed, so the setting of text_size[1] wasn't happening. Whereas binding on height gets them both.
Related
This is a part of a python-script running kivy:
class someclass(Widget):
# code
# code
Clock.schedule_interval(self.timeandlog, 0.1)
self.x = 20
def timeandlog(self,dt):
if condition == True: #
self.ids.pofchild.add_widget(Label(text=logmsg, pos = (10, self.x)))
self.x = self.x + 10 ### just playing with position
condition = False
kv file:
<someclass>
#somelabels and buttons:
ScrollView:
do_scroll_x: False
do_scroll_y: True
pos: root.width*0.3, root.height*0.7
size: root.width*0.8, root.height*0.7
Widget:
cols: 1
spacing: 10
id: pofchild
Now I know the ScrollView accepts one Widget so I added just one with an id: pofchild then I added labels inside it with self.ids.pofchild.add_widget(Label() and changing every new label's pos with pos=(20, self.x) but the labels are not scrollable and only fill the widget height then stop appearing. What are right attributions so they will be scrollable?
In general, when you want a Widget to contain other Widgets, you should use a Layout Widget. A simple Widget does not honor size_hint or pos_hint, so the children of a simple Widget often end up with the default size of (100,100) and the default position of (0,0).
So, a good start is to change:
class someclass(Widget):
to something like:
class Someclass(FloatLayout):
Note that the class name starts with a capital letter. Although it does not cause any difficulties in your example, it can produce errors when you use kv and your classname starts with lower case.
Similarly, the child of the ScrollView is also normally a Layout. A possibility is GridLayout, like this:
GridLayout:
size_hint_y: None
height: self.minimum_height
cols: 1
spacing: 10
id: pofchild
Keys attributes here are the size_hint_y: None and height: self.minimum_height. They allow the GridLayout to grow as more children are added, and its height will be calculated as the minimum height needed to contain the children.
Then, you can add children like this:
self.ids.pofchild.add_widget(Label(text=logmsg, pos=(10, self.x), size_hint_y=None, height=50))
Since we are expecting the GridLayout to calculate its minimum height, we must provide an explicit height for its children, thus the size_hint_y=None, height=50.
I'm new to Kivy and Python in general and have visited this page many, many times. This time, however, I can't seem to find a good solution, or rather any solution, online. So, I figured I'd make my first post. If someone can help me figure this one out it would be greatly appreciated.
THE PROBLEM
For a one-look understanding of my problem, please see screenshots below. I am working on a GUI which shows a bunch of Items in a StackLayout. These items are Layouts themselves and also have a background color. When I first add these widgets, everything is working absolutely fine. When I remove them, though, the Layouts stack rightly back together, but they leave their canvases behind.
This seems like such a trivial problem that I thought there must be a one-liner fix. But as I said, I really couldn't find anything useful. Now, I kind of understand why that happens. My approaches would be to somehow link the Canvas' positioning dynamically to the widget's position, but I don't know how to go about that. What I also tried was erasing the canvas of the parent widget to redraw the backgrounds whenever I remove a line. But canvas.before.clear() does nothing and canvas.clear() empties everything and not just the background color.
CODE
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.stacklayout import StackLayout
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
Builder.load_string("""
<TopLayout>:
Button:
text: 'Remove middle line'
size_hint: 1, .1
on_press: root.remove_middle_line()
<Line>:
size_hint: 1, .1
Label:
text: 'This is a line with a background-color'
""")
class TestApp(App):
def build(self):
return TopLayout()
class TopLayout(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
global middle_line
middle_line = Line()
self.add_widget(Line())
self.add_widget(middle_line)
self.add_widget(Line())
def remove_middle_line(self):
self.remove_widget(middle_line)
class Line(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, delta=0):
if self.pos == [0, 0]:
Clock.schedule_once(self.init_ui, 0)
else:
with self.canvas.before:
Color(.4, .2, .2, .8)
Rectangle(size=self.size, pos=self.pos)
TestApp().run()
EXAMPLE
Before
After
Cheers and thank you for your help in advance.
EDIT:
Funnily, I figured it out right after publishing this post. I added a redraw method for which I created a list of all the lines. It redraws all background colors on line removal.
lines = []
...
def remove_middle_line(self):
self.remove_widget(middle_line)
Clock.schedule_once(self.redraw, 0)
def redraw(self, delta=0):
for line in lines:
line.canvas.before.clear()
with line.canvas.before:
Color(.4, .2, .2, .8)
Rectangle(size=line.size, pos=line.pos)
...
class Line(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.init_ui, 0)
lines.append(self)
Now it looks like this
But since this seems like a work-around, I would still like to leave this question open. Maybe someone has a suggestion of how to make those two move together instead of having to redraw all of the backgrounds every time I move lines. Thanks again, <3
This feels more like a blog that I'm doing than a question haha. I figured it out. It's not so difficult actually. This just shows me how valuable it is to reduce a problem to its core code. When there's hundreds of other lines it's considerably harder to see what's wrong. The solution for this case for me was to simply define the canvas in the Kivy-Code itself. Why I didn't want to do that in the first place was because of a boolean-based check that I am doing, resulting in different background colors. But I can overcome this by assigning a ListProperty and just changing it at initialization. So here is the simple solution, including the ListProperty.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.stacklayout import StackLayout
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
from kivy.properties import ListProperty
Builder.load_string("""
<TopLayout>:
Button:
text: 'Remove middle line'
size_hint: 1, .1
on_press: root.remove_middle_line()
<Line>:
size_hint: 1, .1
canvas.before:
Color:
rgba: self.bgcol
Rectangle:
size: self.size
pos: self.pos
Label:
text: 'This is a line with a background-color'
""")
class TestApp(App):
def build(self):
return TopLayout()
class TopLayout(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
global middle_line
middle_line = Line()
self.add_widget(Line())
self.add_widget(middle_line)
self.add_widget(Line())
def remove_middle_line(self):
self.remove_widget(middle_line)
class Line(StackLayout):
bgcol = ListProperty([.4, .2, .2, .8])
def __init__(self, **kwargs):
super().__init__(**kwargs)
# if something: self.bgcol = [.2, .5, .2, .8]
TestApp().run()
I will leave this alternative solution for anyone who might be interested:
Instead of redrawing the background canvas, you can relocate the old one. All you have to do for that is to assign the drawn rectangle as an attribute of the line and then change its position. I would still have to keep a list of all the lines but I think it's a much nicer solution than the one I had already found. The new and working code looks like this:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.stacklayout import StackLayout
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
Builder.load_string("""
<TopLayout>:
Button:
text: 'Remove middle line'
size_hint: 1, .1
on_press: root.remove_middle_line()
<Line>:
size_hint: 1, .1
Label:
text: 'This is a line with a background-color'
""")
lines = []
class TestApp(App):
def build(self):
return TopLayout()
class TopLayout(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
global middle_line
middle_line = Line()
self.add_widget(Line())
self.add_widget(middle_line)
self.add_widget(Line())
def remove_middle_line(self):
self.remove_widget(middle_line)
Clock.schedule_once(self.redraw, 0)
def redraw(self, delta):
for line in lines:
line.rect.pos = line.pos
class Line(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.init_ui, 0)
lines.append(self)
def init_ui(self, delta=0):
if self.pos == [0, 0]:
Clock.schedule_once(self.init_ui, 0)
else:
with self.canvas.before:
Color(.4, .2, .2, .8)
self.rect = Rectangle(size=self.size, pos=self.pos)
TestApp().run()
Thanks anyway and please feel free to add to this question if you have anything on your mind.
I had found a similar topic at stackoverflow but unluckily it didn't help me.
It's the first time I try to seriously program a GUI and I'm really getting mad.
I'm doing one step at a time, towards what I will finally need.
Now I'm trying to add a simple drop down menu in the top left corner of my widget, whose element should call a function whenever they are selected. I really looked for this in kivy documentation, and in this forum but I can't really solve this.
import multiprocessing
from mesh import MeshApp
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
import os
MAINDIR = os.path.dirname(os.path.realpath(__file__))
categories = {}
def getCategories():
for dir in os.walk(MAINDIR):
if len(dir[1]) == 0:
filelist = set()
for mesh in dir[2]:
filelist.add(mesh.replace('_FRONT.png','').replace('_SIDE.png','').replace('_TOP.png',''))
categories.update({dir[0]: filelist})
#class CategoriesList(DropDown):
# pass
class MainWindow(Widget):
def __init__(self):
#self.categorieslist = CategoriesList()
categories_list = DropDown()
for i in categories.keys():
btn = Button(text=i.replace(MAINDIR, ''), size_hint_y=None, height=30)
btn.bind(on_release=lambda btn: categories_list.select(btn.text))
categories_list.add_widget(btn)
mainbutton = Button(text='Choose directory', size_hint=(1, 1))
mainbutton.bind(on_release=categories_list.open)
categories_list.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
#and now???
class RenderApp(App):
def build(self):
self.launchMeshApp()
return MainWindow()
def launchMeshApp(self):
app = MeshApp()
p = multiprocessing.Process(target=app.run)
p.start()
if __name__ == '__main__':
getCategories()
RenderApp().run()
And:
#:kivy 1.9.1
<MainWindow>:
canvas.before:
Color:
rgba: 0.6, 0.6, 1, 1
Rectangle:
pos: self.pos
size: self.size
canvas:
Color:
rgba: 0, 0, 0, 0.5
Rectangle:
pos: 0, self.height * 5 / 6 - 1
size: self.width, 2
I've created the dropdown as seen in the docs and in several other forum. But I need to place it in the top left corner, and I never found, or understood, the way to do this. Moreover I didn't get how to make them call a function with a parameter whenever they are clicked.
Thank you very much
EDIT: I don't know why but the first line with "Hi all" is automatically deleted
I don't know about the dropdown menu, but I can answer the data one.
When you bind a callback, the first argument you receive will be which widget that is bound to the callback. So the idea is to create a class that uses Button as its base class, then you can define whatever extra information you need.
Here is a rough, non tested example based on the button API example:
class MyAwesomeButton(Button):
def __init__(self, **kwargs):
super(MyAwesomeButton, self).__init__(**kwargs)
self.my_data = {} # enter your data here
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
print instance.my_data
btn1 = MyAwesomeButton(text='Hello world 1')
btn1.bind(on_press=callback)
btn2 = MyAwesomeButton(text='Hello world 2')
btn2.bind(on_press=callback)
In my app, I want to handle background touches and widget touches separately. The Widget documentation ignores how to prevent bubbling from .kv events. Here's a little test case:
from kivy.app import App
class TestApp(App):
def on_background_touch(self):
print("Background Touched")
return True
def on_button_touch(self):
print("Button Touched")
if __name__ == "__main__":
TestApp().run()
And the .kv:
#:kivy 1.8.0
BoxLayout:
orientation: "vertical"
on_touch_down: app.on_background_touch()
padding: 50, 50
Button:
text: "Touch me!"
on_touch_down: app.on_button_touch()
The result: touching either the background or button triggers both handlers. Should I perform collision detection, or is there another way?
You should perform collision detection. For instance, in a class definition:
class YourWidget(SomeWidget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
do_stuff()
Edit: Actually, your method won't work anyway because the Button overlaps the BoxLayout. I would probably instead create a BoxLayout subclass and override on_touch_down, calling super first then if it returns False (indicating the touch hasn't been used yet) doing the BoxLayout interaction.
I wanted a solution that allows me to bind events from .kv files. #inclement solution won't allow me to do that because once you bind the event from .kv, you can't return True anymore to tell the parent you handled the event:
Button:
# you can't return True here, neither from the handler itself
on_touch_down: app.button_touched()
So what I've done is to perform collision detection at the parent, emitting a custom on_really_touch_down only if it doesn't hit any children, and performing collision detection yet again at the child, because all children receive the touch regardless of whatever (it's a mess, I know). Here's the complete solution (requires Kivy >= 1.9.0, because of the usage walk method):
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class CustomTouchMixin(object):
def __init__(self, *args, **kwargs):
super(CustomTouchMixin, self).__init__(*args, **kwargs)
self.register_event_type("on_really_touch_down")
def on_really_touch_down(self, touch):
pass
class CustomTouchWidgetMixin(CustomTouchMixin):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.dispatch("on_really_touch_down", touch)
return super(CustomTouchWidgetMixin, self).on_touch_down(touch)
class CustomTouchLayoutMixin(CustomTouchMixin):
def on_touch_down(self, touch):
for child in self.walk():
if child is self: continue
if child.collide_point(*touch.pos):
# let the touch propagate to children
return super(CustomTouchLayoutMixin, self).on_touch_down(touch)
else:
super(CustomTouchLayoutMixin, self).dispatch("on_really_touch_down", touch)
return True
class TouchHandlerBoxLayout(CustomTouchLayoutMixin, BoxLayout):
pass
class TouchAwareButton(CustomTouchWidgetMixin, Button):
pass
class TestApp(App):
def on_background_touch(self):
print("Background Touched")
def on_button_touch(self, button_text):
print("'{}' Touched".format(button_text))
if __name__ == "__main__":
TestApp().run()
The .kv:
#:kivy 1.9.0
TouchHandlerBoxLayout:
padding: 50, 50
on_really_touch_down: app.on_background_touch()
TouchAwareButton:
text: "Button One"
on_really_touch_down: app.on_button_touch(self.text)
TouchAwareButton:
text: "Button Two"
on_really_touch_down: app.on_button_touch(self.text)
So this allows me to bind touches from .kv.
Methods for binding touch events via .kv file/string syntax are possible, here's an example that modifies the caller's background when collisions are detected.
<cLabel#Label>:
padding: 5, 10
default_background_color: 0, 0, 0, 0
selected_background_color: 0, 1, 0, 1
on_touch_down:
## First & second arguments passed when touches happen
caller = args[0]
touch = args[1]
## True or False for collisions & caller state
caller_touched = caller.collide_point(*touch.pos)
background_defaulted = caller.background_color == caller.default_background_color
## Modify caller state if touched
if caller_touched and background_defaulted: caller.background_color = self.selected_background_color
elif caller_touched and not background_defaulted: caller.background_color = caller.default_background_color
background_color: 0, 0, 0, 0
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
And for completeness, here's how to use the above code within a layout that is touch activated only if none of the children (or grandchildren and so on) have also collided with the same event.
<cGrid#GridLayout>:
on_touch_down:
caller = args[0]
touch = args[1]
caller_touched = caller.collide_point(*touch.pos)
spawn_touched = [x.collide_point(*touch.pos) for x in self.walk(restrict = True) if x is not self]
## Do stuff if touched and none of the spawn have been touched
if caller_touched and True not in spawn_touched: print('caller -> {0}\ntouch -> {1}'.format(caller, touch))
cols: 2
size_hint_y: None
height: sorted([x.height + x.padding[1] for x in self.children])[-1]
cLabel:
text: 'Foo'
size_hint_y: None
height: self.texture_size[1]
cLabel:
text: 'Bar'
size_hint_y: None
height: self.texture_size[1] * 2
I may have gotten the texture_size's backwards, or perhaps not, but the height trickery can be ignored for the most part as it's purpose is to aid in making the parent layout more clickable.
The color changing and printing of caller & touch objects should be replaced with do_stuff() or similar methods, as they're there to make the example self contained, and show another way handling caller saved state when touched.
Summary of test application: I am writing a Kivy app with a scrollable view (named Scroller) with many fields (named Field) to look at. These separate fields are really difficult to distinguish on occasion, so I decided to use alternating background colors for each field to help distinguish each other. My testing application uses 20 individual fields each of which alternates between dark grey and darker grey.
Testing trials:
Starting the application, the program looks great. The alternating background appear just fine. Even when I scroll down the application looks fine. However, the application seems to get bizarre when I scroll up on the application. The text scrolls with the application, but the background does not. Even better (sarcastically), the text starts to fade away into their neighbors background. The problem just seems to vanish when I scroll down again (passed the point of the furthest scroll up point).
Brief problem description: The Field's "background color" messes up the application during scrolling up events.
Side note: I have also noticed that the application got a little sluggish after scrolling too much. I am not that familiar with the drawing cycle of Kivy, but blitting backgrounds should not yield an excessive slowdown.
Testing application:
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self):
ScrollView.__init__(self)
self.view = GridLayout(cols = 1, size_hint = (1, None))
self.add_widget(self.view)
self.view.bind(minimum_height = self.view.setter('height'))
for i in range(20):
self.view.add_widget(Field('Test field {}'.format(i),i%2 is 0))
class Field(GridLayout):
def __init__(self, name, bg):
assert isinstance(name, str)
assert isinstance(bg, bool)
self.bg = bg
GridLayout.__init__(self,
rows = 1,
padding = 10,
size = (0, 60),
size_hint = (1, None))
self.add_widget(Label(text = name))
self.add_widget(Button(text = 'Test button',
size = (200, 0),
size_hint = (None, 1)))
self.bind(pos = self.change_background)
self.bind(size = self.change_background)
def change_background(self, *args):
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()
def change_background(self, *args):
self.canvas.before.clear()#<- clear previous instructions
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
You are adding/piling instructions to the canvas every time the Field's position/size changes, without clearing the previous instructions.
You should also look into using kv as for anything more than a small snippet it ends up saving you a lot of time. You can convert you code using kv like so ::
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<Scroller>
# root is Scroller here
# create a new ObjectProperty in kv that holds the ref to Gridlayout
# so you can access the instance in python code
view: glayout
GridLayout:
id: glayout
cols: 1
size_hint: (1, None)
height: self.minimum_height
<Field>
canvas.before:
Color:
rgba: (0.2, 0.2, 0.2, 1) if self.bg else (0.1, 0.1, 0.1, 1)
Rectangle:
# binding properties is done implicitly and instructions aren't
# piled up while doing that.
pos: self.pos
# self here refers to Field as `self` is supposed to refer to the
# Widget not the drawing instruction
size: self.size
rows: 1
padding: 10
size: (0, 60)
size_hint: (1, None)
Label:
text: root.name
Button:
text: 'test button'
size: (200, 0)
size_hint: (None, 1)
''')
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self, **kwargs):
super(Scroller, self).__init__(**kwargs)
for i in range(20):
# access self.view that was set in kv
self.view.add_widget(
Field(
name = 'Test field {}'.format(i),
bg = i%2 is 0))
class Field(GridLayout):
# use kivy's Properties so it becomes easier to observe and apply changes
# as a plus these can also be directly used in kv. As a advantage of using this now
# you can change name and bg dynamically and the changes should be reflected on
# screen
name = ObjectProperty('Test field uninitialized')
bg = BooleanProperty(False)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()