I'm trying to program a progress bar which counts down from 60 to 0 seconds as soon as the screen it is on opens, however I can't work out why the value of the bar isn't updating.
I believed self.ids.pb.value was a valid way to do this, which leads me to think I'm making a mistake elsewhere.
Thanks in advance.
Error
KeyError: 'pb'
AttributeError: 'super' object has no attribute '__getattr__'
.py file
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, CardTransition
from kivy.uix.progressbar import ProgressBar
class CountDown(ProgressBar):
def count(self):
self.ids.pb.value = 60
seconds = 60
def count_it(seconds):
if seconds == 0:
return
seconds -= 1
self.ids.pb.value = seconds
Clock.schedule_once( lambda dt: count_it(seconds), 1)
Clock.schedule_once( lambda dt: count_it(60), 1)
class EasyMode(Screen):
pass
class TutorialEasy(Screen):
pass
class GIFapp(App):
countdown = CountDown()
def build(self):
return Builder.load_file("testkivy.kv")
Kv file
<TutorialEasy>:
Button:
on_release: app.countdown.count()
on_release: app.root.current = "EasyMode"
<EasyMode>:
CountDown:
id: pb
max:60
Update
Value wasn't updating once errors were fixed because I missed declaring the value in Kv lang ie.
<EasyMode>:
CountDown:
id: pb
max:60
value: app.countdown.value
The value is already in the CountDown class.
So you can set the value with self.value = seconds
If you are in the EasyMode class, it would work with self.ids.pb.value = seconds
Related
I'm trying a custom List item based on the KivyMD's documentation examples, but I'm having a problem identifying which of my chechkboxes has been activated.
With * args I can access its state and which object it is but it prints like this:
{<__ main __. RightCheckbox object at 0x000001B1A62E6970>, False}.
The problem is that "0x000001B1A62E6970" is not a constant value (this value may change with the execution of the code) that represents the checkbox as its id does.
My minimal code KV:
KV = '''
MDCard:
orientation : 'vertical'
size_hint : (0.8,0.3)
pos_hint : {"center_x":.5,"center_y":.5}
elevation : 15
padding : 20
spacing : 30
id: box
MDList:
id: scroll
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
'''
And the MainApp and Custom Class definitions:
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.icon_definitions import md_icons
class ListItemWithCheckbox(OneLineAvatarIconListItem):
'''Custom list item.'''
icon = StringProperty("android")
def on_press(self):
print(self.text)
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, *args):
print(args)
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
icons = list(md_icons.keys())
for i in range(5):
self.root.ids.scroll.add_widget(
ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
)
MainApp().run()
What I have tried:
I have tried to give a default id for the RightCheckbox to later change it to a unique one when putting the widgets in the list and thus access their states in something like "root.ids.checkboxid" but I don't know how to put it when doing self.root.ids.scroll.add_widget (ListItemWithCheckbox (text = f "Item {i}", icon = icons [i]))
Also in the on_active method (when any checkbox is selected) of the RightCheckbox class I have tried to print various attributes such as MDCheckBox.active .icon .ids. .text but none of them prints anything to help me identify which specific checkbox has been selected
I would really appreciate if anyone can help Thanks
One way to do this is to create a reference to the ListItemWithCheckbox within the RightCheckbox like this:
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
listItem: root
Then your on_active() method can use that reference:
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, rcb, value):
print(rcb.listItem.text, 'is', value)
I'm learning from a book about making apps with Kivy and there's this block of code:
from kivy.app import App
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class TestApp(App):
press_count = 1
def button_press(self, button_pressed):
print('Button Pressed', TestApp.press_count, 'TImes')
TestApp.press_count += 1
def build(self):
my_btn = Button(text='Click Me')
my_btn.bind(on_press=TestApp.button_press)
textinput = TextInput(text='Data Inside TextInput')
box_layout = BoxLayout(orientation='vertical')
box_layout.add_widget(widget=my_btn)
box_layout.add_widget(widget=textinput)
return box_layout
if __name__ == '__main__':
TestApp().run()
when I run this I get understandably TypeError: button_press() missing 1 required positional argument: 'button_pressed' my question is why the author of the book isn't getting this error and the code runs ?
my_btn.bind(on_press=TestApp.button_press)
This is wrong, it should be my_btn.bind(on_press=self.button_press).
I would like to delay Kivy screen manager transition, which I can do for example like this:
#!/usr/bin/env python
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.button import Button
from kivy.clock import Clock
Builder.load_string('''
#: kivy 1.9.2
#: import sm kivy.uix.screenmanager
#: set base_font_size 25
<MainWidget>:
manager: screen_manager
ScreenManager:
id: screen_manager
transition: sm.NoTransition()
Screen:
name: 'screen_one'
Button:
text: 'Screen 1, press to switch to Screen 2'
font_size: base_font_size
on_press:
root.manager.current = 'screen_two'
root.statusmsg = 'Screen 2, press to launch Armageddon'
Screen:
name: 'screen_two'
Button:
text: root.statusmsg
font_size: base_font_size
disabled: True if root.statusmsg == 'Busy launching Armageddon' else False # prevent multiple activation
on_press:
root.deliver_payload()
''')
class MainWidget(FloatLayout):
statusmsg = StringProperty()
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
def deliver_payload(self, dt=0):
if not dt: # called via on_press
self.statusmsg = 'Busy launching Armageddon'
# launch Armageddon here
Clock.schedule_once(self.deliver_payload, 2)
else: # scheduled via Clock
self.manager.current = 'screen_one'
class MyApp(App):
def build(self):
mw=MainWidget()
return mw
if __name__ == '__main__':
MyApp().run()
However, manager.current is a Kivy StringProperty and I should be able to assign a new value to it with the set() method and perhaps schedule this with Clock e.g. Clock.schedule_once(lambda dt: self.manager.property('current').set(??, 'screen_one'), 2).
I wasn't able to find documentation for Kivy StringProperty set() method. It seems the method takes two args and the first argument needs to be of type kivy._event.EventDispatcher - according to traceback info that I get when I try some random guesses. Can anyone point to some documentation, or examples of use for the set() method?
EDIT: I realise I did not make it clear what I want to do ultimately - I am asking if it is possible to schedule the Kivy property set() method using Kivy Clock. For example, I am able to toggle a Kivy BooleanProperty like this:
if self.property('myboolean').get(self):
self.property('myboolean').set(self, False)
else:
self.property('myboolean').set(self, True)
But I was not able to schedule the method using Clock. The following line generated no errors but did not have any effect either:
Clock.schedule_once(lambda dt: self.property('myboolean').set(self, False),0)
In the case of Kivy StringProperty set() method, I was not even able to figure out what the 1st argument needed to be, so did not get as far as trying to schedule it with Clock.
FURTHER EDIT:
Although screen manager 'current' is a StringProperty according to documentation, it appears to behave somewhat differently. With a StringProperty I am able to do the following:
self.property('statusmsg').set(self, 'Armageddon now')
However, attempting something like this with manager.current does not succeed:
self.manager.property('current').set(self.manager, 'aux_screen')
The error message is:
self.manager.property('current').set(self.manager, 'screen_one')
TypeError: Argument 'obj' has incorrect type (expected kivy._event.EventDispatcher, got kivy.weakproxy.WeakProxy)
Confusing.
I stumbled across this by chance Kivy: Changing screens in screen manager with an on_press event (thank you, Tshirtman) and I now have one answer. The following change gives me what I want - the ability to schedule screen change directly (i.e. without an intermediate method to call):
def deliver_payload(self):
self.statusmsg = 'Busy launching Armageddon'
Clock.schedule_once(lambda dt: self.manager.setter('current')(self.manager,'screen_one'), 2)
So it appears that for assigning a new value to screen manager's 'current' property, the correct method to use is the 'setter' method (not the 'set' method that I was attempting to use).
You can set a new value for a StringProperty by simply doing a self.manager.current = 'some string', as you do in your code.
So basically I have a kivy project in which,I have a layout that I use in many classes,as such..I have made in custom and put it in a separate file so I can just reference it from different parts of my code.
Snippet:
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string('''
<CustLayout>:
#code here
''')
class CustLayout(BoxLayout):
t_length = NumericProperty(0)
my_len = 0
print(my_len)
def __init__(self, **kwargs):
super(Silvertable,self).__init__(**kwargs)
self.bind(t_length=self.on_t_length)
#This statement is executed after all other prints
print(self.t_length)
def on_t_length(self,instance,length):
#I'd like to get kv file value before the next line
self.my_len = length
print(my_len)
My kiv file:
#:import Silvertable silvertables.Silvertable
#chunk of code
BoxLayout:
Silvertable:
t_length: 5
Here,I DO get the value but unfortunately too late.That is,I get the value after the program has finished.my_len Doesn't change it's still 0
Bind Kivy Properties
The value of my_len is 0 (zero) because the print() functions were executed first before the run() method.
The value of my_len did change from 0 to 5 as per binding on property of t_length.
Please refer to the example and trace for details.
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.lang import Builder
Builder.load_string('''
<CustLayout>:
t_length: 5
''')
class CustLayout(BoxLayout):
t_length = NumericProperty(0)
my_len = 0
print("\tCustLayout.class level-start: my_len=", my_len)
def __init__(self, **kwargs):
super(CustLayout,self).__init__(**kwargs)
print("\nCustLayout.__init__:")
self.bind(t_length=self.on_t_length)
#This statement is executed after all other prints
print("\tself.t_length=", self.t_length)
print("\tself.my_len=", self.my_len)
def on_t_length(self, instance, length):
print("\nCustLayout.on_t_length:")
#I'd like to get kv file value before the next line
self.my_len = length
print("\tself.my_len=", self.my_len)
print("\tCustLayout.class level-end: my_len=", my_len)
class TestBindProperty(App):
def build(self):
print("\nTestBindProperty.build:")
return CustLayout()
def on_stop(self):
print("\tTestBindProperty.on_stop: my_len=", self.root.my_len)
if __name__ == "__main__":
TestBindProperty().run()
Output
i have a issue which i cant solve.
When i run the following code i get this error
AttributeError: 'float' object has no attribute 'root'
Everything works fine till i ad a Clock.schedule
Code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.clock import Clock
global C
C = 0
class Example(App):
def build(self):
global C
C = C + 1
y = str(C)
self.root = FloatLayout()
self.label = Label(text=y, pos=(0,0), size_hint=(1.0,1.0), halign="left")
self.label.text_size = self.label.size
self.root.add_widget(self.label)
return self.root
Clock.schedule_once(build, 0.1)
Example().run()
For Example: When i want to update the time, i call a method every second with
#Clock.schedule_interval(test, 1)
So i call the Method every second
But if i want to update the time and send it to a lable with the ID time i get the same error....
def test(self):
z = time.strftime("%H:%M:%S")
self.ids.time.text = (y)
Clock.schedule_interval(test, 1)
My goal in this projekt is to show the time on every page and update the status of different inputs on different screens to lables for example every second.
i tried the following command to but i guess it doesnt work for more then one screen
self.the_time.text = MyTime
build is a method, and as such be called as self.build, also, it should accept a float argument for the elapsed time since the scheduling.
I assume what happens is that the method is called unbound (no implicit self) and self is assigned that float value, so the method tries to work with that float as self, which doesn't work for pretty obvious reasons.
TL&DR: use Clock.schedule_once(self.build, 0.1) and change build declaration to something like def build(self, dt=0):.