Dynamically deleting and loading Screen object in Kivy - python

I am currently developing an embedded system with kivy.
Therefore, I found that if I make many screens, it slows down the program a lot.
Is there a good way to dynamically control screens so it does not slow down?
For instance, when I have 4 screens in ScreenManager like below,
MyScreenManager:
id: myscreenmanager
transition: FadeTransition()
SCRN_LOADING:
SCRN_IDLE:
SCRN_CALCULATING:
SCRN_RESULT:
Would it be possible to:
innitially load SCRN_LOADING first.
loads SCRN_IDLE and SCRN_CALCULATING while loading.
when loading is done, remove SCRN_LOADING screen object.
loads SCRN_RESULT while calculating.
when going back to idle, remove SCRN_RESULT screen object.
I am guessing this could improve performance.
Currently, the screen lags really hard. So I might have to restart the whole project using C if I can't solve the performance issue.
Please help me out!

I suppose you could declare your screens outside kv and then add them as required in your screen manager,In your kv
MyScreenManager:
id: myscreenmanager
transition: FadeTransition()
In your Window class:
from kivy.uix.screenmanager import ScreenManager, Screen
...
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = self.ids.myscreenmanager
self.loading = Screen(name='SCRNLOADING')
self.idle = Screen(name='SCRN_IDLE')
self.calc = Screen(name='SCRN_CALCULATING')
self.sm.add_widget(self.loading)
self.set_idle()
def add_scrn(self):
self.sm.add_widget(self.idle)
self.sm.add_widget(self.calc)
self.sm.remove_widget(self.loading)
Im not really used to the Clock class but I'm sure you will need it here to load your screens correctly

Related

How can I use Kivy TextInput to assign attributes to an object?

I've coded off and on as a hobby since the pandemic, and feel like I've gotten the hang of OOP and have began working on a basketball simulator. I've created a simulator uses a Player and Team class to simulate full basketball games, and now I'm looking to create a GUI using Kivy. I've watched dozens of tutorials, but I can't find anything that makes sense for what I already understand about Python.
I'd like to have a screen where the user can set attributes 1-99 for each player's offense and defense attribute using Kivy TextInput's, and have those values be assigned to each player.offense, so that when I hit "run," it runs my actual game script.
This is probably a stupid question and I just need to keep digging until I figure it out, but if anyone else had a similar mental barrier when learning Kivy, I'd love to hear how you made it make sense. Thanks!
Here is a minimal example showing how to assign a value to an attribute from a text input:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.textinput import TextInput
kv = '''
BoxLayout:
text: your_textinput
orientation: 'vertical'
TextInput:
id: your_textinput
Button:
text: 'click'
on_press: app.clicked()
'''
class MyApp(App):
my_attribute = StringProperty()
def build(self):
return Builder.load_string(kv)
def clicked(self):
self.my_attribute = self.root.ids.your_textinput.text
print(self.my_attribute)
if __name__ == '__main__':
MyApp().run()
Probably self.root.ids.your_textinput.text is the most important part of it.
It goes as follow:
self is MyApp class
root is the main widget inside the app which is BoxLayout in this example
ids is a dictionary containing items that you assigned in your kv language code.
your_textinput: is the id we assigned to TextInput in kv language code
text is an attribute of the TextInput where the input is stored
Sometimes it gets trick to find which is root and which ids is under what object. You can use print with dir() and __class__ to detect it.
for example:
You can find if root has an ids attribute by using dir() on root:
print(dir(self.root))
You can also know what type of class is it by using:
print(self.root.__class__)
which give:
<class 'kivy.uix.boxlayout.BoxLayout'>
You can also use __doc__ if you added proper comments to your code.
print(self.__doc__)
Gives:
Main app class
You can read more about ids here:
https://kivy.org/doc/stable/api-kivy.uix.widget.html?#kivy.uix.widget.Widget.ids
Hope this is helpful and wish you enjoyable time using Kivy.

Equivalent to `wx.CallLater()` in Kivy/KivyMD or how do deal with `time.sleep()` freezing the GUI?

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)

Error while drawing kivy rectangle using canvas

I need some help with kivy,i am pretty new to kivy and I made a class to to draw a rectangle as a background.
I am pretty sure I did everything correctly but there is an error,so here is my code
The .py file
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
class Background(Widget):
pass
class MY_browser(App):
def build(self):
return Background
MY_browser().run()
The .kv file
Floatlayout:
Background:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
I have tried a lot of things but no difference so if anyone can help I would really appreciate it
A couple problems:
In your build() method, the return Background is returning a class, but the build() method is expected to return a Widget instance. Perhaps this should be return Background().
In your kv, the indentation is incorrect. All indentations should be a multiple of the same number of spaces (typically 4). The indentation of Background is too large.
Your kv does not provide a rule for Background, so when Background() is returned, it will simply be an empty Widget. If you want to return Background(), you should have a <Background>: rule in the kv.
If your kv file is named my_browser.kv, then you do not need a build() method for your App class at all.
If your kv file is not named as above, then your build method can be return Builder.load_file("kv file name"), where kv file name is replaced by the correct name of your kv file.
The Floatlayout in your kv is misspelled. It should be FloatLayout.

How To Replace Kivy Widgets On Callback?

