Trying to use inheritance in kivy python - python

I am trying to create a simple programe that animates my label widget to desired position upon clicking on the kivy window. Just trying to experiment with inheritance to access all the elements in the parent element.
ani is the parent class and transit is the child class. I am trying to access the label attribute in transit via inheritance. But this throws error.
from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.uix.widget import Widget
class transit(ani,Widget):
def __init__(self,**kwargs):
ani.__init__(self,**kwargs)
def on_touch_down(self,touch):
val = 5
print(touch.x,touch.y)
self.val +=10
animation = Animation(x = touch.x,y =touch.y,font_size=self.val,d=2,t='in_out_quad')
animation.start(self.parent.label)
class ani(App):
def __init__(self,**kwargs):
self.label = Label(text='increase')
def build(self):
return transit()
root = ani()
root.run()

I believe you are trying to use inheritance with the wrong class. If you want to move a Label, you need to put the Label in a container and make sure the size of the Label does not fill the container. Since the position of the Label is controlled by its container, you need to use inheritance with the container. Here is a simple code, similar to yours, that uses a FloatLayout as the container:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.animation import Animation
class MyFloatLayout(FloatLayout):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
val = 5
label = App.get_running_app().label
animation = Animation(x = touch.x,y =touch.y,font_size=val,d=2,t='in_out_quad')
animation.start(label)
return super(MyFloatLayout, self).on_touch_down(touch)
class ani(App):
def build(self):
root = MyFloatLayout()
self.label = Label(text='increase', size_hint=(None, None), size=(100,40))
root.add_widget(self.label)
return root
if __name__ == '__main__':
ani().run()
This code uses inheritance to define a new MyFloatLayout class that adds a new on_touch_down() method that does the animation. Note that the new on_touch_down() also calls super(MyFloatLayout, self).on_touch_down(touch). Unless there is a specific reason for not doing that, you normally want to call the super method in an inherited method.

Related

Black Screen When Using on_enter Calling a Function with Kivy/Python

I'm new to Python, and especially new to Kivy.
I'm sure that whatever I'm doing is a simple fix, but I just cannot figure it out for the life of me.
I've been doing this all in a Python file, with no my.kv.
What I'm trying to do is call a function upon entering the first screen of my app, but when I do this it just gives me a blank screen.
Sorry if my code is an absolute mess.
This is my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
import requests
import json
Builder.load_string("""
<Manager>:
BuildScreen:
SubmitScreen:
<BuildScreen>:
name: 'page1'
on_enter: app.BuildAnswer()
<SubmitScreen>:
name: 'page2'
on enter: app.SubmitAnswer()
GridLayout:
cols:1
row_force_default:True
row_default_height:40
Button:
text:"Return"
on_release: root.manager.current = 'page1'
""")
class MainWidget(Widget):
pass
class Manager(ScreenManager):
pass
class BuildScreen(Screen):
pass
class SubmitScreen(Screen):
pass
class TheLabApp(App):
def __init__(self,**kwargs):
super(TheLabApp, self).__init__(**kwargs)
def BuildAnswer(self):
layout = GridLayout(cols=1, row_force_default=True, row_default_height=40)
self.spell = TextInput(hint_text = "Enter Spell", multiline=False)
button = Button(text="Get Spell", on_release=self.SubmitAnswer)
layout.add_widget(self.spell)
layout.add_widget(button)
return layout
def SubmitAnswer(self):
user_input = self.spell.text
#making input into url ready thingy
making_string = ''.join(str(x) for x in user_input)
x = '-'.join(making_string.split())
url = requests.get('https://www.dnd5eapi.co/api/spells/' + x)
#making it look pretty
pretty_spells = json.dumps(url.json(), indent=2)
#making it so I can get values from json
resp = json.loads(pretty_spells)
print(resp['name'])
print(resp['range'])
#the rest is just printing more of the spell's information
def build(self):
sm=ScreenManager()
sm.add_widget(BuildScreen(name="page1"))
sm.add_widget(SubmitScreen(name="page2"))
return sm
Any and all help would be incredibly appreciated, as I've been trying to find a solution for a couple days now.
A couple problems with your code.
First, the BuildAnswer() method creates and returns a layout, but returning something from an on_enter method has no effect. If you want that layout to appear in the BuildScreen, you must explicitly add that layout to the BuildScreen instance.
Second, defining the BuildAnswer() method in the TheLabApp class makes it difficult to access the BuildScreen instance. This is because the on_enter method is triggered very early in the process (before the root of the App is assigned).
I suggest moving the BuildAnswer() method to the BuildScreen class, and calling add_widget() to actually add the created layout to the BuildScreen instance. To do that, start by modifying your kv:
<BuildScreen>:
name: 'page1'
on_enter: self.BuildAnswer() # reflects new location of BuildAnswer()
And modifying your python code:
class BuildScreen(Screen):
def BuildAnswer(self):
layout = GridLayout(cols=1, row_force_default=True, row_default_height=40)
self.spell = TextInput(hint_text="Enter Spell", multiline=False)
button = Button(text="Get Spell", on_release=App.get_running_app().SubmitAnswer) # to access the SubmitAnswer method
layout.add_widget(self.spell)
layout.add_widget(button)
self.add_widget(layout) # add layout to the GUI
# return layout
And remove the BuildAnswer() method from the TheLabApp class.

