KIVY: 'float' has no attributes 'ids' - python

I am trying to make a clock which is just a label update every second with the current time. Each time I try to update the label I am thrown this error:
File "C:\Users\Nitro\Documents\MirOS\MirOS-core.py", line 33, in currentTime
self.ids.current_time.text = timeData
AttributeError: 'float' object has no attribute 'ids'
I did a little research into the kivy.Clock function and I found out that this is most likely happening because the clock function calls the currentTime() and includes a delta time argument which is what causes the AttributeError. Unfortunately, I need the self argument to stay where it is as otherwise my label does not update and I am thrown more errors.
Here is my .py file:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition
import time
sm = ScreenManager(transition = FallOutTransition())
Window.clearcolor = 0, 0, 0, 1
Window.size = 1920, 1080
Window.fullscreen = True
class StartUP(Screen):
def SystemCheck(self):
sm.current = 'active_home'
print('WORKING')
class StartUPCavas(Widget):
pass
class ActiveHome(Screen):
class ActiveHomeCanvas(Widget):
pass
class ActiveClock(Widget):
def currentTime(self):
timeData = time.strftime("%H:%M:%S")
self.ids.current_time.text = timeData
Clock.schedule_interval(currentTime, 1)
class MirOSApp(App):
def build(self):
sm.add_widget(StartUP(name = 'startup'))
sm.add_widget(ActiveHome(name = 'active_home'))
return sm
if __name__ == '__main__':
MirOSApp().run()
Here is the .kv file:
#kivy 2.1.0
<StartUP>:
StartUPCavas:
Image:
source: 'images/MirOS.png'
texture: self.texture
size_hint_y: None
width: 300
center_x: root.width / 2
center_y: root.height / 2
Button:
center_x: root.width / 2
center_y: (root.height / 2) - 100
on_press:
root.SystemCheck()
<ActiveHome>:
ActiveHomeCanvas:
ActiveClock:
Label:
id: current_time
text: ''
font_size: 40
font_name: 'fonts/bahnschrift.ttf'
center_x: root.width / 2
center_y: root.height / 2
color: 1, 1, 1, 1
I am really confused and have tried to solve this issue on my own but I can't seem to find any solution. Any ideas? Thanks for your time!

You are correct about the delta time argument (dt). That argument is being passed into the currentTime() method, but the currentTime() method is expecting to receive the self. So when that method does a self.ids, it is trying to access the ids attribute of dt, causing the error.
In order to provide the self argument, you must call the currentTime() method as an instance method of ActiveHome (since that is where that id is defined).
You can fix this by modifying your ActiveHome class like this:
class ActiveHome(Screen):
def __init__(self, **kwargs):
super(ActiveHome, self).__init__(**kwargs)
Clock.schedule_interval(self.currentTime, 1)
def currentTime(self, dt):
timeData = time.strftime("%H:%M:%S")
self.ids.current_time.text = timeData
This arrangement schedules the calls to currentTime() in an __init__() method, and the currentTime() method handles the correct arguments.

Related

what is the problem with my code. it keeps on displaying this error message

i am trying to make a software that records microphone data in realtime but it keeps on coming up with an error. " TypeError: init() got an unexpected keyword argument '__no_builder'"
i have looked through the code but cant seem to find any problems.
i am using atom to code.
there is a .kv and .py file
python file
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.garden.graph import MeshLinePlot
from kivy.clock import Clock
from threading import Thread
import audioop
import pyaudio
from kivy.core.window import Window
from kivy.lang import Builder
Window.borderless = True
def capture_mic_data():
chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE : 44100
p = pyaudio.PyAudio()
s = p.open(format = FORMAT, channels = CHANNELS, rate = RATE, input = True,frames_per_buffer =chunk)
global levels
while True:
data = s.read(chunk)
mx = audio.rms(data,2)
if len(levels) >= 100:
levels = []
levels.append(mx)
class Logic(BoxLayout):
def __init__(self,):
super(Logic,self).__init__()
self.plot = MeshLinePlot(color=[1,0,0,1])
def start(self):
self.ids.graph.add_plot(self.plot)
Clock.schedule_interval(self.get_value,0.0001)
def stop(self):
Clock.unschedule(self.get_value)
def get_value(self,dt):
self.plot.points =[(i,j/5) for i,j in enumerate(levels)]
class MainApp(App):
def build(self):
return Builder.load_file("main.kv")
levels =[]
capture_mic_data_thread = Thread(target = capture_mic_data)
capture_mic_data_thread.daemon = True
capture_mic_data_thread.start()
MainApp().run()
kivy file
Logic:
BoxLayout:
orientation: "vertical"
BoxLayout:
size_hint: [1,.8]
Graph:
id: graph
xlabel: "Amplitude"
ylabel: "Sample"
x_grid_label : True
y_grid_label : True
background_color : 0.429,0.3,0.423,1
x_ticks_minor : 10
x_ticks_major :5
color : 1,1,0,1
ymax: 500
ymin: 0
xmax: 100
border_color : 1,1,0,1
BoxLayout:
size_hint: [1,.1]
orientation : "horizontal"
Button:
size_hint:[0.2,1]
text: "START"
bold : True
on_press: root.start()
Button:
text: "STOP"
size_hint_x:0.2
bold :True
on_press: root.stop()
You must handle keyword arguments when you extend a class. Just add that to your Logic class:
class Logic(BoxLayout):
def __init__(self, **kwargs):
super(Logic,self).__init__(**kwargs)
self.plot = MeshLinePlot(color=[1,0,0,1])