I'm new to Python and Kivy, and I'm trying to create multipage display of letters of the braille alphabet, with the corresponding braille's letter picture present in every page. I really want to learn more about creating Kivy desktop apps. I really hope you can help me. What I'm trying to do is have a page look like this:
I know how images and buttons are placed and customized in terms of size and position in the KV file. However what I need to learn is how add_widget() and clear_widget() will factor in this. I have read the Kivy docs but they barely explain how I could achieve what I need. What I thought of doing is using the from kivy.uix.screenmanager import ScreenManager, Screen feature, and then just create 26 screens and route them via on_click in the kv file. But that's tedious and too manual. Here's my code so far:
class LetterAScreen(Screen):
pass
class LetterBScreen(Screen):
pass
class LetterCScreen(Screen):
pass
class LetterDScreen(Screen):
pass
class LetterEScreen(Screen):
pass
class LetterFScreen(Screen):
pass
class LetterGScreen(Screen):
pass
#.... so and so until Letter Z
sm = ScreenManager(transition=SwapTransition())
#LearnScreen - Alphabet
sm.add_widget(LetterAScreen(name='lettera'))
sm.add_widget(LetterBScreen(name='letterb'))
sm.add_widget(LetterCScreen(name='letterc'))
sm.add_widget(LetterDScreen(name='letterd'))
sm.add_widget(LetterEScreen(name='lettere'))
sm.add_widget(LetterFScreen(name='letterf'))
sm.add_widget(LetterGScreen(name='letterg'))
sm.add_widget(LetterHScreen(name='letterh'))
sm.add_widget(LetterIScreen(name='letteri'))
sm.add_widget(LetterJScreen(name='letterj'))
sm.add_widget(LetterKScreen(name='letterk'))
sm.add_widget(LetterLScreen(name='letterl'))
sm.add_widget(LetterMScreen(name='letterm'))
sm.add_widget(LetterNScreen(name='lettern'))
sm.add_widget(LetterOScreen(name='lettero'))
sm.add_widget(LetterPScreen(name='letterp'))
sm.add_widget(LetterQScreen(name='letterq'))
sm.add_widget(LetterRScreen(name='letterr'))
sm.add_widget(LetterSScreen(name='letters'))
sm.add_widget(LetterTScreen(name='lettert'))
sm.add_widget(LetterUScreen(name='letteru'))
sm.add_widget(LetterVScreen(name='letterv'))
sm.add_widget(LetterWScreen(name='letterw'))
sm.add_widget(LetterXScreen(name='letterx'))
sm.add_widget(LetterYScreen(name='lettery'))
sm.add_widget(LetterZScreen(name='letterz'))
I haven't gotten around the kv file because i'm clueless how this will pan out. What I need to do is create widgets or a function that will swap out the images of the current letter and display those of the next or previous ones when the next/button is clicked, without having to switch screens every single time. I'm really unfamiliar with how functions work in Kivy and Python. I hope you could help me. Thank you.
Here is a simple solution to your problem. I'll leave it to you to modify and make it look and work exactly how you want :)
Learning the kv language is INCREDIBLY helpful, easy, and it can be picked up quite quickly.
main.py
from kivy.app import App
class MainApp(App):
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def next_letter(self):
# Get a reference to the widget that shows the letters
# self.root refers to the root widget of the kv file -- in this case,
# the GridLayout
current_letter_widget = self.root.ids['the_letter_label']
# Get the letter currently shown
current_letter = current_letter_widget.text
# Find the next letter in the alphabet
next_letter_index = self.alphabet.find(current_letter) + 1
next_letter = self.alphabet[next_letter_index]
# Set the new letter in the widget that shows the letters
current_letter_widget.text = next_letter
MainApp().run()
main.kv
GridLayout: # This is the `root` widget of the main app class
cols: 1
Label:
text: "g"
id: the_letter_label # Setting an id for a widget lets you refer to it later
Button:
text: "Previous"
Button:
text: "Next"
on_release:
# the keyword `app` references the main app class, so we can call
# the `next_letter` function
app.next_letter()
I'm happy to address specific questions if you have them.

Kivy dynamic background loading not functioning

The goal is a screen which uses one of several images (randomly chosen upon each screen load) as a background.
The app contains the following:
class AnswerScreen(Screen):
bkgd = ""
def choose_bkgd(self):
self.bkgd = "{}.jpg".format(random.randint(0,8))
My kv file contains the following:
<AnswerScreen>
on_pre_enter: root.choose_bkgd()
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: root.bkgd
Unfortunately the background is always just a solid white.
I've added a print call to choose_bkgd(), and it always prints an acceptable file name, and I've also tried using on_enter: but there is no change. If I replace source: with a file name instead of root.bkgd the image displays correctly. This leads me to believe that the background is being generated before the function is being called to set the bkgd variable, but this confuses me as I thought the whole point of on_pre_enter was to execute code prior to the loading of the screen. The kivy docs haven't cleared this up for me. Any help is greatly appreciated.
Make bkgd a kivy property. This is essential to be able to bind to it and have things automatically update when it changes.
from kivy.properties import StringProperty
class AnswerScreen(Screen):
bkgd = StringProperty("")
...

Categories

Resources