How to change screen by swiping in kivy python

I am trying to change to another screen by swiping the screen. I've tried carousel but it seems that it only works with images, so I've tried detecting a swipe motion and changing the screen after it has been detected.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.carousel import Carousel
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
class HomeScreen(Screen):
def on_touch_move(self, touch):
if touch.x < touch.ox: # this line checks if a left swipe has been detected
MainApp().change_screen(screen_name="swipedhikr_screen") # calls the method in the main app that changes the screen
class ImageButton(ButtonBehavior, Image):
pass
class LabelButton(ButtonBehavior, Label):
pass
class SettingsScreen(Screen):
pass
class SwipeDhikrScreen(Screen):
pass
#def quit_verification():
# pop = Popup(title="verification", content=Label(text= "Are you sure?"))
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
# get the screen manager from the kv file
screen_manager = self.root.ids["screen_manager"]
screen_manager.transition.direction = "up"
screen_manager.current = screen_name
def quit_app(self):
MainApp().stop()
MainApp().run()
I got an attribute error: "None type object has no attribute 'ids'"
MainApp().change_screen(screen_name="swipedhikr_screen")
This line creates a new instance of MainApp, which doesn't have any widgets and therefore naturally fails when you try to access them.
Use the existing instance of MainApp, i.e. the one that you're actually running, via MainApp.get_running_app().
Also you are not correct that Carousel works only with images.

Accessing "height" property of an instance of a widget

I need to access the height of a widget after it is created. However, whenever I tried to access my_widget.height, it returns the default height of 100 rather than the actual height.
Retrieving self.height inside the on_touch_down definition of the my_widget class works though.
My problem is really best described in the following code.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class Layout(BoxLayout):
# the following yields the correct height of Layout,
# which is the same as the height of the Window
def on_touch_down(self, touch):
print("inside Layout via on_touch_down:", self.height)
class MyApp(App):
def build(self):
l = Layout()
# However, accessing the height here yields the
# the default height 100
print("inside MyApp:", l.height)
return l
if __name__ == "__main__":
MyApp().run()
I spent hours reading different parts of the API doc and the guides on the Kivy website, but couldn't quite find what I missed.
In the build() method the application has not yet been built so the widget has its default value, so you must obtain that information a moment later, for this there are the following options:
Use App.on_start():
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class Layout(BoxLayout):
pass
class MyApp(App):
def build(self):
l = Layout()
return l
def on_start(self):
print(self.root.height)
if __name__ == "__main__":
MyApp().run()
Use Clock.schedule_once():
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
class Layout(BoxLayout):
pass
class MyApp(App):
def build(self):
l = Layout()
Clock.schedule_once(self.after_build)
return l
def after_build(self, dt):
print(self.root.height)
if __name__ == "__main__":
MyApp().run()

