Kivy - Parsing structure to widget - python

I'm having issues with parsing a data structure to a widget in Kivy, which would then access the structure and be able to show a value on the screen be updated continuously via a clock interval (not sure of a better to do this yet).
I have highlighted the issues in the (non-working) code below:
main.py
from kivy.app import App
from test import TestWidget
class TestApp(App):
def build(self):
testStructTable = {'randomVal1': 1, 'testVal': 2, 'randomVal2': 3}
# Issue here parsing the table like this?
return TestWidget(testStructTable)
if __name__ == '__main__':
TestApp().run()
test.py
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
class TestWidget(RelativeLayout):
def __init__(self, testStructTable, **kwargs):
super(TestWidget, self).__init__(**kwargs)
Builder.load_file('test.kv')
sm = ScreenManager()
sm.add_widget(MainScreen(name='MainScreen'))
self.add_widget(sm)
# Error accessing the table
print self.testStructTable
# Have the update_test_val continuously called
#Clock.schedule_interval(MainScreen.update_test_val(testStructTable), 1 / 60)
class MainScreen(Screen):
def __init__(self, **kwargs):
testVal = NumericProperty(0)
def update_test_val(self, testStructTable):
# Get testVal from testStructTable
# Something like:
# self.testVal = testStructTable.testVal + 1 ?
self.testVal = self.testVal + 1
test.kv
<MainScreen>:
FloatLayout:
Label:
text: str(root.testVal)
font_size: 80
My aim is to have the testVal constantly updating on the screen by accessing that data structure, however I am currently unable to achieve this, can you please advise?

In your __init__ method you're passing testStructTable and then you're trying to access self.testStructTable which does not exist untill you explicitly make an assignment:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
class TestWidget(RelativeLayout):
def __init__(self, testStructTable, **kwargs):
super(TestWidget, self).__init__(**kwargs)
print(testStructTable)
self.testStructTable = testStructTable
print(self.testStructTable)
class TestApp(App):
def build(self):
testStructTable = {'randomVal1': 1, 'testVal': 2, 'randomVal2': 3}
# Issue here parsing the table like this?
return TestWidget(testStructTable)
if __name__ == '__main__':
TestApp().run()

Related

How do I access ids in Kivy?

