New to kivy, and OOP.
I'm trying to update a label in kivy with data I pull from a temp sensor. The code that pulls in the sensor data is in labeltempmod. I created a function getTheTemp() that is called every second. In the function I try to assign the text of the label via Label(text=(format(thetemp)), font_size=80). The program ignores this. What am I doing wrong here?
#This is a test to see if I can write the temp to label
import labeltempmod
import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
def getTheTemp(dt):
thetemp = labeltempmod.readtemp()
Label(text=(format(thetemp)), font_size=80)
print thetemp
class LabelWidget(BoxLayout):
pass
class labeltestApp(App):
def build(self):
# call get_temp 0.5 seconds
Clock.schedule_interval(getTheTemp, 1)
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Here is the kivy language file:
<LabelWidget>:
orientation: 'vertical'
TextInput:
id: my_textinput
font_size: 80
size_hint_y: None
height: 100
text: 'default'
FloatLayout:
Label:
id: TempLabel
font_size: 150
text: 'Temp Test'
Thanks.
Sorry but you never update something You are just creating another label
Try this:
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
thetemp = labeltempmod.readtemp()
self.ids.TempLabel.text = thetemp
print thetemp
class labeltestApp(App):
def build(self):
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Update : for your last request, I think the best way to do that is:
...
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
self.Thetemp = None
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
if self.Thetemp is None:
self.thetemp = labeltempmod.readtemp()
else:
self.thetemp = labeltempmod.readtemp(self.theTemp)
self.ids.TempLabel.text = str(self.thetemp)
Related
here i've made a grid full of inputboxes using for-loop and added them to a dictionary to access later since i couldn't set id's in the python file. Now i'm trying to make a function that will make basic calculations based on the inputbox's data but i do not know how i can pass particular inputboxes in that function without using id's. Also the on_text function isn't working as i expected. I'm sure i'm missing something big here. Any guidance is appreciated.
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import StringProperty
Builder.load_file("gridtable.kv")
class MyBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
pass
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols=6
self.textinputs = {}
for i in range(48):
key = i+1
self.textinputs[key] = TextInput(multiline=False,font_size=dp(30),on_text=self.calc(key))
self.add_widget(self.textinputs[key])
def calc(self,key):
print(self.textinputs[key])
class MyApp(App):
def build(self):
return MyBox()
if __name__ == "__main__":
MyApp().run()
<MyBox>:
mygrid:my_grid
orientation: "vertical"
MyGrid:
id: my_grid
size_hint: 1,0.8
BoxLayout:
orientation: "horizontal"
size_hint: 1,0.2
BoxLayout:
orientation: "vertical"
Button:
text: "Expense Total:"
Button:
text: "Revenue Total:"
Button:
text: "Profit:"
font_size: 40
Traceback (most recent call last):
File "c:\Users\jaika\OneDrive\Desktop\python\lil_curry_project\gridtable.py", line 38, in <module>
MyApp().run()
print(self.textinputs[key])
KeyError: 1
#better version of MyGrid()
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols=4
self.textinputs = [0]
self.expense = 0
self.revenue = 0
self.profit = 0
for i in range(16):
t = TextInput(multiline=False,text=str(i+1))
t.bind(on_text_validate=self.calc_profit)
self.textinputs.append(t)
self.add_widget(t)
def calc_profit(self,instance):
index = self.textinputs.index(instance)
if index == 1 or index == 2 or index == 5 or index == 6 or index == 9 or index == 10 or index == 13 or index == 14:
self.expense += int(instance.text)
else:
self.revenue += int(instance.text)
self.profit = self.revenue - self.expense
print(self.profit)
You can use bind to trigger a method call, or use events available in the TextInput (like on_text_validate). Here is a modified MyGrid class that uses both:
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 6
self.textinputs = {}
for i in range(48):
key = i + 1
self.textinputs[key] = TextInput(multiline=False, font_size=dp(30), on_text_validate=self.abba)
self.textinputs[key].bind(text=partial(self.calc, key))
print('in init, key =', key)
self.add_widget(self.textinputs[key])
def abba(self, ti_instance):
print('abba, textinput instance =', ti_instance, ', text =', ti_instance.text)
def calc(self, key, ti_instance, text):
print('calc:')
print('key =', key, ', text =', text, ', textinput instance =', ti_instance)
Note that the bind to text gets triggered with every change to the text in a TextInput. The on_text_validate will only be triggered when the user hits Enter.
I am trying to use Kivy Properties to refresh GUI after property is changed. However, when I try to assign an instance of custom class to property it doesn't work. After modifying property GUI is not refreshed with new value.
MWE:
python file:
from typing import List
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import Screen
class SomeStruct:
def __init__(self, atr1: List[int], atr2: str):
self.atr1 = atr1
self.atr2 = atr2
class Root(Screen):
prop1 = ObjectProperty(None)
prop2 = ObjectProperty(None)
def __init__(self, **kw):
super().__init__(**kw)
self.prop1 = SomeStruct(atr1=[1, 2, 3], atr2="test1")
self.prop2 = "control1"
def modify1(self):
self.prop1.atr1 = [3, 2, 1]
def modify2(self):
self.prop1.atr2 = "test2"
def modify3(self):
self.prop2 = "control2"
class Mwe(App):
pass
if __name__ == '__main__':
Mwe().run()
kv file:
Root:
BoxLayout:
orientation: 'vertical'
Button:
text: str(root.prop1.atr1)
on_press: root.modify1()
Button:
text: root.prop1.atr2
on_press: root.modify2()
Button:
text: root.prop2
on_press: root.modify3()
Button 1 and 2 change classe's varaibles but text on these buttons is not updated. Button 3 works as it should.
Any help would be aprreciated, I really want to avoid making every varaible from class a separate property.
I'm trying to get a live graph working in my code.
I can get a live graph working when everything is within the same python file, though when I try to separate the graph class into its own python file, the graph doesn't update. I have no real indication of why the graph isn't updating, but I think I may be creating a bunch of LogGraph objects as opposed to adding points to the actual LogGraph which is in my.kv, but I am not sure how to not do that.
My actual code is a bit involved and confusing, so I mocked up an example code here which should be behaving exactly the same:
main.py
from math import sin
import kivy
from kivy_garden.graph import Graph, MeshLinePlot
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from data import startdata
class MainWindow(Screen):
def pressrun(self):
self.ids.runlbl.text = 'Started'
startdata()
pass
class MyApp(App):
def build(self):
wm.add_widget(MainWindow())
return wm
class WindowManager(ScreenManager):
def __init__(self, **kwargs):
super(WindowManager, self).__init__(**kwargs)
wm = WindowManager()
kv = Builder.load_file("my.kv")
MyApp().run()
my.kv
#: import LogGraph graphs.LogGraph
<MainWindow>:
id: main
BoxLayout:
orientation: "vertical"
LogGraph:
BoxLayout:
orientation: "horizontal"
Button:
text: "Start Data Gen"
on_release:
root.pressrun()
Label:
id: runlbl
text: "Not Started"
graphs.py
from kivy_garden.graph import Graph, MeshLinePlot
class LogGraph(Graph):
def __init__(self, **kwargs):
super(LogGraph, self).__init__(**kwargs)
self.xlabel = 'X'
self.ylabel = 'Y'
self.x_ticks_major = 25
self.x_ticks_minor = 5
self.x_grid_label = True
self.y_ticks_major = 1
self.y_grid_label = True
self.xmin = 0
self.xmax = 100
self.ymin = 0.1
self.ymax = 10
self.ylog = True
self.x_grid = True
self.y_grid = True
self.plot = MeshLinePlot(color=[1, 0, 0, 1])
self.add_plot(self.plot)
self.plot.points = [(1,1)]
def update_xaxis(self,xmin = 0):
self.xmin = xmin
self.xmax = xmin + 10
def update_yaxis(self,ymin = 0):
self.ymin = ymin
self.ymax = ymin + 10
def update_points(self, point, *args):
self.plot.points.append([point,point])
# x axis resize
if point > self.xmax:
self.update_xaxis(self.xmax)
# y axis resize
if point > self.ymax:
self.update_yaxis(self.ymax)
data.py
from kivy.clock import Clock
from functools import partial
from graphs import LogGraph
class DataStore():
def __init__(self):
self.i = 1
self.dataarray = []
def start(self):
self.clock = Clock.schedule_interval(self.getData, 1/60)
def cancel(self):
self.clock.cancel()
def wait(self):
print('Waited!')
def getData(self):
i = self.i + 1/60
LogGraph.update_points(LogGraph(), i)
pass
def startdata():
ds = DataStore()
ds.start()
Three main problems with your code:
Your code kv = Builder.load_file("my.kv") is loading the my.kv file a second time. It will be loaded automatically because the file is named correctly for that to happen. You should eliminate that code.
Your scheduled calls to DataStore.getData() will not work because your DataStore instance is not saved anywhere, and so it gets garbage collected.
The getData() method of DataStore creates a new instance of LogGraph each time it runs, but does not use the instance of LogGraph that is in your GUI.
To fix these problems, start by adding to your kv to allow access:
#: import LogGraph graphs.LogGraph
<MainWindow>:
id: main
name: 'main' # added to enable access
BoxLayout:
orientation: "vertical"
LogGraph:
id: graph # added to enable access
BoxLayout:
orientation: "horizontal"
Button:
text: "Start Data Gen"
on_release:
root.pressrun()
Label:
id: runlbl
text: "Not Started"
Then in the startdata() method, add a return
def startdata():
ds = DataStore()
ds.start()
# return the DataStore instance so it can be saved
return ds
Then save the returned DataStore in the pressrun() method:
def pressrun(self):
self.ids.runlbl.text = 'Started'
self.dataStore = startdata()
And the getData() method must be modified to access the LogGraph that is in the GUI:
def getData(self, dt): # time interval argument is required
self.i += dt
# access the LogGraph instance in the GUI
lg = App.get_running_app().root.get_screen('main').ids.graph
lg.update_points(self.i)
# LogGraph.update_points(LogGraph(), i)
I need to add a series of labels to my screen layout - the number of the labels are defined at runtime - and update the label texts with runtime data.
Hence, I need to add the labels programatically.
I have created a little test programme to investigate the behaviour:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.properties import StringProperty
import random
Builder.load_string('''
<YourWidget>:
BoxLayout:
size: root.size
Button:
id: button1
text: "Change text"
on_release: root.change_text()
# Label:
# id: label1
# text: root.random_number
''')
class YourWidget(Widget):
random_number = StringProperty()
def __init__(self, **kwargs):
super(YourWidget, self).__init__(**kwargs)
label = Label(
id= 'label1',
text = root.random_number
)
self.add_widget(label)
self.random_number = str(random.randint(1, 100))
def change_text(self):
self.random_number = str(random.randint(1, 100))
class updatetest(App):
def build(self):
return YourWidget()
#if __name__ == '__main__':
if (True):
updatetest().run()
If I uncomment the three lines relating to Label in the Builder.load_string, and remove the label = & self.add_widget lines from the python code, then the label gets updated as expected.
However, I have been unsuccessful in adding the label with the code as it stands.
What am I missing?
All assistance gratefully received!
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()