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

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.

Related

Kivy button example closes immediately when testing

I'm trying to learn Kivy using their examples, however I'm having an issue. I'm using their button doc example:
from kivy.uix.button import Button
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
btn1 = Button(text='Hello world 1')
btn1.bind(on_press=callback)
btn2 = Button(text='Hello world 2')
btn2.bind(on_press=callback)
However, the program runs and immediately closes. I assumed maybe its tkinter, where the program runs on a constant loop and you need to add something at the end so it doesn't close, but I couldn't find anything on their docs about that.
To reiterate, I don't get any errors, the file just runs, I get a very brief pop up, and then it ends. I don't get an interface.
Firstly, kivy need to loop for control all own functions. So we need a App class and have to return our layouts directly or layouts under Screen Manager. In Kivy-Button documentation, Kivy shows you only related part. So there is a no any App class or loop for control.So program runs and closes immediately because app class doesn't loop window.
If you're beginner and trying to learn kivy from documentation, you need to figure how Kivy actually works and how documentation explain things. I'm sharing this code below for you, you need to understand add-remove widgets ,set layouts,... in kivy from documentations or search for full-code examples not part.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class TestLayout(BoxLayout):
def __init__(self, **kwargs):
super(TestLayout, self).__init__(**kwargs)
self.orientation = 'vertical'
but1 = Button(text='Button1')
self.add_widget(but1)
but2 = Button(text='Button2')
self.add_widget(but2)
class MyApp(App):
def build(self):
return TestLayout()
if __name__ == '__main__':
MyApp().run()
When you understand how it works, you should start to use Screen Manager for easily create pages, send-get values (and many things) for your applications.I hope these helps you at the beginning. Good luck.

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.

Dynamically deleting and loading Screen object in Kivy

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

Kivy class in .py and .kv interaction 2

Follow up from Kivy class in .py and .kv interaction , but more complex.
Here is the full code of what I'm writing:
The data/screens/learnkanji_want.kv has how I want the code to be, but I don't fully understand how the class KanjiOriginScreen() plays it's role in screen management.
data/screens/learnkanji.kv works how I want it, but for this to work I have to put keyb_height in class KanjiOriginScreen() (main.py). However I want that code to be in the class LayoutFunction() (learnkanji.py).
Question
How can I put keyb_height in the function LayoutFunction() and access this in the .kv file in <LayoutFunction>?
Could you also explain why KanjiOriginScreen: can be put in learnkanji.kv without < > and the program still recognizes it should use this?
If anything is unclear, please ask :)
Edit
I found out that I didn't import the learnkanji.py in the learnkanji.kv file and that caused that it couldn't find the class 'LayoutFunction'.
#:import learnkanji data.screens.learnkanji
To answer your questions:
The way you are doing it should work. You should be able to access object attributes from kv. If your attribute is going to change, however, and you want the UI to update when it does, you should use Kivy Properties. If it is constant, a normal attribute works fine.
From the Kivy Docs, <Widget>: is a class rule that will be applied to every instance of that class. Widget: will create an actual instance of that class (in this case it is your root widget).
As for ScreenManager and Screens, you can think of them this way. Each Screen is it's own individual UI (it's own root widget). The screen manager is a container that holds your Screen and can swap between different Screens. This lets you create separate UIs that you can toggle between. Each UI is a separate widget tree with a Screen at its root. The docs are actually pretty good at describing ScreenManager.
How can I put keyb_height in the function LayoutFunction() and access this in the .kv file in ?
You can't do this with a function. You need to make LayoutFunction into a class to do this. Like so:
main.py
class LayoutClass(BoxLayout): # I made it a boxlayout, you could make it anything you want
keyb_height = NumericProperty(260) # from kivy.properties import NumericProperty
kv file:
<LayoutClass>: # can only access it this way if it's a class in main.py
something: root.keyb_height
Could you also explain why KanjiOriginScreen: can be put in learnkanji.kv without < > and the program still recognizes it should use this?
It sounds like you're asking how you can achieve this.. but I can't think why?
Unless you want it managed by a ScreenManager perhaps? However, the only way you can have KanjiOriginScreen within the kv file without the <> is if it is inside another root widget. For instance, see Testy and ScreenTwo as they are in the kv file under <Manager> in my answer to your other question(here). They are without <> because they are class instances, WITHIN another class(the Manager class). Only root widgets have the <> around them in the kv file. If none of this makes sense to you, you need to do a tutorial on kivy.
Check out this tutorial I made a while back, it explains a little about root widgets in kv(at around 4.30).
Sorry I was not clear with my question, but with the help on IRC on #Kivy I ended up with the following:
learnkanji.py
class LayoutFunctioning(BoxLayout):
keyb_height = NumericProperty(260)
learnkanji.kv
KanjiOriginScreen:
name: 'LearnKanji'
fullscreen: True
LayoutFunction:
id: lfunc
#...code...
height: lfunc.keyb_height #Instead of root.keyb_height
Now I understand how to use the id, I can use lfunc to call my code in LayoutFunction() :)

Kivy Framework - Most pythonic solution with TextInput/Label Updating?

I've been working with Kivy and Python 3 and I've run across a problem. I have 2 widgets in a BoxLayout, one a TextInput widget and one a Label widget. When some text is entered into the TextInput widget and the enter key is pressed, I would like Label.text to update to reflect TextInput.text.
I've put together a solution that works. Here is the code (question after the break):
from kivy.app import App
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class TexttestApp(App):
def on_enter(self,textin):
self.lab.text = textin.text #is this incorrect?
def build(self):
window = BoxLayout()
self.lab = Label(text="Inital Label") #is this incorrect?
text = TextInput(multiline=False)
text.bind(on_text_validate=self.on_enter)
window.add_widget(text)
window.add_widget(self.lab)
return window
My questions are as follows:
Is assigning the Label widget to an instance variable a bad programming practice? From a software engineering point of view, is this bad/confusing? Or should I be assigning all of my widgets to the instance of the TexttestApp class? (i.e. self.text, self.window, etc). The code right now looks disorganized to me, but I can't figure out another way of solving the problem.
Thanks in advance. This is my first attempt at using bind() to attach a function to a keyboard event.
This all looks fine to me. I suppose in principle I could nitpick stuff, but there's really nothing very important in such a small code snippet, since you aren't doing anything really wrong. The stuff you comment is fine, in general terms, and there's no rule against storing stuff as attributes of your app although there may be better or more convenient alternatives (as below).
From a kivy point of view, the biggest thing is probably...use kv language! In this case, you could have a file texttest.kv with
BoxLayout:
TextInput:
multiline: False
on_text_validate: the_label.text = self.text
Label:
id: the_label
text: "Initial Label"
This would replace both methods of your app class. It's quite similar to your example in length, since it's very simple, but I'd say its already a little clearer - and kv rapidly becomes much clearer and less verbose as things become more complicated, since it takes care of a lot of bindings automatically.
This example happens to also avoid binding to your own function to change the label text, since it can all be done in a line of kv, but your way isn't wrong and it might still be appropriate to call a method or function in the python file if the task is more complex.

Categories

Resources