For some process in my app, I use the internet to acquire some data. So for those processed I want a simple popup box(with a text Loading...) to appear at the begining of the process, but when I ran the test code, I observed that the popup box displays at the end of the process rather than at the begining of the process which makes it useless. Here is the test code I am using. Your help is appreciated, thanks.
class ScreenManagement(ScreenManager):
def popup(self):
self.pop_up=Popup(title='Loading...')
self.pop_up.open()
def popup_done(self):
self.pop_up.dismiss()
def ite(self):
for i in range(100):
App.get_running_app().root.current='second'
return i
def thread_it(self,fx):
self.popup()
mythread = threading.Thread(target=fx)
mythread.start()
def ite(self,num):
for i in range(num):
txt=str(i)*40
self.ids.lbl.text=txt
print txt
#if i==num-1: #this is not working
# self.popup_done()
class labelApp(App):
def build(self):
pass
labelApp().run()
.kv file
ScreenManagement:
Screen:
BoxLayout:
Button:
#on_press:root.popup()
#on_release:root.popup_done()
on_press:root.thread_it(root.ite(40000))
on_press:root.current='second'
Screen:
name:'second'
BoxLayout:
Label:
id:lbl
text: 'hello'
The problem is with your ScreenManagement.ite() method. It doesn't run in thread and you've already noticed that anything that could compete with Kivy's main loop and doesn't run in Thread will freeze the main loop. Kivy has Clock which might interest you too.
For this exact code however you'll need partial, which will freeze a function in time and return a thing you can pass as argument, so that it wouldn't be executed in-place where you want to add it as an argument to a thread function (e.g. if ite() means executing, partial will remove those brackets and let Thread add them when it's necessary)
Thread(target=fx) means after passing the ite() method basically this:
Thread(target=root.ite(40000))
i.e. runs that thing in-place and as ite() method doesn't return anything, it'll do this:
# freeze main loop
# after done, call Thread
Thread(target=None)
and starts it. So you freeze your main loop and after the ite() ends, the Popup gets its turn to show up. Let's fix it with partial:
#:import partial functools.partial
<ScreenManagement>:
Screen:
BoxLayout:
Button:
#on_press:root.popup()
#on_release:root.popup_done()
on_press: root.thread_it(partial(root.ite,400))
on_press: root.current='second'
Related
Can I just put more function in this on release or this wont work. I want to have more pop functions and pictures displayed, maybe some audio, etc. This is .kv file:
<Root>:
orientation: 'vertical'
RecordButton:
id: record_button
background_color: 1,1.01,0.90,1
text: 'Order'
on_release:
self.record()
root.pop1()
height: '100dp'
size_hint_y: None
TextInput:
text: record_button.output
readonly: True
Defining an event callback as sequence of statements.
Inside the KV file
Indentation and thus structuring control-flow readable is limited in a KV file. As inclement commented there are basically 2 ways for defining a callback sequence:
statements per line (same indentation)
semicolon separated statements
on_release:
self.record()
root.pop1()
on_press: print('pressed'); self.insert_text("pressed!")
See Kivy-language syntax Valid expressions inside :
[..] multiple single line statements are valid, including those that escape their newline, as long as they don’t add an indentation level.
Define a function in Python
You have more flexibility to define the a function inside the Python script and declare this callback on the event inside the KV file.
For example the function as method of your RecordButton (assume its a class extending Button) in Python:
class RecordButton(Button):
# callback function tells when button released
# It tells the state and instance of button.
def record_and_pop(self, instance):
print("Button is released")
print('The button <%s> state is <%s>' % (instance.text, instance.state))
self.record()
root.pop1()
then used inside the KV:
RecordButton:
on_release: self.record_and_more()
See also
kivy: firing multiple functions on 1 button click
Kivy API: Button class
I'm trying to make an app with KivyMD/Kivy, and I'd like to change a label's text multiple times with a few seconds of interval between the changes. I initially tried to do this with time.sleep(), but this froze up the GUI completely, which made the label changes and such not work.
I've seen that wxPython has the wx.CallLater() function which (if I understand correctly) will call a certain function in some amount of time without freezing up the GUI. In this thread, people were talking about threading, but it seemed to rise another problem without fixing the initial problem, so I'm really not sure if this would work in my case.
So is threading the way to go, is there an equivalent of wx.CallLater() in Kivy, or is there another better solution to my problem?
Working test code:
from kivymd.app import MDApp
from kivy.lang import Builder
import time
KV = '''
MDScreen:
MDFillRoundFlatIconButton:
id: button
icon: 'git'
on_release: app.some_func()
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def some_func(self):
for i in range(3):
self.root.ids.button.text = str(3 - i)
time.sleep(3)
self.root.ids.button.text = 'Go'
Test().run()
As #John Anderson suggested, the Clock object from kivy.clock has methods that achieve the same thing as wx.CallLater().
from kivy.clock import Clock
# to schedule an event once:
Clock.schedule_once(lambda _: some_function(), in_x_seconds)
# to schedule an event repeatedly:
Clock.schedule_interval(lambda _: some_function(), every_x_seconds)
I am working on a doorbell project using a raspberry pi 3 and the 7inch touchscreen display. I am using kivy to power the GUI however have run into the following issue I cannot seem to solve.
The kivy app loads on a button press and gives the user options for authentication (e.g rfid, keypad etc) On selecting an option it takes the user to another screen telling them to input their pin in the case of the keypad option.
This is just the snippet for the keypad screen:
class KeypadScreen(Screen):
keypadTextTest = StringProperty("Please Input 4 Digit Passcode")
def Decision(self, *args):
keypadOutput = FourDigitCodeCheck()
if keypadOutput == True:
self.change_text()
Clock.schedule_interval(self.change_text, 0.1)
self.change_text()
doorOpen()
return
def change_text(self, *args):
self.keypadTextTest = "Door Open"
When the user inputs their pin and the code returns True (Ie it is the correct pin) I wish for the label to change to say "Door Open" and then run the doorOpen function (seen below) which for now just turns an LED on and off.
def doorOpen():
print ("Door Open : Please Enter")
GreenLED("ON")
sleep(5)
GreenLED("OFF")
return
Currently however the doorOpen function is called before the label changes meaning that the LED turns on, then off 5 seconds later and then the label changes.
I have tried various different methods of updating the label text and have even tried adding in different alterations afterwards to try and force an update before the doorOpen function is ran however to no avail.
Any input would be greatly appreciated as has been a tedious afternoon. Below is the kivy lang snippet for the keypad screen encase something is wrong there:
<KeypadScreen>:
on_enter: root.Decision()
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
Label:
id: keypadtext
text: root.keypadTextTest
font_size: 50
Pastebin of full python script: https://pastebin.com/K2CnnmVB
Pastebin of full Kivy script: https://pastebin.com/9mgZFxyx
I was testing out Kivy's markup feature. The basic outline of my test program is there are 4 labels and a button and if the button is pressed, it changes the color of the first letter of label's text. Now, the problem is when I press the button for the first time, it changes the color of first letter of all the label's text BUT from the second press onwards, it starts adding the markup syntax in the reverse manner at the beginning of the text. This is the program:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
import string
Builder.load_string(
'''
<CLabel#Label>:
markup: True
<box>:
orientation: 'vertical'
Button:
text: 'press'
on_press: app.change()
CLabel:
id: a
text: 'abcd'
CLabel:
id: b
text: 'efgh'
CLabel:
id: c
text: 'ijkl'
CLabel:
id: d
text: 'mnop'
'''
)
class box(BoxLayout):
pass
class main(App):
def change(self):
for lol in string.lowercase[:4]:
self.root.ids[lol].text = '[color=#E5D209]{}[/color]{}'.format(self.root.ids[lol].text[0], self.root.ids[lol].text[1:])
def build(self):
return box()
if __name__ == "__main__":
main().run()
This is the output after the first press:
This is the output after the second press:
This is the output after the third press:
I hope you get the problem now. The markup syntax at the beginning of the text keeps on increasing with the number of times the button is pressed.
I thought maybe it was the loop's fault. So I removed the loop and tested with only the first widget. Same problem.
Now here's the catch- when I change the color by changing the contents of the change function like this:
def change(self):
self.root.ids.a.text = '[color=#E5D209]a[/color]bcd'
self.root.ids.b.text = '[color=#E5D209]e[/color]fgh'
self.root.ids.c.text = '[color=#E5D209]i[/color]jkl'
self.root.ids.d.text = '[color=#E5D209]m[/color]nop'
It works perfectly fine. But by doing this method, I'll have to copy paste a lot of lines. This was just a snippet of what I'm working on. The real project I'm working on has more than 15 labels and copy pasting for each and every label is tiresome. It'd be much better if it is done by a loop. It makes work short and precise.
After this, out of frustration I tried with get_color_from_hex method by this code:
self.root.ids[lol].text[0] = self.root.ids[lol].text[0].get_color_from_hex('#E5D209')
But I ended up getting an error message saying:
AttributeError: 'str' object has no attribute 'color'
I'd be really glad if someone came with a way to change the color of first letter of the text of god knows how many labels. :'(
The markup is part of the string stored in text. So the second time you run the loop, indeed the first character ([) gets inserted in between the markup tags, messing up the parsing.
What you want to do could be achieved by storing the raw text in another StringProperty, let's call it _hidden_text. Then, in the loop, you can set
self.root.ids[lol].text = '[color=#E5D209]{}[/color]{}'.format(self.root.ids[lol]._hidden_text[0], self.root.ids[lol]._hidden_text[1:])
In this way you avoid reusing the added markup.
Of course you may want to set up bindings for making the assignment _hidden_text→text automatic.
Edit:
Add this class definition:
class CLabel(Label):
hidden_text = StringProperty('')
then change the kv style for CLabel to
<CLabel>:
markup: True
text: self.hidden_text
and each use of CLabel should look like
CLabel:
id: a
hidden_text: 'abcd'
I'm currently struggling with a memory usage problem in Kivy.
When a popup is created and opened, memory usage goes up a little (which is normal), but when the user closes it (with the dismiss() method, which closes the popup and removes it from its parent), the memory isn't released.
So, if the user decides to open the popup a lot of times, the program will eventually use a lot of memory. Here is a part of my code that shows the problem by creating, opening and then dismissing a popup 500 times.
# py file
class OptionsView(Screen):
def popupLoop(self):
for x in range(0, 500):
popup = self.MyPopup()
popup.open()
popup.dismiss()
class MyPopup(Popup):
pass
# kv file
<OptionsView>:
BoxLayout:
orientation: "vertical"
Button:
text: "Popup Loop"
on_press: root.popupLoop()
<MyPopup>:
size_hint: (.6, .6)
title: "Confirmation"
BoxLayout:
Button:
text: "Cancel"
on_press: root.dismiss()
Pressing the "Popup Loop" button in the OptionView screen causes the program to jump from 1.2% memory usage to 11.7% (according to top). Resizing the window (which calls gc.collect()) does bring this number down a little bit, but it remains really high.
How can I prevent this from happening? (Bear in mind that I'm far from being a Python/Kivy expert, so even if the solution is really obvious to you, please try to explain it to me!)
popup.dismiss() will not remove the popup from memory immediately.
Maybe this will help How to force deletion of a python object?
Second; on why sometimes your popups get dismissed and sometimes they don't, you have to understand that UI frameworks need to be programmed using events. Events/progression do not happen in linear fashion.
Better test for what you are trying to check would be using Clock
self.pops = 0
Clock.schedule_once(self.test_pops)
def test_pops(self, dt):
if self.pops > 10:
return
self.pops += 1
pup = self.MyPopup()
pup.bind(on_parent=self.dismiss_pup)
pup.open()
def dismiss_pup(self, pup, parent)
# popup was opened, please close it now
pup.unbind(on_parent=self.dismiss_pup)
pup.dismiss()
Clock.schedule_once(self.test_pops)
It would be a lot simpler to just use the web debugger module geared towards this instead though.
http://kivy.org/docs/api-kivy.modules.webdebugger.html