I have an issue when I try to update a label text during a for-loop. There are similar entries (e.g.: Update properties of a kivy widget while running code) but they do not seem to fit my issue exactly (or I missed the point…).
I run following code:
*.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
#from time import sleep
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
for i in range(1,10):
self.tobeupd = str(i)
print(self.tobeupd)
input('Write something: ') # new line, see edit below
#sleep(0.5)
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
*.kv
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
Whereas the ‘print’ statement updates the shell regularly, the label text updates at the end of the for-loop only.
Can anyone explain to me why Kivy works this way and how I can overcome this problem?
EDIT: According to PM2Ring and Gugas, I changed the code in order to avoid the sleep-function. The problem remains if I ask the user to enter something before the loop can be continued. The values are updated in the shell but not on the label.
You can use threading for this.
When you do a loop or wait for an input in kivy, the main thread is waiting, and nothing will update on the app. threading will prevent that.
Use threading to make another thread besides the main thread.
Example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.lang import Builder
import threading
Builder.load_string('''
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
''')
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
threading.Thread(target=self.update_label).start()
def update_label(self):
for i in range(1,10):
print(self.tobeupd)
self.tobeupd = str(i)
input('Write something: ') # new line, see edit below
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
Now its worth mentioning that you can keep pushing the button and start threads, even if the first did not finish yet. This might be an unwanted behavior.
This can be prevented by disabling the button in the beginning of the thread, and enabling it again at the end.
Give the button an id in kv:
Button:
id: updatebutton
text: 'Start Update'
on_release: root.upd_ltxt()
And in the thread do like this:
def update_label(self):
self.ids.updatebutton.disabled = True
for i in range(1,10):
self.tobeupd = str(i)
input('Write something: ')
self.ids.updatebutton.disabled = False
You can also use Kivys clock Class, which is an Event dispatcher. It will schedule an event, which is a function. For example, updating your labels text.
from kivy.clock import Clock
def to_be_called_back(self,dt):
print("This function should be periodically executed")
def do_the_loop(self):
Clock.schedule_interval(self.to_be_called(),0.5)
Here the function to_be_called() will be called every 0.5 seconds. The dt variable stands for deltatime and is just apparently needed by the Clock class (without it, it made problems with my code)
I'd still put the do_the_loop() function into a separate thread. But thats what kivy provides for it. If you want to know more about the clock Class head over here.
I believe all you need to do is put
root.update()
whenever you want the GUI to update, so therefore in the loop, after the text change has been made. Works in tkinter like a charm. You can also restrict the update to a specific text area by changing root (or main) to the name of the text area.
Related
import kivy
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.widget import Widget
from kivy.clock import Clock
Builder.load_string('''
<apper>
input_val : input_t
BoxLayout:
size: root.size
Label:
id: label_t
text: root.texter
TextInput:
id: input_t
Button:
on_press : root.doer()
''')
class apper(Widget):
texter = StringProperty()
input_val = ObjectProperty(None)
run = 0
def doer(self, dt):
self.run+=1 # keep track of number of times this function ran
#self.texter = self.ids.input_t.text
print(self.ids.input_t.text, self.run) #prints an empty string for self.ids.input_t.text
self.texter = self.input_val.text
class runs(App):
def build(self):
Clock.schedule_interval(apper().doer, 1.0/2.0)
return apper()
runs().run()
#the code works when you remove the Clock statement and the second argument or apper.doer() and you have to press the button
#to rename the label
#how comes it doesnt work when you introduce the clock statement
#Note that I am new to kivy but not new to programming
When I use a button to run the function on press the correct values from text input are returned but using a clock it doesn't work, can anyone help.
Also when the program should be running properly with the Clock then the button is unnecessary since it updates the label with the text of the TextInput every 0.5 seconds.
Is there any way the code can be rewritten or mended such that the TextInput returns a value and not an empty string.
move the clock to ur apper class: and put it like this
Clock.schedule_interval(self.doer, 1/2)
and declare doer like this:
def doer(self, *args):
and feel free to check this tuto i uploaded recently
https://www.youtube.com/watch?v=DbX-CdVob6E&t=57s
I'm a learning kivy on my own and am trying to create a sublcass of TextInput with the custom behavior that, when selected, the contents of the text box are highlighted (so that a user can then begin typing in a new value). I have two questions, one for each attempt.
My first attempt was this:
from datetime import date
# kivy imports
from typing import Text
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.textinput import TextInput
from kivy.clock import Clock
from kivy.properties import (StringProperty)
class MedAssignApp(App):
def build(self):
return MainWindow()
class MainWindow(AnchorLayout):
pass
class TextEntry(TextInput):
def on_touch_down(self, touch):
super().on_touch_down(touch)
Clock.schedule_once(lambda dt: self.select_all())
I then have several instances of TextEntry in the medassign.kv file:
.. other stuff above
GridLayout:
cols: 4
rows: 3
Label:
text: 'Date'
DateEntry:
Label:
text: 'Shift'
TextEntry:
text: 'THIS WILL BE A DROPDOWN'
Label:
text: 'ST1'
TextEntry:
text: 'Enter name..'
Label:
text: 'ST2'
TextEntry:
text: 'Enter name..'
Label:
text: 'ST3'
TextEntry:
text: 'Enter name..'
Label:
text: 'ST4'
TextEntry:
text: 'Enter name..'
<TextEntry>:
However, when I test the application and I click on one of the text fields, all texts in all instances are highlighted. So my first question is: Does anyone understand why?
My second attempt:
Then I attempted to do it this way (below), which works, but again, I don't know why it works.
# built-in imports
from datetime import date
# kivy imports
from typing import Text
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.textinput import TextInput
from kivy.clock import Clock
from kivy.properties import (StringProperty)
class MedAssignApp(App):
def build(self):
return MainWindow()
class MainWindow(AnchorLayout):
pass
class TextEntry(TextInput):
pass
and medassign.kv
<TextEntry>:
TextInput:
on_touch_down: Clock.schedule_once(lambda dt: root.select_all())
Note: In this second case, if I use root it works as I would like. But if I use self, it does not work as I would like (it behaves like the first attempt).
My second question: Can anyone explain why this works with root?
Thank you!
The first version fails because you have not taken into account that all the widgets get the on_touch_down() call on every touch down event. It is the responsibility of the instances on_touch_down() method to determine if the touch is relevant to that instance. The on_touch_down() method returns a bool:
If True, the dispatching of the touch event will stop. If False,
the event will continue to be dispatched to the rest of the widget
tree.
So your on_touch_down() for every instance applies the select_all(). Here is a modified version of on_touch_down():
def on_touch_down(self, touch):
if self.collide_point(*touch.pos): # check if touch is on this instance
Clock.schedule_once(lambda dt: self.select_all())
return super().on_touch_down(touch) # Let the TextInput handle this touch as normal.
I want to use Kivy program including multiprocessing process.
Sample code worked as i thought;however, a blank white screen which does not accept the operation appeared. It appeared just when i started the multiprocessing process.
I already know that using threading.Thread, the blank screen won't appear;however, an actual program that i want to implement needs multiprocessing process.
How can i remove this blank screen?
This is what i saw.
P.S.
Considering comments, maybe it's a peculiar problem on windows.
I'm using windows 10, Python 3.6.4.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
import time
from datetime import datetime
import multiprocessing as mp
from multiprocessing import Process, Queue, freeze_support
sm = ScreenManager(transition=NoTransition())
Builder.load_string('''
<TestScreen>:
BoxLayout:
orientation: "vertical" # place object vertically
Label:
size_hint: 1, 1
text: 'TITLE'
font_size: 150
color: 1, 1, 1, 1
Button:
text: 'start'
on_press: root.start_mp()
Button:
text: 'stop'
on_press: root.stop_time()
''')
def count_time(x):
for i in range(x):
print('count={}'.format(i))
time.sleep(1)
class Test(App):
def build(self):
sm.add_widget(TestScreen(name='test'))
return sm
class TestScreen(Screen):
p1 = mp.Process(target=count_time, args=(10, ))
def start_mp(self):
self.p1 = mp.Process(target=count_time, args=(10, ))
self.p1.start()
def stop_time(self):
self.p1.terminate()
print(datetime.now())
if __name__ == '__main__':
Test().run()
Turns out that the problem is the ScreenManager that you create at the start of your file. Move the sm = ScreenManager(transition=NoTransition()) into the build method of your app and it should work without creating a second screen.
After some research, it appears that the problem results from the new Process importing the original python script. Since the sm = ScreenManager(transition=NoTransition()) is unprotected in the original file, it gets executed upon that import. So the solution is simply to put that command anywhere that is protected. So another possibility that will work is to move that command into the if __name__ == '__main__': block just before the Test().run() command.
I am having problems understanding the usage of custom Properities and ways of binding methods to events.
Here's my code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import StringProperty
kivy_lang = '''
<MainWidget>:
on_my_property: my_label.text = 'from button bind method via StringProperty' + my_property
Label:
id: my_label
text: root.my_property
Button:
id: my_button
text: 'intro button'
'''
class MainWidget(BoxLayout):
# bind some properties
my_property = StringProperty('0')
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
# if needed to do sth on widget construction
self.ids.my_button.bind(on_press=self.my_method)
def my_method(self,*args,**kwargs):
self.my_property = str(int(self.my_property)+1)
self.ids.my_button.text = 'new'
class MyApp(App):
def build(self):
Builder.load_string(kivy_lang)
return MainWidget()
if __name__ == '__main__':
MyApp().run()
When I run it it renders OK, but when I click a button, as a result I get
NameError: name 'my_property' is not defined
I tried binding method for Button in kv lang with (and removing whole 'init()' on python side):
on_press: root.my_method
and then when I press button the app doesn't crash but nothing happens
Can someone explain me how to adjust this code to work?
I understand the code is a little 'mixed techniques' but I did it that way to get to know different approaches, so I would appreciate if You don't turn it all around :)
1/ you are missing 'self' before 'my_property' in 'on_my_property' bindind, hence the crash
2/ in kv bindings. the python code is called as written, so you need '()' after 'root.my_method', or the statement has no effect.
I try to design a GUI to handle stepper motors via ROS, kivy and python. You can find a minimal version of the GUI below. Actually I want to use a ROS message to update a kivy text input field (read only). In the minimal example, pressing the button should transfer the data of the first input field over a local ROS node to the second input field. Actually it seems, that the Callback within the rospy.Subscriber() doesn't enter the Test class.
Thank You for any suggestions!
main.py
import kivy
kivy.require('1.7.2')
import rospy
from std_msgs.msg import String
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
class Test(BoxLayout):
text_is = 'text before button press'
def Pub(self):
publish = self.ids.test_text_pub.text
try:
ROSNode.new_text_is.publish(publish)
except rospy.ROSInterruptException: pass
class ROSNode(Widget):
def Callback(publish):
print(publish.data) #check if Callback has been called
test.text_is = publish.data
new_text_is = rospy.Publisher('new_text_is', String, queue_size=10)
rospy.Subscriber('new_text_is', String, Callback)
rospy.init_node('talker', anonymous=True)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
test.kv
#:kivy 1.0
<Test>:
BoxLayout:
orientation: 'vertical'
Button:
id: test_button
text: 'publish'
on_press: root.Pub()
TextInput:
id: test_text_pub
text: 'text after button press'
TextInput:
id: test_text_sub
text: root.text_is
If you want your text to automatically update, you need to use Kivy properties.
from kivy.properties import StringProperty
class Test(BoxLayout):
text_is = StringProperty('text before button press')
Kivy properties support data binding, so any updates to the property will propagate through to any bound widget. Binding happens automatically in kv, so when you do this:
text: root.text_is
..you're telling Kivy that when root.text_is is updated, update my text also.
So I found the solution:
I had to add an on_release event to the button.
on_release: test_text_sub.text = root.text_is