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'
Related
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.
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.
I've made a rule in my kv for the Button class to have a specific background (provided in an image). When I created an ActionBar that contains ActionPrevious and ActionButton widgets, they both seemed to get the same background.
And I would understand that ActionButton instance got that background, since it inherits from Button and ActionItem classes, but why did the ActionPrevious get the same background? It inherits from BoxLayout and ActionItem, neither of which have anything to do with the Button class. What's the reason behind it?
Also, a side question
The ActionPrevious has a property with_previous which, when set to True, adds a clickable arrow. However, the title of the widget remains unclickable. But the docs say that this property would make the whole widget clickable. While it's not a big deal, I'd rather want the entire ActionPrevious widget background to change on press. Is it possible to achieve this?
So what I mean is that when you press the Back arrow, only the space around it and the app icon turns blue, but the text doesn't, as if it's part of a different widget.
Here is the code to visualize the question:
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.uix.actionbar import ActionBar
Builder.load_string('''
<Button>:
background_normal: 'some_file.png'
<MenuBar>:
ActionView:
ActionPrevious:
title: "Log out"
with_previous: True
ActionButton:
text: "Settings"
''')
class MenuBar(ActionBar):
pass
runTouchApp(MenuBar())
Okay, so the truth was in the style.kv file. Basically, ActionPrevious widget has the following structure:
<ActionPrevious>:
GridLayout:
ActionPreviousButton:
GridLayout:
ActionPreviousImage:
id: prev_icon_image # the "back" arrow
ActionPreviousImage:
id: app_icon_image # the app icon
Widget:
# perhaps something to split the title from the GridLayout
Label:
id: title # the title
And the ActionPreviousButton inherits from Button, so that's why the ActionPrevious arrow part was getting that background.
Here also lies the answer to the side question. Since the clickable part is the ActionPreviousButton, and the title is kept in a Label, that is not a child of it, the text is unclickable. So to fix that, one has to create a custom class and put the Label as a child of the ActionPreviousButton.
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("")
...
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.