I am a beginner to Kivy (though not to Python), and I am struggling to get the ids from a kv string into my main code. I have the following, but the 'print' statement tells me that there are no IDs. The application itself runs with no errors.
import kivy
kivy.require('2.1.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
kvString = """
MainScreen:
id: maincontainer
cols: 1
thumbnails: thumbnails.__self__
GridLayout:
id: thumbnails
cols: 3
rows: 3
Image:
source: "test.png"
"""
class MainScreen(GridLayout):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
# This prints 0
print("Ids: {}".format(len(self.ids.items())))
class ExampleApp(App):
def build(self):
root = Builder.load_string(kvString)
return root
if __name__ == "__main__":
ExampleApp().run()
When I ran your code, I got a critical warning that there are no screens, and therefore the app will terminate. As soon as I switched MainScreen to a screen, it worked out perfectly. Here is the code:
.py
import kivy
kivy.require('2.1.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.image import Image
sm = ScreenManager()
#I removed some unnecessary code such as the cols, thumbnail, etc.
kvString = """
<MainScreen>
GridLayout:
id: thumbnails
cols: 3
rows: 3
Image:
source: "test.png"
"""
#NEEDS TO INHERIT SCREEN
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
# This now prints 1
print(f'There are {len(self.ids.items())} id(s)')
class ExampleApp(App):
def build(self):
root = Builder.load_string(kvString)
#Adds the screen
sm.add_widget(MainScreen(name='screen'))
return sm
if __name__ == "__main__":
ExampleApp().run()

Updating multiple Labels with kivy

Hello i'm relatively new to python and kivy and I've been looking around but i cant quite find the answer, i'm trying to make a hub where I can display live data from my rasberry-pi. I made a clock widget using a Label, and it updates time, but when i try to add my CPU usage only one label shows up. I thought the widgets might be covering each other but I used size_hint and it wont help. When i run the code below, only the timex will show up and only if i delete the Clock.schedule_interval(display32.timex, (1)) will the *updatex * display.
Thanks alot for the help
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
import psutil
from kivy.lang import Builder
import time
class Display(Label, FloatLayout):
def __init__(self):
super(Display, self).__init__()
def updatex(self, *args):
self.cpu2 = psutil.cpu_percent()
self.text=str(self.cpu2)
self.size_hint=(.5, .25)
self.pos=(500, 500)
self.texture_size=(0.1,0.1)
def timex(self, *args):
self.time2 = time.asctime()
self.text = str(self.time2)
self.size_hint = (.5, .25)
self.pos = (30, 500)
self.size_hint=(0.1,0.1)
class TimeApp(App):
def build(self):
display32 = Display()
Clock.schedule_interval(display32.updatex, (1))
Clock.schedule_interval(display32.timex, (1))
return display32
if __name__ == "__main__":
TimeApp().run()
Instead of passing both labels to the clock, you can define a widget, add both labels and start the clock on the widget.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.clock import Clock
import psutil
import time
class Display(Widget):
def draw(self, *args):
self.clear_widgets()
self.add_widget(Label(text=str(psutil.cpu_percent()), pos=(500, 500)))
self.add_widget(Label(text=str(time.asctime()), pos=(30, 500)))
class TimeApp(App):
def build(self):
display32 = Display()
Clock.schedule_interval(display32.draw, 1)
return display32
if __name__ == '__main__':
TimeApp().run()

Trying to display mapview on a window kivy

Im trying to display a mapview on my naviwindow. However, i dont really know how to implement the class MapviewApp to the naviwindow. Any help would be appreciated since im new to kivy !!
.py file
from kivy.garden.mapview import MapView, MapMarker
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
KV = """
WindowManager:
NaviWindow:
<NaviWindow>:
Label:
text:"hi"
"""
class MapViewApp(App):
def build(self):
map = MapView(zoom=18, lat=1.3099, lon=103.7775, double_tap_zoom = True)
marker_1 = MapMarker(lat=1.3099, lon=103.7775)
map.add_marker(marker_1)
return map
class WindowManager(ScreenManager):
pass
class NaviWindow(Screen):
pass
class MyMainApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == "__main__":
MyMainApp().run()

Python/Kivy passing variables

Struggling to pass a variable to kivy window. I have read similar threads all over the place but none of the fixes seem to work for me. Im sure this is simple to someone who knows their way around tiny, unfortunately I don't.
main.py
import kivy
from kivy.uix.togglebutton import ToggleButton
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.app import App
kivy.require('1.10.0')
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
print("launching")
def __init__(self):
super(Controller, self).__init__()
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(dt):
if b.get_light(1, 'on')== True:
#print("down") # When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
else:
#print("up")# When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
class ActionApp(App):
def build(self):
Clock.schedule_interval(Controller.update, 1.0 / 60.0)
return Controller()
myApp = ActionApp()
myApp.run()
action.kv
<Controller>:
cols: 4
rows: 3
spacing: 10
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: Controller.update # This is the part that is throwing up the error.
The error:
11: #on_release: root.KitchenSpot1(False)
12: #state1 = app.update.h
>> 13: state: Controller.update
14:
15:
...
NameError: name 'Controller' is not defined
Thanks in advance to anyone that can help me.
Make update an instance method and use a StringProperty to update state property in your kv:
main.py:
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.togglebutton import ToggleButton
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('normal') # <<<<<<<<<<<<
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down' # <<<<<<<<<<<<
else:
self.state = 'normal' # <<<<<<<<<<<<
class ActionApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
myApp = ActionApp()
myApp.run()
action.kv:
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal" # <<<<<<<<<<<<
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state # <<<<<<<<<<<<
Here is a more generic simplified answer from the kivy documentation, look for the section called "Keyword arguments and init()" because there are some other ways to do it as well.
The following code passes myvar to the build() method of MyApp. It does this by over-riding the init() of the Kivy App class by a new init() that calls App.init() and then continues with whatever extra initialisation you want. You can then store variables in the MyApp class instances and use them in build().
from kivy.app import App
from kivy.uix.label import Label
myvar = 'Hello Kivy'
class MyApp(App):
def __init__(self, myvar, **kwargs):
super(MyApp, self).__init__(**kwargs)
self.myvar = myvar
def build(self):
widget = Label(text=self.myvar)
return widget
if __name__ == '__main__':
MyApp(myvar).run()

Nested / alternating apps with Kivy

I'd like to have a kivy app function as a launcher for other kivy apps, depending on input. The way I implemented it below is obviously not working (because the kv file gets reloaded and its styles reapplied, thus adding more and more buttons), and there also seems to be some recursion as the trace suggests when I hit Esc to exit.
I do get the warning that app1.kv is loaded multiple times, however, in the documentation for App.load_kv() it says
This method is invoked the first time the app is being run if no
widget tree has been constructed before for this app.
This implies to me that it should be possible to run() an app multiple times?
Here is my code:
main.py
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.lang import Builder
class OutsideApp(App):
current_app = ObjectProperty(None)
def build(self):
Clock.schedule_interval(self.update, 3)
return Widget()
def update(self, dt):
if isinstance(self.current_app, App):
self.current_app.stop()
if isinstance(self.current_app, App1):
self.current_app = App2()
else:
self.current_app = App1()
self.current_app.run()
class App1(App):
pass
class App2(App):
def build(self):
gl = Builder.load_string("<SequencesGame#GridLayout>:\n cols: 2\n Button:\n text: \"hello 2\"\nSequencesGame:")
return gl
if __name__ == '__main__':
oa = OutsideApp()
oa.run()
app1.kv
#:kivy 1.0.9
<SequencesGame#GridLayout>:
cols: 2
Button:
text: "hello 111"
SequencesGame:
This seems to be an issue even if apps are not nested:
main2.py
from kivy.app import App
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.lang import Builder
class App1(App):
pass
class App2(App):
def build(self):
return Builder.load_string("<SequencesGame#GridLayout>:\n cols: 2\n Button:\n text: \"hello 2\"\nSequencesGame:")
current_app = None
def switch(*args):
global current_app
if isinstance(current_app, App):
current_app.stop()
if isinstance(current_app, App1):
current_app = App2()
else:
current_app = App1()
current_app.run()
if __name__ == '__main__':
Clock.schedule_interval(switch, 2)
switch()
In the end I followed #inclement's suggestion of using multiprocessing. Here is a basic implementation (without communication through Pipes or Queues):
import sys
import multiprocessing
from kivy.app import App
from kivy.lang import Builder
from time import sleep#, localtime, time
def str_to_class(str):
return reduce(getattr, str.split("."), sys.modules[__name__])
class App1(App):
def build(self):
return Builder.load_string("BoxLayout:\n Button:\n text: \"hello 1\"")
class App2(App):
def build(self):
return Builder.load_string("BoxLayout:\n Button:\n text: \"hello 2\"")
class Wrapper(object):
def __init__(self, *kargs, **kwargs):
super(Wrapper, self).__init__()
self.running_app = None
self.next_app = 'App1'
def change_running_app(self, name='App1'):
if self.running_app is not None:
self.running_app.terminate()
self.running_app = multiprocessing.Process(target=self.run_app, kwargs={'name':name})
self.running_app.start()
def run_app(self, name='App1'):
app = str_to_class(name)()
app.run()
def swap(self):
self.change_running_app(name=self.next_app)
self.next_app = ['App1', 'App2']['App1' is self.next_app]
if __name__ == '__main__':
counter = 0
w = Wrapper()
while True:
counter += 1
print "Started App instance {}".format(counter)
w.swap()
sleep(5)

Categories