Modify variables within Python through a button in Kivy - python

I have an application that at a certain point needs to check if a variable within its Python code has one value or another, and also requires a button capable of changing that variable from one value to another to also save that information in a text file and that the variable loads it the next time the application is started.
Here I prepared a small program to exemplify what I am looking for:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.core.window import Window
Window.size=(200,200)
data=open('data.txt','r') #Let's say there is a text file called "data" that contains the number 0
Builder.load_string("""
<Screen>
BoxLayout:
orientation:'vertical'
size:root.width,root.height
Label:
text:'Switch On' if app.var==1 else 'Switch Off' #Initially then the label should say "Switch Off"
Button:
text:'Switcher'
on_release: #Change app.var from 0 to 1 or from 1 to 0
""")
class Screen(Widget):
pass
class App(App):
def build(self):
self.var=data.read()
#Save the changes made by the Button
return Screen()
if __name__=='__main__':
App().run()
How can I do this or something similar? A better and simpler way?

You should define a function and bind it to a button. The documentation can really help you here! you can bind your function using my_button.bind(on_press=on_button_click) where:
def on_button_click():
this.my_var += 1
Let me know if you think the documentation is unclear!

Related

Kivy, Python 3.8 - Unable to get SoundLoader working properly

I'm making an app/game and the user is able to play a sound when pressing the sound button, and the same sound is played after the user finishes a level. The next level contains a new sound.
For some reason, my code doesn't act how I want it and the results aren't consistent: sometimes it works only once, sometimes it works twice or more. Sometimes the next level contains the new sound but most of the time it doesn't play anything.
It probably has something to do with the way I load/try to unload audio files because the code returns 'play' when printing out the audio state.
Here is a simplified version of the code:
import time
import kivy
from kivy.app import App
from kivy.core.audio import SoundLoader
from kivy.lang import Builder
from kivy.uix.widget import Widget
root_widget = Builder.load_file('app.kv')
class ExampleWidget(Widget):
def play_sound(self):
file = 'correct.wav'
print('playing ', file)
click_sound = SoundLoader.load(file)
click_sound.play()
time.sleep(2)
print(click_sound.state)
click_sound.stop()
print(click_sound.state)
#click_sound.unload() # Crashes the app
click_sound = ''
class MyApp(App):
def build(self):
return ExampleWidget()
MyApp().run()
app.kv:
<ExampleWidget>:
id: ew
GridLayout:
col: 2
rows: 3
size: root.size
Button:
text: 'play sound'
size: self.size
on_release: ew.play_sound()
I also tried to empty the click_sound variable by assigning it a None value, using 'del click_sound', tried emptying the variable at the start of the function, end or both and I also tried using the unload() function but that returns the following error:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
I'm also getting the following warning. I don't think it's a part of my problem but I'm adding it just in case:
[WARNING] [ffpyplayer ] [wav # 0x7fc738908e00] Discarding ID3 tags because more suitable tags were found.
Hopefully someone can help me out, I thank you in advance.
EDIT:
As ApuCoder said, the code works fine, just not on my macbook. Just tested it on a Windows pc and there were no problems. Does anybody know a way to fix this on Apple computers?

Kivy button: how to assign on_press command defined in another function within same class

I should probably start by saying I am an absolute beginner with Python and Kivy, so please excuse me if the question is very naive.
I am trying to get a Kivy button to run a command on_press. The command is defined in another function which belongs to the same class. However, I always get the following error:
self.searchButton.bind(on_press=self.searchRecipe())
File "kivy_event.pyx", line 419, in kivy._event.EventDispatcher.bind
AssertionError: None is not callable
I have already looked at similar questions (here and here) but still could not figure out what I am doing wrong.
I should also add that, for the sake of learning, I do not want to use .kv files at the moment, but include everything in my python script. After getting a nice understanding of it, I will try to migrate some parts to a .kv file.
Below is a minimal (non-working) example. Of course the searchRecipe function should do more than just printing (connect to a SQLite db and perform some other stuff), but this is just a tryout.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class RootWindow(BoxLayout):
def __init__(self, **kwargs):
super(RootWindow, self).__init__(**kwargs)
self.orientation = 'vertical'
self.searchButton = Button(text="Search")
self.searchButton.bind(on_press=self.searchRecipe())
self.add_widget(self.searchButton)
def searchRecipe(self):
print("It works!")
class RecipApp(App):
def build(self):
return RootWindow()
if __name__ == "__main__":
RecipApp().run()
self.searchButton.bind(on_press=self.searchRecipe())
self.searchRecipe() evaluates to None. You want self.searchButton.bind(on_press=self.searchRecipe).

kivy executes "on_release"-command multiple times

To trigger the acquisition of an image, I use the on_release-function of a kivy-button.
So, whenever this button is clicked (or pressed -- since using a touchscreen) a camera is triggered using gphoto2.
The issue:
From time to time, the function is executed multiple times (taking multiple images), while it was clearly pressed only a single time.
According to the logs, I'm confident, that it's a kivy-related issue (not related to the camera, etc.): Logging entries within the on_release-function appear multiple times within the logs.
I'm running an app with kivy (version 1.9.0) and python (version 2.7.6) under Ubuntu 14.04 LTS (64bit) using a touchscreen.
Any hint on how to debug or fix the issue is welcome.
I was stuck at the same problem for days!
Do you have this line in your code?
Config.set('input', 'mouse', 'mouse, multitouch_on_demand')
If yes, then remove it.
On touch devices, touch gets triggered multiple times.
Hope it helps someone with the same problem.
You could disable the button when the first release event starts, and in the end of the thread where the picture is taken, enable the button again. Then you will not have multiple events from that button executing, but still the main app thread is allowed to continue.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
import time
import threading
Builder.load_string('''
<MyLayout>:
Button:
text: 'Print to terminal'
on_release:
root.button_released(self)
''')
class MyLayout(BoxLayout):
def button_released(self,button):
button.disabled = True
print("{} pressed!".format(button))
threading.Thread(target=self.take_picture, args=(button,)).start()
def take_picture(self,button):
time.sleep(1) # taking picture or whatever
button.disabled = False
class MyApp(App):
def build(self):
return MyLayout()
MyApp().run()

Using external .kv file vs doing things internally?

I have noticed that most examples I foind online don't have an external .kv file. They define all the instances internally. However they also say that having an external .kv file is a good practice. Which is better to do? If having external .kvfiles are better, then how am I supposed to use the code which uses internal code and turn it into external .kv files?
For example, doing this ->
from kivy.app import App
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
class TutorialApp(App):
def build(self):
b = BoxLayout(orientation='vertical')
t = TextInput(font_size=150,
size_hint_y=None,
height=200,
text='default')
f = FloatLayout()
s = Scatter()
l = Label(text='default',
font_size=150)
t.bind(text=l.setter('text'))
f.add_widget(s)
s.add_widget(l)
b.add_widget(t)
b.add_widget(f)
return b
if __name__ == "__main__":
TutorialApp().run()
Instead of-
<ScatterTextWidget>:
orientation: 'vertical'
TextInput:
id: my_textinput
font_size: 150
size_hint_y: None
height: 200
text: 'default'
FloatLayout:
Scatter:
Label:
text: my_textinput.text
font_size: 150
"Internal" usage of kv is through Builder class, which allows you even to load external file. Those examples are worded in a Builder.load_string(...) way because it's way simpler to have a small example in one place in one file.
How to convert it to the external one? Simple, copy&paste the string from Builder.load_string() into a separate .kv file with a name of your class that inherits from App(your main class with build()) and that's it. It'll load the same thing from the external file.
Why it's better or worse? Isn't any of that actually. It's like comparing "java" and python style i.e. either putting everything out of your file and having basically this construction a'la java, where the main file contains this:
class This(something):
SpecialClass.doThis()
AnotherClass.doThat()
and other classes(or different things) are in separate files. Or this construction:
class Special(...):
...
class Another(...):
...
class This(something):
Special.do_this()
Another.do_that()
Both of them are useful and you'll find yourself working with a mix between them. It's about transparency and clearness of your code, but maybe you don't want to have a hundred of files... or a 2MB main.py, pretty much compromise of how do you decide to code.
Edit:
The python vs kv is a funny "fight", but except a for(and while?) loop you can pretty much do everything necessary inside kv in such an easy way! Kv is here to make writing easier e.g. remove too much stuff like add_widget() or basically making an empty class just to rename a widget or to change its size for using it in one place.
With python in a 500line file without kv you won't do that much as with 100 extra lines in kv. The documentation has important parts in python and maybe it's even targeted for users who can't/don't want to use kv. Also this and all examples highly depend on the author of an example and that particular part of the docs.
Which returns me back to the java vs python style of coding I mentioned before. It's pointless to do complicated stuff just because you think it'll feel/look better if you can do it cleaner and more readable i.e. don't just go one way if you have a tool that increase your speed of coding exponentially. Find the middle way.

Associating Screens with GridLayout classes in kivy

I've created a ScreenManager, and created several Screen instances for that ScreenManager.
I'd like each Screen to display a GridLayout class. For example, let's say you have:
class MainScreen(Screen):
...
class MainLayout(GridLayout):
...
When MainScreen is the active screen, I'd like MainLayout to be shown.
Is there a way to do this purely in python (i.e. without markup)? Thank you.
Is there a way to do this purely in python (i.e. without markup)? Thank you.
You never need to use kivy language (I assume that's what you mean by markup), though it's highly recommended where possible because it makes lots of stuff easier.
Nonetheless, to actually answer your question, all you have to do is add your gridlayout widget to your screen widget, something like
mainscreen = MainScreen()
mainlayout = MainLayout()
mainscreen.add_widget(mainlayout)
Then when you set the current screen in your screenmanager to be mainscreen, you should see the GridLayout.
Edit: In case it's unclear, this is in general the way you add widgets to other widgets. When you see an example in kv language like
<MyScreen>:
GridLayout:
...
...ultimately that gets translated to something much like the above example code - an instance of MyScreen is created and a GridLayout is added to it with add_widget.

Categories