Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Handling touch or mouse input, more canvas instructions
In this tutorial we’ll directly add touch handling to the basic code
developed in tutorial 5, starting with the code from last time:
from kivy.app import App
from
... [More]
kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
Color(1, 0, 0, 1) # note that we must reset the colour
Rectangle(size=(300, 100),
pos=(300, 200))
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
class DrawingApp(App):
def build(self):
root_widget = DrawingWidget()
return root_widget
DrawingApp().run()
We’ve already seen some input interaction via the Button widget, where
we could bind to its on_press event to have a function called
whenever the Button was clicked. This is great for a Button, but is
not a general way to handle interaction - it gives no indication of
the position of the touch, or any other information like the button
clicked on the mouse.
Kivy achieves general mouse/touch handling via the
on_touch_down, on_touch_move and on_touch_up
methods of all Widget classes. Let’s dive in with an example,
modifying our DrawingWidget:
from random import random
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.rect_colour = Color(1, 0, 0, 1) # note that we must reset the colour
Rectangle(size=(300, 100),
pos=(300, 200))
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
self.rect_colour.rgb = (random(), random(), random())
print('touch pos is {}'.format(touch.pos))
Note that the only changes are to set self.rect_colour, and to
add the on_touch_down method. Run the code now, and whenever
you click the screen you should see the colour of the rectangle change.
How does this work? The answer is that whenever a mouse click or touch
is registered, the root widget’s on_touch_down method is
called, with a touch object holding information about the
touch: you can see this here, where we access the pos of this
object to get the pixel coordinates of its position. Each widget
passes this touch object to all its children. For this reason, it’s
important to call super(...) if you want the touch to also be
passed to the current Widget’s children, though as it happens that’s
not actually important here.
Note that although these methods are called on_touch_..., and
I’ve called the argument touch, they relate to both mouse and
touch handling; these events are handled in exactly the same way,
except that the touch object may contain different information such as
the button clicked (in the case of the mouse). I’ll switch to mostly
referring to this input as ‘touch’, but this always includes mouse
interaction too.
The other methods I mentioned, on_touch_move and
on_touch_up, work the same way; they are called whenever that
thing happens, though only when on_touch_down has already
happened, you don’t get events when moving the mouse without having
clicked. We can use this to achieve drawing.
First, change the kivy.graphics import to include Line:
from kivy.graphics import Rectangle, Color, Line
Then, add modify on_touch_down and on_touch_move to
draw and update a Line each time:
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
Run the code again, and try clicking and dragging…you should see a
line! Each time you click and drag the line has a different colour, as
we add a new random Color instruction before its instruction each
time. We’re updating it by adding the x and y value of the touch
position to the Line’s points, every time the touch is moved.
You can also note that we only use with self.canvas when the
Line is instantiated - not when it is updated. This is because we only
need to add the Line canvas instruction to the canvas once, after that
the gui will automatically be updated whenever the Line changes,
including if we modified e.g. its width. Try changing
self.line.width in on_touch_move and see what happens.
Note
This way of storing the line (in self.line) isn’t
very robust if there are multiple simultaneous interactions,
e.g. in a multitouch display. This is easy to resolve by
storing the reference more cleverly, including in the touch
object itself, but I’ve just ignored the issue here.
You could continue here by experimenting with other actions in
response to touches, such as drawing different things (e.g. a
Rectangle at the touch position rather than a Line) or doing more
complex modifications to existing instructions.
With the basic drawing apparatus set up, the next tutorial will
introduce the kv markup language, showing how a gui can easily be
constructed without some of the Python boilerplate that comes from
using a general purpose language for creating a gui.
Full code
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.rect_colour = Color(1, 0, 0, 1) # note that we must reset the colour
Rectangle(size=(300, 100),
pos=(300, 200))
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class DrawingApp(App):
def build(self):
root_widget = DrawingWidget()
return root_widget
DrawingApp().run()
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Canvas instructions
The next couple of tutorials will move to a new application in order
to showcase some more of Kivy’s core components. In this tutorial
we’ll cover canvas instructions, Kivy’s low level drawing ...
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Events and Kivy properties
We left the last tutorial with a calculator app GUI with some nice
automatic behaviour, but which doesn’t actually do anything. Let’s
change that; it’s time to learn about binding events.
To refresh, the
... [More]
basic calculator GUI code was as follows. If you
modified it to experiment, feel free to continue with your modified
code, and try to change the instructions to fit your modifications:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = BoxLayout(orientation='vertical')
output_label = Label(size_hint_y=1)
button_symbols = ('1', '2', '3', '+',
'4', '5', '6', '-',
'7', '8', '9', '.',
'0', '*', '/', '=')
button_grid = GridLayout(cols=4, size_hint_y=2)
for symbol in button_symbols:
button_grid.add_widget(Button(text=symbol))
clear_button = Button(text='clear', size_hint_y=None,
height=100)
root_widget.add_widget(output_label)
root_widget.add_widget(button_grid)
root_widget.add_widget(clear_button)
return root_widget
YourApp().run()
Note
This tutorial introduces some major new Kivy concepts. I
recommend working through it even if you don’t entirely
follow what’s going on, then going back to modify components
and see how things change.
The plan now is that every one of these buttons should add their
symbol to the Label at the top, except ‘=’ which should evaluate the
code and display the result. This is obviously an extremely basic
calculator, but the point here is to showcase some Kivy basics - if
you’d like to improve the interface, go ahead with trying to do so
along the way.
To make the buttons do something, we must bind to their events. This
is a generic Kivy concept; whenever you want one thing to trigger
another, you look for an event to bind to. Some widgets such as Button
have events indicating they have been clicked on, and every Kivy
property (such as all those used to customise Widgets so far) has an
associated event when it changes.
Let’s start with a simple binding example:
def print_button_text(self, instance):
print(instance.text)
for button in button_grid.children[1:]: # note use of the
# `children` property
button.bind(on_press=print_button_text)
# we could also have used `button.bind(on_press=lambda instance: print(instance.text))`
If you run the code now, and click on any of the buttons, you should
see its text printed in the console (but not in the Kivy GUI).
The key concept here is the bind method, which you can use
with any Widget, as well as several other Kivy objects (discussed in
later tutorials). This takes any number of keyword arguments, each
specifying an event name and a function to call; in this case the
event name is on_press, and the function to be called is our
new print_button_text`. The ``bind` method makes sure that
whenever on_press occurs, the function is called. It
automatically receives a single argument, the binded widget instance.
Also note that we’ve iterated over
button_grid.children[1:]`. The ``children` property is
available on any Widget, and holds a list of all the widgets added to
it, in reverse order. In this case, we use [1:] to skip the
first element, ‘=’, as we want to use this to evaluate the result.
Note
Button also has an on_release event that is called
when the user releases a click or touch. You can find more
information in the Button documentation.
This binding idea is very normal in Kivy, and you’ll quickly get used
to seeing it used in different ways, including some introduced later
in these tutorials. The kv markup language, also introduced later,
has special syntax designed to make it even simpler and clearer.
Anyway, all this does so far is print some text when the event occurs,
but we want to update the GUI. Let’s change the bound function to
achieve that:
def print_button_text(instance):
output_label.text += instance.text
Run the code again. Now when you press the buttons, you should see the
text appear at the top of the screen, as in the screenshot below:
At this point, a new problem presents itself; the font size of the
label is kind of small. We can use another event to have it update
automatically in response to the label’s height:
def resize_label_text(label, new_height):
label.font_size = 0.5*label.height
output_label.bind(height=resize_label_text)
Note that the event here is named height. This works because
the Label has a Kivy property named height (as do all Widgets, see the
documentation,
and all Kivy properties can be bound to as an event of the same name,
called when the property changes. In this case, you can now resize the
window, which causes the layouts in the Widget tree to automatically
resize their children, which in turn causes resize_label_text
to automatically be called.
We’ll use one final binding to make the calculator interface actually
work; when the ‘=’ button is pressed, we can evaluate the entire label
text as python code, and display the result.
Note
Using eval as a calculator like this is in general a
terrible idea, used here only to avoid dwelling on the
details rather than the Kivy principles.
def evaluate_result(instance):
try:
output_label.text = str(eval(output_label.text))
except SyntaxError:
output_label.text = 'Python syntax error!'
button_grid.children[0].bind(on_press=evaluate_result)
# Remember, button_grid.children[0] is the '=' button
Further, we can make the ‘clear’ button clear the label, so that you
can start a new calculation:
def clear_label(instance):
output_label.text = ''
clear_button.bind(on_press=clear_label)
With this all in place, run the app again and…the calculator works!
Every button now does something, either adding its symbol to the
output label, evaluating the label’s text as python code, or clearing
the result. You should be seeing something like the image below:
These core event binding concepts are central to working with Kivy
widgets, and come up in many different ways. Don’t worry if you don’t
remember all the details straight away, such as the way all properties
have events you can bind to, or the specific syntax; you can look all
this up in the documentation as linked throughout and indexed on the
Kivy website. Later
tutorials also follow on to help cement this knowledge.
Full code
your_filename.py:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = BoxLayout(orientation='vertical')
output_label = Label(size_hint_y=1)
button_symbols = ('1', '2', '3', '+',
'4', '5', '6', '-',
'7', '8', '9', '.',
'0', '*', '/', '=')
button_grid = GridLayout(cols=4, size_hint_y=2)
for symbol in button_symbols:
button_grid.add_widget(Button(text=symbol))
clear_button = Button(text='clear', size_hint_y=None,
height=100)
def print_button_text(instance):
output_label.text += instance.text
for button in button_grid.children[1:]: # note use of the
# `children` property
button.bind(on_press=print_button_text)
def resize_label_text(label, new_height):
label.font_size = 0.5*label.height
output_label.bind(height=resize_label_text)
def evaluate_result(instance):
try:
output_label.text = str(eval(output_label.text))
except SyntaxError:
output_label.text = 'Python syntax error!'
button_grid.children[0].bind(on_press=evaluate_result)
def clear_label(instance):
output_label.text = ''
clear_button.bind(on_press=clear_label)
root_widget.add_widget(output_label)
root_widget.add_widget(button_grid)
root_widget.add_widget(clear_button)
return root_widget
YourApp().run()
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Adding Widgets to one another
The tutorals so far have covered the very basics of a Kivy
application; getting everything running, adding a Widget (the Label),
and doing some customisation.
Let’s now combine some widgets to make a
... [More]
larger GUI. This tutorial
will solely cover joining the widgets together, not making them do
anything; this is covered in later tutorials.
Note
This tutorial will construct the GUI using entirely Python
code. You can always do this with Python as described here,
but normally we recommend using the easier, clearer and more
concise kv language to construct
widget trees. This will be covered fully in later tutorials.
Our new task will be to build a simple calculator app; we’ll need
Buttons for each of the numbers and mathematical operations, and a
Label to display the result.
Let’s start with a new basic app structure:
from kivy.app import App
class YourApp(App):
def build(self):
return None
YourApp().run()
Right now, you can run the code but the window will be empty because
we didn’t add any widgets. Let’s do that now, but we no longer want
just a Label; our app will be made of multiple Widgets next to one
another. For this, we use Layout classes; let’s start with the following:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class YourApp(App):
def build(self):
layout = BoxLayout(orientation='vertical')
b1 = Button(text='button 1')
b2 = Button(text='button 2')
layout.add_widget(b1)
layout.add_widget(b2)
return layout
YourApp().run()
We’re now instantiating three Widget classes; the BoxLayout and two
Buttons. Just like with the Label, each one can be customised by
passing properties. The only new one here is the orientation
of the BoxLayout; passing 'vertical' means it will place its
children below one another. The Buttons are internally a Label with a
background image and touch behaviour (you can see this in the Button
documentation,
check the ‘Bases:’), so we can use the Label’s text property just like before.
After instantiating the widgets, we can add them to one another. You
can almost always add any widget instance to any other in exactly this
way. When you do so, the newly added widgets will appear on the
screen, and you’ll be able to interact with them. The widget you add
to is called the parent widget, and the added widget (in this case
the Buttons) is the child widget.
This code should give you something like the following image. You can
also now click the buttons to see their colour change; this behaviour
is automatic, they don’t do anything else yet.
Try setting the BoxLayout orientation to 'horizontal' to see
how it affects the result.
Resize the window, and note that the sizes and positions of the
buttons update automatically. This happens because the BoxLayout
repositions and resizes its children when its own size changes, and
because it is the root widget its own size tracks that of the
window. This is very important! If you replace the BoxLayout with
a plain Widget (from kivy.uix.widget import Widget) this will
not happen, the Buttons will both have their default position and
size in the bottom left of the window. For this reason, you’ll want to
use Layouts like BoxLayout all the time to automatically position
things, though you can also create your own automatic bindings (see
later tutorials on Kivy Properties).
With these basic ideas in hand, let’s proceed to add Widgets
representing our entire calculator interface:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = BoxLayout(orientation='vertical')
output_label = Label(size_hint_y=1)
button_symbols = ('1', '2', '3', '+',
'4', '5', '6', '-',
'7', '8', '9', '.',
'0', '*', '/', '=')
button_grid = GridLayout(cols=4, size_hint_y=2)
for symbol in button_symbols:
button_grid.add_widget(Button(text=symbol))
clear_button = Button(text='clear', size_hint_y=None,
height=100)
root_widget.add_widget(output_label)
root_widget.add_widget(button_grid)
root_widget.add_widget(clear_button)
return root_widget
YourApp().run()
This introduces a couple of new ideas; the GridLayout is a new layout
class that arranges its child widgets in (you guessed it) a
grid. We’ve set its cols` property to ``4`, which means
that every 4 widgets we add it will start a new row. Since we add 16
buttons altogether, that’s 4 rows of 4.
The other new idea here is the size_hint_y setting for the
output_label and button_grid. All widgets have a size_hint_x
(horizontal) and size_hint_y (vertical) that you can set. They
are used by Layout classes to set relative sizes; in this case, the
the one with size_hint_y=2 takes up twice as much vertical
space as the one with size_hint_y=1.
You can also override the size hint to set a manual width and/or
height for your Widget, but you must do this explicitly, as shown here
with the ‘clear’ button. By setting size_hint_y=None, we
ensure that its height=100 is never overridden, this Button
will have a height of 100 pixels no matter what.
Your final code should look something like the image below. You can
resize the window to see all the components move around and resize
automatically, thanks to the use of Layouts for positioning.
You are strongly encouraged to experiment with modifying this code
to see what happens. All the concepts used here are standard when
working with Kivy widget positioning.
The calculator GUI clearly doesn’t do anything yet (although you can
click on the buttons due to their default behaviour). Adding some
functionality is covered in the next tutorial.
Full code
your_filename.py:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = BoxLayout(orientation='vertical')
output_label = Label(size_hint_y=1)
button_symbols = ('1', '2', '3', '+',
'4', '5', '6', '-',
'7', '8', '9', '.',
'0', '*', '/', '=')
button_grid = GridLayout(cols=4, size_hint_y=2)
for symbol in button_symbols:
button_grid.add_widget(Button(text=symbol))
clear_button = Button(text='clear', size_hint_y=None,
height=100)
root_widget.add_widget(output_label)
root_widget.add_widget(button_grid)
root_widget.add_widget(clear_button)
return root_widget
YourApp().run()
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Modifying Widget appearance, Kivy properties
It’s great to say Hello World, but it looks pretty boring, and you’d
expect that you’d be able to customise the appearance of
text. Fortunately, you’d be right…so let’s do it.
We’ll
... [More]
continue modifying the code from last time, which was:
from kivy.app import App
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = Label(text='Hello world!')
return root_widget
YourApp().run()
The basic way to modify things in Kivy is to change Kivy properties
of the widgets. As far as we’re concerned right now, we can set these
by passing arguments at instantiation, or by treating them as
attributes of the class. For instance, we could also have set the text
as follows:
root_widget = Label()
root_widget.text = 'Hello world!'
Let’s set ourselves three goals:
Make the text larger
Italicise the text
Colour "Hello" and "world!" differently
To customise the Label appearance, we must check the documentation to
find an appropriate Kivy property. For the text size, check the Label
doc and find the
font_size listing. It looks something like the following:
Following the documentation, this lets us set the font size in pixels,
and it defaults to '15sp'. This is a special Kivy syntax, the sp
units automatically scale the font size according to the DPI of the
display and the user’s font size setting (on some platforms); on
desktop on a non-hidpi display, it is just 15 pixels. For now let’s
just set a simple pixel number:
root_widget = Label(
text='Hello world!',
font_size=100)
You can run the code now to see the result.
To make the text italic, the procedure is the same. Check the Label doc and find the
italic property entry. you’ll see that this is a
BooleanProperty that defaults to False; just set it to True to enable
the underline:
root_widget = Label(
text='Hello world!',
font_size=100,
italic=True)
Finally, we want to colour Hello and world!
differently. Things are a little different here as we can’t use a
single property setting to modify the whole string, since the two
words should be treated differently.
Instead we enable the markup property:
root_widget = Label(
text='Hello world!',
font_size=100,
underline=True,
markup=True)
You can now use Kivy’s markup syntax to
modify the text within the Label. Try the following:
root_widget = Label(
font_size=100,
italic=True,
markup=True)
root_widget.text = '[color=#ff0000]Hello[/color] [color=#00ff00]world![/color]'
Now run the application again, python your_filename.py. The
result should now look something like the following image.
Note
This is just a basic introduction to customising Kivy
widgets, you can use similar methods to accomplish many
different changes in many different scenarios. Kivy
properties also have other important functionality, covered
later in these tutorials.
Full code
The full code for this exercise was:
from kivy.app import App
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = Label(
font_size=100,
italic=True,
markup=True)
root_widget.text = '[color=#ff0000]Hello[/color] [color=#00ff00]world![/color]'
return root_widget
YourApp().run()
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Starting an App, getting Kivy running
It’s compulsory that the introduction to any programming project
should contain a "Hello World!" application. Since Kivy is a GUI
framework, that means starting an application and displaying the
... [More]
words
on the screen. Start by adding each of the following lines to your program:
from kivy.app import App
This imports the App class, something you’ll be using in any
Kivy application. Your instance of this class will create the Kivy
window and serve as the top level of your application
from kivy.uix.label import Label
This introduces one of Kivy’s most important components; the
Widget. Your entire application will be built with Widgets, each of
which does a single (relatively) small task. For instance, Label is a
Widget that displays some text, Button is (obviously) a button, and
Layouts are Widgets that contain other Widgets and lay them out in
some particular arrangement.
You can find the documentation for Label here. We’ll need this later!.
In every Kivy application, your first task is to create an App
subclass as follows:
class YourApp(App):
def build(self):
root_widget = Label(text='Hello world!')
return root_widget
The build method is the only important addition you have to
make, and is the usually the first entry point for your use of a Kivy
application. This method must instantiate and return what will be your
root widget, the top level widget of your Kivy application, which
will contain anything else you add.
The root widget will automatically fill the window, so in this case
the Label text will appear right in the middle.
In our case, the application will only ever need a single Widget; the
Label displaying our text. We set the text by simply passing it as an
argument. This works automatically because text is a Kivy
property of the Label widget…but that doesn’t matter right now.
Finally, add a line to start and run the Application:
YourApp().run()
This instantiates and runs the instance of your App. Any Kivy
application is created and started with some variation of these
basic steps.
Now…run the code!
python your_filename.py
You should see a Window something like the following
image. Congratulations, you’ve written and run your first Kivy application.
Full code
your_filename.py:
from kivy.app import App
from kivy.uix.label import Label
class YourApp(App):
def build(self):
root_widget = Label(text='Hello world!')
return root_widget
YourApp().run()
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
python-for-android is
a packaging tool for Python apps on Android. You can create your own
Python distribution including the modules and dependencies you want,
and bundle it in an APK along with your own code.
python-for-android 2019.08.09 has ju...
|
Posted
almost 6 years
ago
by
Alexander Taylor
A user on the Kivy Discord
just raised the question of how to delay widget updates during resize
events. The problem was that the widgets did some heavy processing
(generating matplotlib graphs) that would be very slow if called for
every tiny update
... [More]
during a larger resize event.
This is a good opportunity to return to the flexibility of Kivy
layouts. It’s very easy to add some simple behaviour that delays
updates until a short period has passed without the size
changing. Here’s a quick implementation I threw together:
from kivy.uix.anchorlayout import AnchorLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty, NumericProperty
from functools import partial
class DelayedResizeLayout(AnchorLayout):
do_layout_event = ObjectProperty(None, allownone=True)
layout_delay_s = NumericProperty(0.2)
def do_layout(self, *args, **kwargs):
if self.do_layout_event is not None:
self.do_layout_event.cancel()
real_do_layout = super().do_layout
self.do_layout_event = Clock.schedule_once(
lambda dt: real_do_layout(*args, **kwargs),
self.layout_delay_s)
This layout could be used as the root widget of a whole application,
to delay the resizing of all the content, or somewhere within the app
to delay only a small part of it.
And a simple example:
from kivy.uix.button import Button
from kivy.base import runTouchApp
button = Button(text='example button')
layout = DelayedResizeLayout()
layout.add_widget(button)
runTouchApp(layout)
[Less]
|
Posted
almost 6 years
ago
by
Alexander Taylor
One of the biggest Kivy confusions I see is how different widgets can
access one another when they’re in different parts of the
application. Fortunately, it’s generally straightforward to do so. This
post gives examples of methods you might use in
... [More]
different situations.
The emphasis here is on what you can do, not what you should. If
you aren’t sure which way is best in a given situation, go ahead and
choose what seems best at the time, but don’t be afraid to revisit it later.
How can a widget access its parent?
Use the parent property of the widget:
child = Widget()
parent = Widget()
assert child.parent is None # child widget isn't added to any parent
parent.add_widget(child)
assert child.parent == parent
How can a widget access its children?
Use the children property. This is a list containing all the children you added.
child = Widget()
parent = Widget()
assert len(parent.children) == 0 # no children added to parent
parent.add_widget(child)
print(parent.children) # []
assert child in parent.children
Note: The children list is actually backwards, i.e. the last
widget you add will by default be the first in the
list. This is a bit surprising, and only really happens for
backwards compatibility.
How can a widget in Python access children from its kv rule?
Option 1: ids
You can give the widgets in your kv rule ids to access them from Python.
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
class KvRuleWidget(BoxLayout):
def on_touch_down(self, touch):
print('We can get references to all the children using the ids dict.')
# syntax is `self.ids.`
assert self.ids.middle in self.children
assert self.ids.bottom in self.children
# widgets can be accessed from deep in the kv rule
assert self.ids.top_left not in self.children
assert self.ids.top_right not in self.children
class ExampleApp(App):
def build(self):
return KvRuleWidget()
# example.kv
<KvRuleWidget>:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
Label:
id: top_left
text: 'top left'
Label:
id: top_right
text: 'top right'
Label:
id: middle
text: 'middle'
Label:
id: bottom
text: 'bottom'
Note: You cannot set up widget ids from Python code, if
you write e.g. w = Widget(id='some_name') this will not
crash but the id will not be available in any ids dictionary.
Option 2: properties
You can use Kivy properties to pass around references to widgets.
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty
class KvRuleWidget(BoxLayout):
top_right_label = ObjectProperty()
def on_touch_down(self, touch):
print('The top right label is {}'.format(self.top_right_label))
class ExampleApp(App):
def build(self):
return KvRuleWidget()
# example.kv
<KvRuleWidget>:
orientation: 'vertical'
top_right_label: top_right # note that we used an id to set the property
BoxLayout:
orientation: 'horizontal'
Label:
id: top_right
text: 'top left'
Label:
text: 'top right'
Label:
text: 'middle'
Label:
text: 'bottom'
Option 3: The parent and children properties
It is possible to walk through the widget tree using the parent and children properties.
This is usually a bad idea and is prone to breakage if the structure
of the widget tree changes. However, it’s still possible.
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty
class KvRuleWidget(BoxLayout):
def on_touch_down(self, touch):
# get a reference to the top right label only by walking through the widget tree
top_right_label = self.children[-1].children[0]
print('The top right label is {}'.format(self.top_right_label))
class ExampleApp(App):
def build(self):
return KvRuleWidget()
# example.kv
# note: this time there are no ids at all
<KvRuleWidget>:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
Label:
text: 'top left'
Label:
text: 'top right'
Label:
text: 'middle'
Label:
text: 'bottom'
How can a widget in Kv access children defined in Python?
Sometimes you might have some children defined via a Kv rule, and
others created dynamically in Python. You can access the Python
widgets in kv by saving references to them in Kivy properties:
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty
class KvRuleWidget(BoxLayout):
label_created_in_python = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
# add a widget from python code
label = Label(text='label created in Python')
self.add_widget(label)
self.label_created_in_python = label # save a reference
class ExampleApp(App):
def build(self):
return KvRuleWidget()
# example.kv
<KvRuleWidget>:
orientation: 'vertical'
Label:
text: 'label created in Kv'
Label:
text: 'the label created in Python has text "{}"'.format(root.label_created_in_python.text)
How can a widget defined in a kv rule access a widget defined in another kv rule?
Sometimes you might have two widgets in very different places that
need to talk to one another somehow. Usually the best way to achieve
this is to consider how they are related to one another, and pass
information between them via their common relations.
Also see the next Section for how to access any widget from anywhere,
without worrying about how the widgets are related. However, that
usually isn’t such a good choice in the long run.
The following example is deliberately very simple, but the same
principles can be used to link together widgets across your whole
program using references passed around where the kv rules meet.
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty
class IncrementCounterButton(Button):
counter = NumericProperty(0)
def on_press(self):
self.counter += 1
class CounterLabel(Label):
counter = NumericProperty(0)
class RootWidget(BoxLayout):
pass
class ExampleApp(App):
def build(self):
return RootWidget()
# example.kv
<IncrementCounterButton>:
text: 'press me'
<CounterLabel>:
text: 'the counter value is {}'.format(app.counter) # `app` in kv is equivalent to `App.get_running_app()` in Python
<RootWidget>:
orientation: 'vertical'
CounterLabel:
counter: button.counter # this means the CounterLabel's counter will always match the button's counter
IncrementCounterButton:
id: button
How can any widget access any other widget from anywhere?
Sometimes you really do want widgets to interact with one another
without any good relationship between them. You can do this in a
convenient way by using a Kivy property in the App class.
Note: This is notionally similar to using a global variable, and
is often bad practice for all the same reasons.
The following example is quite contrived to keep it simple. In this
case you could probably think of a better way to do the same thing,
perhaps using the methods from the previous Sections.
# main.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty
class IncrementCounterButton(Button):
def on_press(self):
# You can always access your App class from Python as follows:
App.get_running_app().counter += 1
class CounterLabel(Label):
pass
class ExampleApp(App):
counter = NumericProperty(0)
def build(self):
boxlayout = BoxLayout(orientation='vertical')
label = CounterLabel()
button = IncrementCounterButton()
boxlayout.add_widget(label)
boxlayout.add_widget(button)
return boxlayout
# example.kv
<IncrementCounterButton>:
text: 'press me'
<CounterLabel>:
text: 'the counter value is {}'.format(app.counter) # `app` in kv is equivalent to `App.get_running_app()` in Python
[Less]
|
Posted
almost 6 years
ago
by
Alexander Taylor
python-for-android is
a packaging tool for Python apps on Android. You can create your own
Python distribution including the modules and dependencies you want,
and bundle it in an APK along with your own code.
python-for-android 2019.06.06 has ju...
|