Python, Kivy: Problem with calling functions from different classes/screens

I'm having trouble with correctly calling functions from different classes.
I am making a simple game which calculates the score using the amount of time it takes to clear a level. There's a stopwatch running in the background and I want to add a pause button that popup menu, and a resume button inside this popup menu.
The problem is that when calling the pause function from within the popup menu, it will also be returned inside the popup, instead of inside the main widget.
Here is a simplified version of the code:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
from kivy.clock import Clock
root_widget = Builder.load_file('app.kv')
class ExampleWidget(Widget):
time = NumericProperty(0)
paused = False
stop = False
# Keeping time
def increment_time(self, interval):
self.time += .1
print(self.time) # To check if stopwatch is running or not
# Stop should mean that the stopwatch must reset when it starts again.
# When paused it should resume when it starts again
def stop_start_or_pause(self):
# stop stopwatch
if self.stop:
Clock.unschedule(self.increment_time)
print('Stopped')
# Make sure time is 0 when restarting
elif not self.stop and not self.paused:
# Keeping time
self.time = 0
Clock.schedule_interval(self.increment_time, .1)
# Pause stopwatch
elif self.paused:
Clock.unschedule(self.increment_time)
print("!!", self.time) # To make it easier to see if stopwatch actually resumes where it left off
print('unscheduled') # Just to confirm and to make it a bit easier to see
# resume stopwatch
elif not self.paused:
Clock.schedule_interval(self.increment_time, .1)
class PopupMenu(Popup):
example = ExampleWidget()
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
MyApp().run()
.kv file:
#:import Factory kivy.factory.Factory
<PopupMenu#Popup>
auto_dismiss: False
size_hint_y: .8
size_hint_x: .9
title: 'Pause'
example: app.ExampleWidget
BoxLayout:
Button:
text: 'resume'
on_press: root.example.paused = False
on_release: root.dismiss(); root.example.stop_start_or_pause()
size: self.size
<ExampleWidget>:
GridLayout:
col: 2
rows: 3
size: root.size
Button:
text: 'start'
size: self.size
on_press: root.stop = False; root.stop_start_or_pause()
Button:
text: 'stop'
size: self.size
on_press: root.stop = True; root.stop_start_or_pause()
Button:
text: 'Pause menu'
size: self.size
on_press: root.paused = True
on_release: Factory.PopupMenu().open(); root.stop_start_or_pause()
Label:
text: str(round(root.time))
size: self.size
I tried making a function and using Clock.schedule.interval() to keep checking if paused == True, but it keeps returning:
AttributeError: 'float' object has no attribute 'stopped'
This didn't seem like efficient solution anyways, so I didn't want to spend too much time on this function. I also tried to find 'stupid' mistakes (I.e. ',' instead of '.') but that was before I realised that the resume button returned a 'second' stopwatch instead of updating the one I actually wanted to use.
I hope that someone can help, and that my question is clear. English is not my first language so I sometimes have a hard time finding the best way to explain/ask questions.
Thank you in advance!
If I understand your question, the problem is with your MyApp class:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
This code is creating two instances of ExampleWidget. One is returned in the build() method, and one is saved as the ExampleWidget attribute of MyApp. Now, when you use the ExampleWidget attribute of MyApp, you are not referencing the ExampleWidget that is the root of your GUI, so it has no effect on what appears on the screen. The fix is to just creat a single instance of ExampleWidget, like this:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return self.ExampleWidget

Add points to a live graph from a different file

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)

How to trigger an action once on overscroll in Kivy?

