I am trying to update the position of a widget via a NumericProperty. When the function has been called the NumericProperty wont update. Updating inside of the function does not update the pos of my widget; updating outside yields an error. What can I do?
This code has been simplified
main.py
from kivy.app import App
from kivy.clock import mainthread
from kivy.properties import NumericProperty, BooleanProperty
class Game(App):
X_value = 0
X_value = 0
GPS_X = NumericProperty(0)
GPS_Y = NumericProperty(0)
#mainthread
def on_location(self, *args, **kwargs):
self.my_lat = kwargs['lat']
self.my_lon = kwargs['lon']
self.pos_updater()
return self.my_lat, self.my_lon
def pos_updater(self):
scaler = 100
self.Y_value = scaler * 111320.0
self.X_value = scaler * 40075000.0 * math.cos(self.my_lat)/360.0
print("Delta:",self.Y_value, self.X_value)
GPS_X += X_value + 100
GPS_Y += Y_value
def build(self):
pass
Game().run()
main.kv
<Game>
BoxLayout:
orientation: 'vertical'
FloatLayout:
size_hint: 1, 3
Button:
text: "higher"
pos: (root.GPS_X, root.GPS_Y + root.height/2)
on_release:
root.on_location()
Label:
id: time_id
text: "test"
Error Message:
TypeError: unsupported operand type(s) for +=: 'kivy.properties.NumericProperty' and 'int'
I am assuming ur are using a mapview:
in kivy file :
MapView:
id: main_map
size: root.width, root.height
MapMarkerPopup:
source: 'pin.png'
id: main_map_me
Now in ur main.py try this:
def on_location(self, *args, **kwargs):
self.my_lat = kwargs['lat']
self.my_lon = kwargs['lon']
self.ids.main_map_me.lat = self.my_lat
self.ids.main_map_me.lon = self.my_lon
its EASIER by id, use properties only when u cant use ids.(my opinion)
lets say ur Button has an id(btn)
in ur kivy file try this:
Button:
id: btn
in ur main.py try this:
self.ids.btn.pos[0] = self.width*0.5 # or whatever u need.....
self.ids.btn.pos[1] = self.height* #..... whatever u need
And check the type of lat and lon if they are not strings cuz u are assuming that they are NumericProperty i think they are StringProperty if so just convert them and work with ur original code stringproperty = float(my_Lat)
And at last i dont know what u are trying to do with the scaler exactly but u must know that the screen position has nothing to do the position thats on the mapview.
Related
I have this code below. I've set id by python code, but I couldn't access.
def abrirReceita(self,instance):
instance.text = str(instance.ids)
I'd like to change the text with the number of the ID when I press.
Exemple: if I input the first button, change the text for '1', which is the ID I've passed.
from kivymd.app import MDApp
from kivymd.uix.boxlayout import BoxLayout
from kivymd.uix.floatlayout import FloatLayout
from kivymd.uix.list import TwoLineListItem
from kivymd.uix.textfield import MDTextField
from kivy.lang import Builder
import os
from kivy.core.window import Window
import sqlite3
KV = '''
ScreenManager:
Screen:
name: 'telaSelecionada'
BoxLayout:
orientation: 'vertical'
MDToolbar:
id: tb
title: 'Drinks'
md_bg_color: 0, 0, 0, 1
TelaSelecionada:
id: telaselecionada
<TelaSelecionada>:
ScrollView:
MDList:
id: mostraReceita
padding: '20dp'
'''
Window.softinput_mode = "below_target"
class TelaSelecionada(FloatLayout):
pass
class Aplicativo(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
i = 1
for x in range(5):
self.textLine = TwoLineListItem(text = 'change text', secondary_text = 'change text')
self.root.ids.telaselecionada.ids.mostraReceita.add_widget(self.textLine)
self.root.ids.telaselecionada.ids.mostraReceita.ids[i] = self.textLine
self.textLine.bind(on_release = self.abrirReceita)
i += 1
def abrirReceita(self,instance):
instance.text = str(instance.ids)
Aplicativo().run()
How do I access the IDs from python code?
I'd like to change the text with the number of the ID when I press.
Just pass the required args through method abrirReceita using functools.partial as,
def on_start(self):
...
self.textLine.bind(on_release = partial(self.abrirReceita, i))
i += 1
# Then in `abrirReceita` :
def abrirReceita(self, id_index, instance):
instance.text = str(id_index)
Note:
The ids are not used here at all (actually you don't need that here for this purpose) !
Update:
As, the Widget.id has been deprecated since version 2.0.0 you should use it only in kvlang not in python.
But that doesn't keep you from creating it as a dynamically added attribute. Thus you can do something like this (I modified the on_start method a little):
def on_start(self):
mostraReceita = self.root.ids.telaselecionada.ids.mostraReceita
for i, _ in enumerate(range(5), start=1):
# Or just,
# for i in range(1, 5+1):
self.textLine = TwoLineListItem(
text = 'change text',
secondary_text = 'change text',
)
self.textLine.index = i
# Or even,
# self.textLine.id = i
self.textLine.bind(on_release = self.abrirReceita)
mostraReceita.add_widget(self.textLine)
def abrirReceita(self,instance):
instance.text = str(instance.index) # or, str(instance.id)
from kivy.graphics.context_instructions import Color
from kivy.graphics.instructions import InstructionGroup
from kivy.graphics.vertex_instructions import Line
from kivy.properties import ObjectProperty
from kivy_garden.mapview import MapView, MapMarker
from kivy.app import App
from kivy.lang import Builder
kv = '''
MyMapView:
zoom: 2
double_tap_zoom: True
id: gps
Button:
text: " [86-6] "
background_color: (1,1,1,1)
color: (0,0,0,1)
font_size: 15
size_hint: (None,None)
width: 150
height: 30
on_press: root.gpss()
'''
class MyMapView(MapView):
grp = ObjectProperty(None)
def gpss(self):
self.ids.gps.lat = 48.20753856396109
self.ids.gps.lon = 16.372519189874197
class MapViewApp(App):
def build(self):
return Builder.load_string(kv)
MapViewApp().run()
Error
self.ids.gps.lat = 48.20753856396109
File "kivy\properties.pyx", line 864, in kivy.properties.ObservableDict.__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
[Finished in 5.0s with exit code 1]
The problem is your use of ids. The ids of a kivy object are a dictionary to objects in the widget tree below the root. But the only id you have assigned in your kv is for the root node itself. So, no ids are actually added to the ids dictionary. That is what causes the error message (the ids dictionary is empty). But since your gpss() is a method of the MyMapView object, you don't need to use ids to get a reference to it, it is simply self. So your gpss() method can be changed to:
def gpss(self):
self.lat = 48.20753856396109
self.lon = 16.372519189874197
self.zoom = 20
You may need to add something like a zoom setting to get the MapView to respond
I am trying to have a function run continuously and spit out a distance that the label uses that I'll eventually tie to a sonar module, but the label remains blank and I am at a loss as to what I am doing wrong. If I just add a print statement for that distance variable it prints and updates just fine, just can't get the label to use it.
Part II of my question is how do I reference my same function in the second window and also have a label that updates from that same function?
Thanks for the help in advance, I am very very new to kivy and just started learning python a few months ago as well.
Python code:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen # for multiple screens
from kivy.properties import StringProperty
class MySonar(Screen):
global i
i = 1
distance = StringProperty("")
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
self.root.distance=str(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("help.kv")
class HelpMe(App):
def build(self):
#running interval update to keep running code above
Clock.schedule_interval(lambda dt: MySonar.sonar(self), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Kivy:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text:root.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
##text:root.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
To simplify the access to distance, you can put that StringProperty in the HelpMe App:
class MySonar(Screen):
global i
i = 1
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
# set the value of distance in the StringProperty of the App
App.get_running_app().distance=str(distance)
print(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
# kv = Builder.load_file("help.kv")
class HelpMe(App):
distance = StringProperty('')
def build(self):
kv = Builder.load_file("help.kv")
#running interval update to keep running code above
sonar_instance = kv.get_screen('Main')
Clock.schedule_interval(lambda dt: sonar_instance.sonar(), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Note that I have also moved the Builder.load_file() inside the App. This is good practice when you reference app in the kv file (as I have done). Also, calling the sonar() method using MySonar.sonar(self) will not work. You need to use a reference to the instance of MySonar that is in your GUI.
Now the kv file becomes:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text: app.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
text: app.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
The change is that the text attribute of both Labels is now just app.distance.
I am trying to make a program that reads the dictionary after the user inputs their name and assigns a random selection based on weighted values in the dictionary. As of now the logic for selecting a random value from the dictionary is working but I have it printing to the console. I would like it to appear in a popup window (which i have but cannot get the output variable to show up there)
four.kv
WindowManager:
MainWindow:
<MainWindow>:
name:'main'
player_python:player_kv
GridLayout:
cols:1
GridLayout:
cols:2
Label:
text:'Player:'
TextInput:
id: player_kv
multiline: False
Button:
text: 'Random'
on_press: root.btn()
<P>:
output:output
FloatLayout:
Label:
id: output
main4.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
import random
#from Dict import *
#### example dictionary
character = {
'John':
{'Water': 2, #50%
'Fire': 1, #25%
'Earth': 1,}, #25%
'Bill':
{'Water': 1, #25%
'Fire': 2, #50%
'Earth': 1,}} #25%
####
class MainWindow(Screen):
player_python = ObjectProperty(None)
output = StringProperty('')
def btn(self):
show_popup()
player = self.player_python.text
weighted_list = []
for c in character[player]:
for w in range(character[player][c]):
weighted_list.append(c)
self.output= random.choice(weighted_list)
print(self.output) ###### instead of this printing to console I want it to display in popup window
self.player_python.text = ''
class P(FloatLayout):
pass
def show_popup():
show = P()
popupWindow = Popup(title='random character', content=show, size_hint=(None,None),size=(400,400) )
popupWindow.open()
class WindowManager(ScreenManager):
pass
kv = Builder.load_file('four.kv')
class FourApp(App):
def build(self):
return kv
if __name__ == '__main__':
FourApp().run()
https://gist.github.com/PatrickToole/00cc72cdd7ff5146976e5d92baad8e02
Thanks in advance
-P
I haven't tested this code, but try passing self.output to your show_popup() method. This would mean changing your btn() method to something like:
def btn(self):
player = self.player_python.text
weighted_list = []
for c in character[player]:
for w in range(character[player][c]):
weighted_list.append(c)
self.output= random.choice(weighted_list)
print(self.output) ###### instead of this printing to console I want it to display in popup window
self.player_python.text = ''
show_popup(self.output)
And in the show_popup() method:
def show_popup(output):
show = P()
show.output.text = output
popupWindow = Popup(title='random character', content=show, size_hint=(None,None),size=(400,400) )
popupWindow.open()
As I mentioned, I haven't tested this code, but something like this should work.
I would like to create two labels in Kivy that update their text with sensor data from temp sensors.
Temp sensors are connected to an Arduino, which prints their values to serial in the example format every two seconds or so:
A 82.4 (on line 1)
B 80.6 (on line 2)
The A/B is included in each print as an identifier that python could pick up to differentiate between the two.
The issue is importing this data into python and attaching it to labels.
Here is the existing .py:
import kivy
kivy.require('1.10.0')
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from digitalclock import DigitalClock
from kivy.animation import Animation
import serial
import time
import opc
class IntroScreen(Screen):
pass
class ContScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
#Disregard this, a timer is included in layout
class Timer(Label):
a = NumericProperty() # seconds
def __init__(self, root, instance, duration, bg_color, **kwargs):
super(Timer, self).__init__(**kwargs)
self.obj = instance
self.a = duration
self.root = root
self.obj.disabled = True # disable widget/button
self.obj.background_color = bg_color
self.root.add_widget(self) # add Timer/Label widget to screen, 'cont'
def animation_complete(self, animation, widget):
self.root.remove_widget(widget) # remove Timer/Label widget to screen, 'cont'
self.obj.background_color = [1, 1, 1, 1] # reset to default colour
self.obj.disabled = False # enable widget/button
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
self.anim.bind(on_complete=self.animation_complete)
self.anim.start(self)
def on_a(self, instance, value):
self.text = str(round(value, 1))
class Status(FloatLayout):
_change = StringProperty()
_tnd = ObjectProperty(None)
def update(self, *args):
self.time = time.asctime()
self._change = str(self.time)
self._tnd.text = str(self.time)
print (self._change)
#Here is where I start referencing Serial Comms, this line is to identify where
#to *send* commands to via a separate identifier.
bone = serial.Serial('/dev/ttyACM0', 9600)
class XGApp(App):
time = StringProperty()
sensor1 = NumericProperty(0)
sensor2 = NumericProperty(0)
def update(self, *args):
self.time = str(time.asctime())
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
for line in data.split('\n'):
try:
sensor, value = line.strip().split(' ')
except:
print("parse error!")
continue
if sensor == 'A':
self.sensor1 = float(value)
elif sensor == 'B':
self.sensor2 = float(value)
else:
print("unknown data! {}".format(line))
def build(self):
try:
self.arduino = serial.Serial('/dev/ttyACM0', 9600)
except Exception as e: print(e)
Clock.schedule_interval(self.update, 1)
return Builder.load_file("main.kv")
xApp = XGApp()
if __name__ == "__main__":
xApp.run()
and the .kv:
<ContScreen>:
FloatLayout
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 50
Label:
id: 'TempLabel1'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.2, 'center_y':0.6}
Label:
id: 'TempLabel2'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.5, 'center_y':0.6}
later in the .kv:
StackLayout
orientation: "tb-rl"
spacing: 15
Button:
text: "1"
size_hint: None, .16
width: 225
on_press:
Timer(root, self, 10, [100, 0, 100, 1.75]).start()
bone.Write('j'.encode())
print("One Executed")
TempLabel1 and TempLabel2 are the two labels i'd like updated from the sensors.
It's totally possible. But you are missing a few things.
You are trying to connect to the serial port after running your app, that won't work as your app will be stopped when you arrive there. Instead, you want to do this part while your app runs. I would do the try/except to connect to arduino in app.build.
def build (self):
try:
self.arduino = serial.Serial('/dev/ttyACM0')
exept:
print("unable to connect to arduino :(")
Clock.schedule_interval(self.update, 1)
return Builder.load_file("main.kv")
then, you want to check for messages in the update method, but you don't want to block, so you only read the amount of data that is waiting in the buffer.
def update(self, *args):
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
then you do your processing, i assume your data is something like:
A value
B value
in such case, you can parse it and update the corresponding variable, something like:
def update(self, *args):
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
for line in data.split('\n'):
try:
sensor, value = line.strip().split(' ')
except:
print("parse error!")
continue
if sensor == 'A':
self.sensor1 = float(value)
elif sensor == 'B':
self.sensor2 = float(value)
else:
print("unknown data! {}".format(line))
would do the job, it's a bit simplistic, as it assumes you always get full lines, but it can be improved later if needed (and it seems enough for a lot of cases in my experience).
Now, we need to make sure that our labels notice the changes of values, for this, kivy uses properties, which are smarter attributes, you need to declare them on the app class.
class XGApp(App):
sensor1 = NumericProperty(0)
sensor2 = NumericProperty(0)
now, you can make your update display the value directly, through the app instance.
<ContScreen>:
FloatLayout
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 50
Label:
id: 'TempLabel1'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.2, 'center_y':0.6}
Label:
id: 'TempLabel2'
text: str(app.sensor2)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.5, 'center_y':0.6}