12
I Use This!
Moderate Activity

News

Analyzed about 15 hours ago. based on code collected about 15 hours ago.
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...