I'm making a label that should update every second or so. I tried making it with the clock schedule but it doesn't seem to work. The weird is if I use a button to call the same function it works fine.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
class FirstLayout(BoxLayout):
r = 0
def __init__(self, **kwargs):
super(FirstLayout, self).__init__(**kwargs)
self.change = self.ids.temp_label
def my_callback(self, *args):
self.r += 1
print self.r
t = str(self.r)
self.change.text = t
class TryApp(App):
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1)
return FirstLayout()
app = TryApp()
app.run()
the .kv file:
<FirstLayout>:
orientation: 'vertical'
Label:
id: temp_label
text: 'something'
Button:
on_press: root.my_callback()
When I run the code I get the prints showing that the function is running but the label doesn't update. Anything wrong with my logic there?
Thank you in advance.
PS: I know there are several questions about this here, sadly those I found were replied with answers about using the Clock which I already do
The problem is that the callback is for an instance that you don't use:
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1) #<--- FirstLayout created and never used
return FirstLayout() #this one will be used :(
Instead, You need to call the method of the FirstLayout that you are using
def build(self):
first_layout = FirstLayout() # "There should be one ..." :)
Clock.schedule_interval(first_layout.my_callback, 1)
return first_layout
Related
I just wanted to ask, is it possible to set transitions for attributes changes in kivy? Let's say my widget with id toolbar has opacity 0:
self.root.ids.toolbar.opacity = 0
Is it possible to set a transition if I plan to change it into opacity 1?
self.root.ids.toolbar.opacity = 1
Answers, suggestions, and corrections are appreciated. Thanks!
I have used kivy for some time and ran into similar problem, but I'm not an expert! So there might be better ways to do this.
That being said, here's a small example introducing fading effect. I believe, you should be able to implement a version for your own problem based on this.
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<Screen1>:
test_widget : ID1
BoxLayout:
padding : (50, 50)
Button:
text: 'Trigger'
on_release:
root.trigger()
Label:
id : ID1
text: "Your Fading Text"
font_size : 30
bold : True
""")
class Screen1(Screen):
# Loading object properties based on the id
test_widget = ObjectProperty()
# Variables for the fading effect
event = None
trans_type = "fade_out"
# The trigger function schedules a task using Clock
def trigger(self):
if self.event != None:
# See below
self.event.cancel()
# The clock calls the given function every X seconds
self.event = Clock.schedule_interval(lambda _:self.transition(self.test_widget), 0.03)
def transition(self, widget):
# Changing the opacity of the widget, pretty self explanatory I think
if self.trans_type == "fade_out":
if (widget.opacity > 0.5):
widget.opacity -= 0.05
else:
self.trans_type = "fade_in"
# We need to make sure to cancel events, otherwise they would not stop
self.event.cancel()
elif self.trans_type == "fade_in":
if (widget.opacity < 1):
widget.opacity += 0.05
else:
self.trans_type = "fade_out"
self.event.cancel()
class TestApp(App):
def build(self):
# Creating a simple screen using ScreenManager and Screen
sm = ScreenManager()
sm.add_widget(Screen1(name='screen1'))
return sm
if __name__ == '__main__':
# Starting the app
TestApp().run()
Also, you might be interested in:
https://kivy.org/doc/stable/api-kivy.animation.html
This might simplify your solution, however I'm not familiar with it, so you have to figure that out for yourself :)
I was making a program similiar to exel.
And then I ran into a issue.
StackLayout stacks good, but because the size of inputs is static it leave a blank space in some cases.
I try to do like size=(self.width/5, self.height/5).
If someone saw how exel look now that there are more inputs of the screen this is why I use ScrollLayout and I want only 5 inputs in one row(user can change it in display settings(I will do it then creating rest of UI))
Here is what I had tried for now.
main.py:
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
class EditorGrid(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
size = dp(self.width / 5)
print(size)
for i in range(0, 100):
b = TextInput(text=str(i + 1), size_hint=(None, None), size=(dp(size), dp(size)))
self.add_widget(b)
def on_size(self, *args):
self.size = dp(self.width/5)
print(self.size)
pass
class pxApp(App):
pass
if __name__ == "__main__":
pxApp().run()
kivy file:
Scrolling:
<Scrolling#ScrollView>
do_scroll_x: True
do_scroll_y: True
EditorGrid:
size_hint: 1, None
height: self.minimum_height
<EditorGrid>:
There is next problem that in init window size is 100x100.
I recommend using setters in binding under kv script. If you want to apply it in python, you could use a binding function for each button.
b = TextInput(.......) #omit size_hint and size attributes
#the following function is called every time EditorGrid(self) size_hint changes when the function is bind
#The function could be defined in a outer scope.
#Look at kivy callback functions to better understand the syntax
binding_function = lambda ins, *a: setattr(ins, 'size_hint', tuple((dim/5 for dim in self.size_hint))
b.bind(size_hint=binding_function)
Im writing an app in Kivy which automaticaly adds Buttons and gives them a unique id using a for loop. This id is then used as a key in the dictionary for a link. So the dictionary works fine and after printing it, it outputs {'button0': 'somewebsite', 'button1': 'other website', 'button2': 'andanotherwebsite'} which is exactly what I want but the button callback function always prints out button2 instead of its own id. Am I assigning the ids wrong? The example below demonstrates my problem.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivymd.utils import asynckivy
from kivy.clock import Clock
class TestButton(Button):
def callback(self):
print(self.id)
class RootWidget(BoxLayout):
def __init__(self):
super().__init__()
self.links = ["somewebsite", "other website", "andanotherwebsite"]
self.dic_btn_to_lnk = {}
self.size_hint = (None, None)
self.size = ("600dp", "50dp")
Clock.schedule_once(self.add_widgets, 0)
def add_widgets(self, *args):
async def update():
number = 0
for link in self.links:
button = TestButton()
button.text = link
button.size = ("200dp", "50dp")
button.pos_hint = {"center_x": .5}
btn_id = "button" + str(number)
button.id = btn_id
button.bind(on_release=lambda x: button.callback())
number += 1
self.dic_btn_to_lnk[btn_id] = link
self.add_widget(button)
print(self.dic_btn_to_lnk)
asynckivy.start(update())
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that your on_release binding is calling button.callback(), and button will be the last Button added by the time the on_release is triggered. The solution is to use partial, which freezes its arguments to their values when partial is executed, so the on_release calls the correct button.callback. Like this:
button.bind(on_release=partial(button.callback))
And to simplify the above, the definition of callback is changed to:
class TestButton(Button):
def callback(self, instance):
print(self.id)
I want to generate a dropdown-list in my second screen managed by Kivys ScreenManager. If I do so, I get this traceback:
...
File "C:/Users/ORANG/PycharmProjects/waldi/playground/cw.py", line 76, in on_text
instance.drop_down.open(instance)
File "C:\Kivy-1.9.0-py2.7-win32-x64\kivy27\kivy\uix\dropdown.py", line 215, in open
'Cannot open a dropdown list on a hidden widget')
kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget
This is the code, which is basically the same as in this
example, just embedded in a screenmanager scenario simple as can be:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Lieferant>:
ComboLayout:
Label:
text: 'Label'
ComboEdit:
size_hint: .5, .3
pos_hint: {'center':(.5, .5)}
# `args` is the keyword for arguments passed to `on_text` in kv language
on_text: self.parent.on_text(self, args[1])
''')
class ComboEdit(TextInput):
"""
This class defines a Editable Combo-Box in the traditional sense
that shows it's options
"""
options = ListProperty(('',))
'''
:data:`options` defines the list of options that will be displayed when
touch is released from this widget.
'''
def __init__(self, **kw):
ddn = self.drop_down = DropDown()
ddn.bind(on_select=self.on_select)
super(ComboEdit, self).__init__(**kw)
def on_options(self, instance, value):
ddn = self.drop_down
# clear old options
ddn.clear_widgets()
for option in value:
# create a button for each option
but = Button(text=option,
size_hint_y=None,
height='36sp',
# and make sure the press of the button calls select
# will results in calling `self.on_select`
on_release=lambda btn: ddn.select(btn.text))
ddn.add_widget(but)
def on_select(self, instance, value):
# on selection of Drop down Item... do what you want here
# update text of selection to the edit box
self.text = value
class ComboLayout(BoxLayout):
rtsstr = StringProperty("".join(("Substrate1,,,Substrate1,,,Substrate1,,,",
"Substrate1,,,Substrate1,,,Substrate_coating",
",,,silicon,,,silicon_Substrate,,,substrate_",
"silicon,,,")))
def on_text(self, instance, value):
if value == '':
instance.options = []
else:
match = re.findall("(?<=,{3})(?:(?!,{3}).)*?%s.*?(?=,{3})" % value, \
self.rtsstr, re.IGNORECASE)
# using a set to remove duplicates, if any.
instance.options = list(set(match))
instance.drop_down.open(instance)
class Intro(Screen):
pass
class Lieferant(Screen):
pass
class CWApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(Lieferant(name='Lieferant'))
return sm
if __name__ == '__main__':
CWApp().run()
Is it possible to combine them? How would you do this?
This code is running if I just comment this line out, which adds a screen before the screen with the dropdown:
sm.add_widget(Intro(name='Intro'))
Ok - for this question I got the "Tumbleweed Badget" - LOL
I admit it's a very special one and makes obvious, how much I am a beginner here. So I asked another question based on the same problem and spend some effort to make it easier to understand the code and to reproduce it. This paid off! Look here for the solution if you tumble once into that kind of problem: Is it a bug in Kivy? DropDown + ScreenManager not working as expected
I know this might be a very basic question, but after spending hours wrapping my head around it I still can't figure it out.
I basically just want to bind the text of a label to a variable in the python code. lets call it value. however it should get updated everytime I run a loop Clock.schedule_interval(RootWidget.update, 1.0/1.0)
here is the python, simplified so its basically just the time, which is also printed just to see if it is actually working.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.core.window import Window
import time
class RootWidget(FloatLayout):
def update(self, *args):
value = time.time()
print value
self.ids.value_label.text = str(value)
class MainApp(App):
def build(self):
Window.size = (800, 480)
r = RootWidget()
Clock.schedule_interval(r.update, 1)
print 'build running'
return r
def on_pause(self):
return True
if __name__ == '__main__':
MainApp().run()
the kv file looks as such:
<RootWidget>:
Label:
id: value_label
text:
y: 20.0
x: 0.0
width: 100.0
italic: False
height: 50.0
Clock.schedule_interval(RootWidget.update, 1.0/1.0)
You need to schedule the update method of an instance, not the class itself.
For instance:
r = RootWidget()
Clock.schedule_interval(r.update, 1)
return r
The clock will pass some arguments by default, so you should also declare the update method to accept these. If you don't want to use them then you can just do:
def update(self, *args):
...