I have a ScrollView that's supposed to have an update feature when you overscroll to the top (like in many apps). I've found a way to trigger it when the overscroll exceeds a certain threshold, but it triggers it a lot of times, as the on_overscroll event is triggered on every movement. So is there a way to limit it?
My code looks like this:
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.effects.dampedscroll import DampedScrollEffect
class Effect(DampedScrollEffect):
def on_overscroll(self, *args):
super().on_overscroll(*args)
if self.overscroll < -50:
print('hey')
class TestApp(App):
def build(self):
sv = ScrollView(effect_cls = Effect,
size_hint_y = 0.2)
gl = GridLayout(cols = 1,
size_hint_y = None)
gl.bind(minimum_height = gl.setter('height'))
for i in range(5):
gl.add_widget(Button(text = str(i),
size_hint = (None, None)))
sv.add_widget(gl)
return sv
TestApp().run()
So, as you can see, when the overscroll goes beyond 50, it prints a simple message. But when you actually try it, you'll see that it prints it many times. What I want for it is to trigger an event, stay untriggerable for some time (like a second) and update the content. I've tried messing with boolean flags and Clock, but it didn't work. What could be done here?
I would use a stateful decorator here:
class call_control:
def __init__(self, max_call_interval):
self._max_call_interval = max_call_interval
self._last_call = time()
def __call__(self, function):
def wrapped(*args, **kwargs):
now = time()
if now - self._last_call > self._max_call_interval:
self._last_call = now
function(*args, **kwargs)
return wrapped
class Effect(DampedScrollEffect):
def on_overscroll(self, *args):
super().on_overscroll(*args)
if self.overscroll < -50:
self.do_something()
#call_control(max_call_interval=1)
def do_something(self):
print('hey')
I know this an old question but someone might find it useful
This is a sample from tshirtman's github gist
from threading import Thread
from time import sleep
from kivy.app import App
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.clock import mainthread
from kivy.properties import ListProperty, BooleanProperty
KV = '''
FloatLayout:
Label:
opacity: 1 if app.refreshing or rv.scroll_y > 1 else 0
size_hint_y: None
pos_hint: {'top': 1}
text: 'Refreshing…' if app.refreshing else 'Pull down to refresh'
RecycleView:
id: rv
data: app.data
viewclass: 'Row'
do_scroll_y: True
do_scroll_x: False
on_scroll_y: app.check_pull_refresh(self, grid)
RecycleGridLayout:
id: grid
cols: 1
size_hint_y: None
height: self.minimum_height
default_size: 0, 36
default_size_hint: 1, None
<Row#Label>:
_id: 0
text: ''
canvas:
Line:
rectangle: self.pos + self.size
width: 0.6
'''
class Application(App):
data = ListProperty([])
refreshing = BooleanProperty()
def build(self):
self.refresh_data()
return Builder.load_string(KV)
def check_pull_refresh(self, view, grid):
max_pixel = 200
to_relative = max_pixel / (grid.height - view.height)
if view.scroll_y < 1.0 + to_relative or self.refreshing:
return
self.refresh_data()
def refresh_data(self):
Thread(target=self._refresh_data).start()
def _refresh_data(self):
self.refreshing = True
sleep(2)
self.append_data([
{'_id': i, 'text': 'hello {}'.format(i)}
for i in range(len(self.data), len(self.data) + 50)
])
self.refreshing = False
#mainthread
def append_data(self, data):
self.data = self.data + data
if __name__ == "__main__":
Application().run()

Kivy Adding Widget to a screen

This seems to be a silly question. But I have a widget that I want to add to a screen called GameScreen.
This is my Python code:
class WelcomeScreen(Screen):
pass
class BasicScreen(Screen):
pass
class GameScreen(Screen):
parent = Widget()
game = ShootingGame()
parent.add_widget(game)
Clock.schedule_interval(game.update, 1.0 / 60.0)
# return parent
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcome'))
sm.add_widget(BasicScreen(name='basic'))
sm.add_widget(GameScreen(name='game'))
class ShootingApp(App):
def build(self):
print(sm.current)
return sm
if __name__ == '__main__':
ShootingApp().run()
And this is my kivy code:
<WelcomeScreen>:
Button:
text: "Learn about haptic illusions"
size_hint: None, None
size: 500, 70
pos: 100, 200
font_size: 30
on_release: app.root.current = "basic"
Button:
text: "Play our game"
size_hint: None, None
size: 500, 70
pos: 100, 100
font_size: 30
on_release: app.root.current = "game"
<BasicScreen>:
name: "basic"
<GameScreen>:
name: "game"
The error I am getting is this. And I think this is because I already defined a parent for the widget game. However, I need that parent because the game widget uses width and height values of its parent (e.g., self.parent.width). Is there any workaround for this so that the game widget can be nested in a parent and add the parent to the screen?
kivy.uix.widget.WidgetException: Cannot add <Screen name='game'>, it already has a parent <kivy.uix.widget.Widget object at 0x1093dc8d8>
Thanks guys!!
you can do something like this
class GamesScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.game = ShootingGame()
self.add_widget(self.game)
clock.schedule_interval(self.game.update, 1.0 / 60.0)
It looks to me like what's happening is that you attempt to give GameScreen a parent TWICE. Once when telling it it's parent is Widget(), and again when you add it to the ScreenManager(which would make sm it's parent). Whichever of them is executed first(I think the parent = Widget() line from looking at the Exception) is causing the error when you try it the second time.

Categories