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
Related
I have the following code in which I am trying to send time to an Arduino to be displayed on an OLED. the Arduino side works well, if commands are sent individually it will display. However, I want the time to be updated every second. With some tweaking I am able to get it to update ever 2 seconds or so, with sometimes showing two times at once. I tried using threading but I'm not sure if its correct or not.
Here is the python script:
import serial
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from datetime import datetime, timedelta
import time
from kivy.clock import Clock
import threading
ard_connected = False
try:
ard = serial.Serial(
port='COM10',
baudrate = 9600
)
ard.flush()
ard_connected = True
except:
print("Arduino Not Connected")
Builder.load_file('Layout.kv')
class GUILayout(Widget):
def ShowTime(self, state):
threading.Timer(1, self.ShowTime).start()
now = datetime.now()
a = timedelta(seconds=1)
while state == 'down':
if (ard_connected):
current_time = now.strftime("%H:%M:%S")
ard.write(current_time.encode())
ard.flush()
now += a
if (ard_connected):
ard.write(" ".encode())
ard.flush()
class GUI(App):
def build(self):
updateClock = GUILayout()
Clock.schedule_interval(updateClock.ShowTime, 0.5)
return updateClock
if __name__ == '__main__':
GUI().run()
and the .kv file:
<GUILayout>
BoxLayout:
orientation: "vertical"
size: root.width, root.height
GridLayout:
cols: 2
ToggleButton:
text: "Time"
on_state: root.ShowTime(self.state)
backgrund_normal: ""
background_color: (150/255,150/255,150/255,1)
You have too many things starting the ShowTime() method:
Clock.schedule_interval(updateClock.ShowTime, 0.5)
and
on_state: root.ShowTime(self.state)
and
threading.Timer(1, self.ShowTime).start()
And each of those has the possibility to start an infinite loop (while state == 'down':), since the state variable that is passed into ShowTime() will never change. The loop started by clicking on the ToggleButton will run in the main thread, freezing your GUI.
I believe a better approach would be to just start/stop the ShowTime() method at one location. perhaps using the ToggleButton.
Try changing the kv to accomplish that:
ToggleButton:
text: "Time"
on_state: root.startShowTime(self.state) if self.state == 'down' else root.stopShowTime()
backgrund_normal: ""
background_color: (150/255,150/255,150/255,1)
and add/change GUILayout and GUI methods to support that:
class GUILayout(Widget):
def startShowTime(self, state):
self.clock_event = Clock.schedule_interval(self.ShowTime, 0.5)
def stopShowTime(self):
self.clock_event.cancel()
if (ard_connected):
ard.write(" ".encode())
ard.flush()
def ShowTime(self, dt):
if (ard_connected):
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
ard.write(current_time.encode())
ard.flush()
class GUI(App):
def build(self):
updateClock = GUILayout()
return updateClock
I'm new in Kivy and i need your help ..
I have a little question:
I need a dynamic array, which the user can enter first value, in the erst TextInput Box , then he can press the Button "new line", he gets the possibility to enter second value in a new TextInput Box,than he can press again the Button "new line".. he has the possibility to enter a third value in the new TextInput Box ..
at any time he can press "Result" .. to get the sum of this Values in a label
How can I make this dynamic array ?
Thanks
H Lothric ..
this is the code
main.py
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class MainWindow(Screen):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
self.counter = 1
self.textlist = [TextInput()]
self.ids.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.ids.grid.add_widget(self.textlist[0])
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.ids.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.textlist.append(TextInput())
self.ids.grid.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
self.ids.label_id.text = str(result)
class WindowManager(ScreenManager):
pass
class MyMainApp(App):
def build(self):
b1=WindowManager()
MainWindow()
return b1
if __name__ == "__main__":
MyMainApp().run()
and this is the main.kv
<CustButton#Button>:
font_size: 40
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
GridLayout:
cols:1
# you will control that GridLayout from .py so here it's empty
GridLayout:
# set the id to be able to control it from .py file
id: grid
cols: 2
CustButton:
text: "new line"
on_press: root.addnewtextinput()
CustButton:
text: "result"
font_size: "30sp"
on_press: root.getresult()
TextInput:
id:label_id
font_size: 40
multiline: True
this is the code
What's the problem? Make a list of TextInputs and then take the values from it. Something like this:
# create a list with first text input:
self.textlist = [TextInput()]
# I don't know which layout you are using, for example BoxLayout with box variable. Add TextInput to box:
self.box.add_widget(self.textlist[0])
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.textlist.append(TextInput())
self.box.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
return result
Ok, in your case it would be like this:
.py file:
# import this
from kivy.properties import ObjectProperty
from kivy.clock import Clock
...
class MainWindow(Screen):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
# we have to delay running that function, it will run when kv file will be ready to provide widgets by id
Clock.schedule_once(self.getids)
self.counter = 1
self.textlist = [TextInput()]
self.grid.add_widget(Label(text='Input value ' + counter))
self.counter += 1
self.grid.add_widget(self.textlist[0])
# don't forget this
grid = ObjectProperty(None)
# function to connect layout with the variable
def getids(self):
self.grid = self.ids.grid
# function to create new inputs, that button 'new line' calls:
def addnewtextinput(self):
self.grid.add_widget(Label(text='Input value ' + self.counter))
self.counter += 1
self.textlist.append(TextInput())
self.grid.add_widget(self.textlist[-1])
# function to get a result:
def getresult(self):
result = 0
for i in self.textlist:
# you may convert it to float if you need, like float(i.text)
result += int(i.text)
self.ids.label_id.text = str(result)
class MyMainApp(App):
def build(self):
self.b1 = WindowManager()
self.b1.add_widget(MainWindow())
return self.b1
.kv file:
<MainWindow>:
name: "main"
# don't forget to add this
grid: grid.__self__
GridLayout:
cols:1
# you will control that GridLayout from .py so here it's empty
GridLayout:
# set the id to be able to control it from .py file
id: grid
cols: 2
CustButton:
text: "new line"
on_press: root.addnewtextinput()
CustButton:
text: "result"
font_size: "30sp"
on_press: root.getresult()
TextInput:
id:label_id
font_size: 40
multiline: True
I have an application with multiple screens (using ScreenManager), and would like to return to the home screen from any of the other screens: either on >30s inactivity, or on the click of a 'home' button.
Button:
I could add a button to every screen in my .kv file, and bind the following on_press method:
on_press:
screen_manager.transition.direction = 'left'
screen_manager.transition.duration = 0.5
screen_manager.current = 'main_screen'
Is there a better way to add a button on all screens, rather than repeating this on every screen? This would also require me to make sure the button is on the exact same position on each screen.
Automatic check:
When on a screen, is there a way to check how long the screen has not been touched, and if that time is more than 30s then call the method from the button above?
I try to split my graphic code in the .kv file and the code logic in the python file, so I am happy with answers involving both.
Thanks a lot for any pointers, eager to learn more kivy!
You can create a custom button just for that purpose in your kv file:
<MyReturnButton#Button>:
text: 'Return'
pos_hint: {'center_x':0.5, 'y':0}
size_hint: None, None
size: self.texture_size
on_press:
screen_manager.transition.direction = 'left'
screen_manager.transition.duration = 0.5
screen_manager.current = 'main_screen'
Then, just add:
MyReturnButton:
To the kv rule for any screen where you want that Button. Of course, the size, pos_hint, and any other properties can be set to your choice.
As for the automatic screen change, you can use Clock.schedule_once to set a timer in the on_enter method of Screen, and use the on_touch_down method to reset the timer:
def callbackTo2(*args):
screen_manager.current = 'main_screen'
class ScreenOne(Screen):
def __init__(self, *args):
super(ScreenOne, self).__init__(name='ScreenOne')
self.timer = None
def on_enter(self, *args):
print('on_enter ScreenOne:')
# start the timer for 30 seconds
self.timer = Clock.schedule_once(callbackTo2, 30)
def on_leave(self, *args):
# cancel the timer
self.timer.cancel()
self.timer = None
def on_touch_down(self, touch):
if self.timer is not None:
self.timer.cancel()
# reset the timer
self.timer = Clock.schedule_once(callbackTo2, 30)
return super(ScreenOne, self).on_touch_down(touch)
I have a bunch of lights I'm trying to control. Rather than have each button state change call a unique function I want to try and have a multipurpose function as thats what functions are for (as far as I understand).
Button calling function:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
The function:
def changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
That function then calls this function to physically change the state of the light using a 3rd part library.
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
The reason I passed "1" inside of the function is because it didn't like having nothing passed in it (I don't know why it didn't like it). If you hadn't already guessed it, I am new at this. I have a bit of a cpp micro controller background but I'm trying to get my head around python and PC based programming. I'm looking for a bit of advice on how best I can condense this and make it as efficient as possible. I may not know much about python, but, I know I shouldn't be typing practically the same thing out 30 times.
Thanks in advance to anyone that can share some of their wisdom.
Its with noting I am using kivy with python to generate the button.
Full main.py code:
from kivy.properties import StringProperty
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('xx.xxx.xxx.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('down')
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 changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
def KitchenSpot2(self,state):
lights[1].name
lights[1].on = state
def KitchenSpot3(self,state):
lights[2].name
lights[2].on = state
def OfficeSpot1(self,state):
lights[3].name
lights[3].on = state
def OfficeSpot2(self,state):
lights[4].name
lights[4].on = state
def OfficeSpot3(self,state):
lights[5].name
lights[5].on = state
def OfficeSpot4(self,state):
lights[6].name
lights[6].on = state
def JuliaBedside(self,state):
lights[7].name
lights[7].on = state
def JohnBedside(self,state):
lights[8].name
lights[8].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()
Full action.kv code
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
text: "Kitchen Spot 2"
Button:
text: "Kitchen Spot 3"
Button:
text: "Kitchen Spot 4"
Button:
text: "Office Spot 1"
Button:
text: "Office Spot 2"
Button:
text: "Office Spot 3"
Button:
text: "Office Spot 4"
Update:
Python program:
def lightcontrol(self,lightnumber):
if b.get_light(1, 'on'):
lights[lightnumber].name
lights[lightnumber].on (False)
#self.KitchenSpot1(False)
else:
lights[lightnumber].name
lights[lightnumber].on (True)
#self.KitchenSpot1(True)
Kivy button:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.lightcontrol(0)
Have each button call the same function but with a different parameter.
# Add the a number parameter here based on what you've
def KitchenSpot(self,state, light_index):
lights[light_index].name
lights[light_index].on = state
Then in the KV file,
Button:
text: "Kitchen Spot 3"
on_press: root.KitchenSpot(state, light_index = 3)
Button:
text: "Kitchen Spot 4"
on_press: root.KitchenSpot(state, light_index = 4)
You only have to create the function one, with each button passing in the relevant light_index number.
Neither knowing kivy nor phue I've tried to reduce the problem to your code redundancy by abstracting the definition of your methods (and also the creation of your action.kv file).
So I hope this what you are looking for: First, I would define all the relevant data of the buttons in a global variable, like:
BUTTONS = [
{'id': 0, 'methodname': 'KitchenSpot1', 'text': 'Kitchen Spot 1'},
{'id': 1, 'methodname': 'KitchenSpot2', 'text': 'Kitchen Spot 2'},
...
]
Then define your Controller-class with all unique methods just like you did (__init__ and update in your case; however I don't see what update should do, I just left it be):
class Controller(GridLayout):
state = StringProperty('down')
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down'
else:
self.state = 'normal'
# this iteratively creates your button-individual methods
for button_dict in BUTTONS:
def func(self, state):
lights[button_dict['id']].name # whatever this might do just adressing the name attribute. Is it a python-property on lights that does some action?
lights[button_dict['id']].on = state
def func_change(self, change):
if change == True and b.get_light(button_dict['id'], 'on'):
getattr(self, button_dict['methodname'])(False)
else:
getattr(self, button_dict['methodname'])(True)
# create .KitchenSpot1, .KitchenSpot2, ...
setattr(Controller, button_dict['methodname'], func)
# create .changeKitchenSpot1, .changeKitchenSpot2, ...
setattr(Controller, "change{}".format(button_dict['methodname']), func_change)
The instantiated Controller will have bound methods named accordingly to all methodnames and change-methodnames.
Finally you can create your action.kv file dynamically
actionkv_toggle_button = """
ToggleButton:
id: {methodname}Toggle
text: "{text}"
on_press: root.change{methodname}(1)
#on_release: root.{methodname}(False)
#state1 = app.update.h
state: root.state
"""
actionkv_str = """
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
{buttons}
""".format(
buttons="".join([
actionkv_toggle_button.format(
methodname=button_dict['methodname'],
text=button_dict['text']
) for button_dict in BUTTONS
])
)
this gives the output
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKitchenSpot1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
id: KitchenSpot2Toggle
text: "Kitchen Spot 2"
on_press: root.changeKitchenSpot2(1)
#on_release: root.KitchenSpot2(False)
#state1 = app.update.h
state: root.state
Save it to a file
with open('action.kv', 'w') as f:
f.write(actionkv_str)
Helpful links:
Dynamically attaching methods to a class
String formatting in python
List comprehension
I want to access root widgets ids from other rootwidgets, but I can't seem to fully grasp how referencing works in Kivy and using a ScreenManager with different screens makes it even harder for me.
I want to achieve the following:
Edit: single file version
(This code assumes you're going to build a complex app, so I don't want to load all code at startup. Hence the kv_strings are loaded when switching screen, and not put into kv code of the ScreenManager. Code is based on the Kivy Showcase.)
Code main.py, Edit 2: working code (see answer why)
#!/usr/bin/kivy
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import StringProperty, ObjectProperty
kv_foo = '''
<FooScreen>:
id: fooscreen_id
BoxLayout:
id: content
orientation: 'vertical'
spacing: '20dp'
padding: '8dp'
size_hint: (1, 1)
BoxLayout:
orientation: 'vertical'
Label:
id: important_text
size_hint_y: 0.3
text: app.imp_text
Button:
id: magic_change
size_hint_y: 0.3
text: "Change text above to text below (after screen switch)"
on_press: app.change_text()
ScreenManager:
id: sm
on_current_screen:
idx = app.screen_names.index(args[1].name)
'''
class FooScreen(Screen):
# 'content' refers to the id of the BoxLayout in FooScreen in foo.kv
def add_widget(self, *args):
if 'content' in self.ids:
return self.ids.content.add_widget(*args)
return super(FooScreen, self).add_widget(*args)
class FooApp(App):
imp_text = StringProperty("Should change to text from id: magic_text")
screen_magic = ObjectProperty()
magic_layout = ObjectProperty()
def build(self):
self.title = 'Foo'
self.root = root = Builder.load_string(kv_foo)
# Trying stuff with References
self.sm = self.root.ids.sm # ScreenManager
# Setting up screens for screen manager
self.screens = {}
self.available_screens = [kv_mainmenu, kv_magic]
self.screen_names = ['MainMenu', 'Magic']
self.go_screen(0)
# Go to other screen
def go_screen(self, idx):
print("Change MainScreen to: {}".format(idx))
self.index = idx
# Go to not main menu
if idx == 0:
self.root.ids.sm.switch_to(self.load_screen(idx), direction='right')
# Go to main menu
else:
self.root.ids.sm.switch_to(self.load_screen(idx), direction='left')
# Load kv files
def load_screen(self, index):
if index in self.screens:
return self.screens[index]
screen = Builder.load_string(self.available_screens[index])
self.screens[index] = screen
# if index refers to 'Magic' (kv_magic), create reference
if index == 1:
Clock.schedule_once(lambda dt: self.create_reference())
return screen
# Trying to get id's
def create_reference(self):
print("\nrefs:")
# Get screen from ScreenManager
self.screen_magic = self.sm.get_screen(self.screen_names[1])
# screen.boxlayout.magiclayout
self.magic_layout = self.screen_magic.children[0].children[0]
def change_text(self):
# Get text from id: magic_text
if self.magic_layout:
self.imp_text = self.magic_layout.ids['magic_text'].text
kv_mainmenu = '''
FooScreen:
id: mainm
name: 'MainMenu'
Button:
text: 'Magic'
on_release: app.go_screen(1)
'''
kv_magic = '''
<MagicLayout>
id: magic_layout
orientation: 'vertical'
Label:
id: magic_text
text: root.m_text
FooScreen:
id: magic_screen
name: 'Magic'
MagicLayout:
id: testmagic
'''
class MagicLayout(BoxLayout):
m_text = StringProperty("Reference between widgets test")
if __name__ == '__main__':
FooApp().run()
Question
How can I set up proper references that the button "Change text above..." can retrieve magic_text.text ("Reference between widgets test") and change self.imp_text to magic_text.text?
I found a way to reference to Kivy widgets not loaded at the startup of the app without using globals. Thanks #inclement for ScreenManager.get_screen().
I had to add the following code:
class FooApp(App):
screen_magic = ObjectProperty()
magic_layout = ObjectProperty()
...
# Trying to get id's
def create_reference(self):
print("\nrefs:")
# Get screen from ScreenManager
self.screen_magic = self.sm.get_screen(self.screen_names[1])
# screen.boxlayout.magiclayout
self.magic_layout = self.screen_magic.children[0].children[0]
def change_text(self):
# Get text from id: magic_text
if self.magic_layout:
self.imp_text = self.magic_layout.ids['magic_text'].text
self.screen_magic is assigned the screen I need (<FooScreen>) and self.magic_layout is assigned the widget I need (<MagicLayout>). Then I can use the ids from <MagicLayout> to access the Label magic_text's text.
(For full code see updated question)