Prevent unnecessary event from Kivy Properties

I am using Kivy in an application where several widgets are used to modify/edit a set of data (the model). When some data is changed in one widget others need to be informed that something has changed so they can update their view.
To do this I use Kivy Properties to mirror the model data, and this works fine.
However the first time the Kivy Properties are set, when reading in the model data, this generates alot of unnecessary updates in the UI. I would like there to be a way to update a Kivy Property without generating update events.
I have looked in the documentation (https://kivy.org/docs/api-kivy.properties.html) and in the code (https://github.com/kivy/kivy/blob/master/kivy/properties.pyx) but I have not been able to find anything to do this.
How can this be solved?
I have done a very simple example app to show how the code is organized and where the problem occurs.
#! /usr/bin/env python
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListView
from kivy.adapters.listadapter import ListAdapter
from kivy.uix.listview import ListItemButton
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
model_data = ["Data1","Data2","Data3"]
class TestApp(App):
def build(self):
tl = TestLayout()
tl.read_data(model_data)
return tl
class TestLayout(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.test_list = TestList()
self.test_edit = TestEdit()
self.add_widget(self.test_list)
self.add_widget(self.test_edit)
self.test_list.adapter = ListAdapter(data=[],
cls=ListItemButton,
selection_mode='single')
self.test_list.adapter.bind(selection=self.select_data)
self.test_edit.bind(data=self.update_list)
def read_data(self, data_list):
self.test_list.init_list(data_list)
def select_data(self, list_adapter, selection):
if len(selection) > 0:
data = selection[0].text
self.test_edit.init_data(data)
def update_list(self, test_edit, data):
self.test_list.adapter.selection[0].text = data
class TestList(ListView):
def init_list(self, data_list):
self.adapter.data = [str(data) for data in data_list]
class TestEdit(BoxLayout):
data = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.text_input=TextInput()
self.add_widget(self.text_input)
btn_update = Button(text="Update data")
self.add_widget(btn_update)
btn_update.bind(on_press=self.update_data)
def init_data(self, data):
# Setting the data property the first time triggers an unnecessary update.
# How can this be prevented?
self.data = data
self.text_input.text = data
def update_data(self, btn):
self.data = self.text_input.text
if __name__ == "__main__":
app = TestApp()
app.run()
Also as you can see in the example code, an unnecessary update is dispatch whenever you select a new item in the list view.
Have I overlooked something obvious? Or is this not supported by Kivy?
Thanks!

kivy python widget instance or all widgets

Please help me with understanding classes/instances in python. I want to make a few buttons, and change color of the button, when it's clicked. I don't understand why on_touch_down changes the color of all the instances of the class, not the one that is touched. It's difficult for me to find answer because I don't know how to name it, I don't have much experience with objects. Please explain this. Thank you a million.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.graphics import Color, Ellipse
class MemoWidget(Button):
def on_touch_down(self, touch):
self.background_color=[100,100,1,1]
class MyApp(App):
def build(self):
root = BoxLayout(orientation='vertical',spacing=4)
m1 = MemoWidget()
m2 = MemoWidget()
m3 = MemoWidget()
root.add_widget(m1)
root.add_widget(m2)
root.add_widget(m3)
return root
if __name__ == '__main__':
MyApp().run()
You might think that on_touch_down only affects the widget you touch. But it affects all widgets of that class.
So what you might want, is on_press or on_release, to only affect the widget itself.
class MemoWidget(Button):
def on_release(self):
self.background_color=[100,100,1,1]

Categories

Resources