add_widget to a widget KivyMD? - python

I am trying to use TwoLineIconListItem with add_widget in a for loop but I can't figure out how to add the icon. It does not take an Icon parameter because per the docs IconLeftWidget is nested like so:
TwoLineIconListItem:
text: "Two-line item with avatar"
secondary_text: "Secondary text here"
IconLeftWidget:
icon: "language-python"
I know how to do this in the KV language but how do you do it in a python for loop that populates a list. This is the closest I got but it puts the icon above the text
for i, z, n in zip(x[1::2], x[0::2], range(1,10)):
self.root.ids.todays_workout.add_widget(
IconLeftWidget(icon=f"numeric-{n}-box-multiple-outline"))
self.root.ids.todays_workout.add_widget(
TwoLineIconListItem(text=f"{i}", secondary_text=f"{z}"))
Perhaps a nested add_widget()? I'm not sure how to accomplish this

I think your nested add_widget() is along the right track, but you can't actually do that because add_widget() returns None. So you can do it something like this:
for i, z, n in zip(x[1::2], x[0::2], range(1,10)):
icon = IconLeftWidget(icon=f"numeric-{n}-box-multiple-outline"))
listItem = TwoLineIconListItem(text=f"{i}", secondary_text=f"{z}"))
listItem.add_widget(icon)
self.root.ids.todays_workout.add_widget(listItem)

With a second look at the docs and some help from the KivyMD support Discord Channel, I have found the solution.
You need to create a new class that inherits from the TwoLineIconListItem as such:
class ListWithIcon(TwoLineIconListItem):
icon = StringProperty("string")
Then I created a new .kv file called listwithicon.kv with the following (notice the class names match)
<ListWithIcon>:
IconLeftWidget:
icon: root.icon
Finally in my main KV string(or file) I added #: include listwithicon.kv
These steps will allow you to add an icon parameter to your function. Just be sure to pass ListWithIcon (your new class) instead of the KivyMD class TwoLineIconListItem
for i, z, n in zip(x[1::2], x[0::2], range(1,number_workouts)):
self.root.ids.todays_workout.add_widget(
ListWithIcon(text=f"{i}", secondary_text=f"{z}", icon=f"numeric-{n}-box-multiple-outline"))

Related

Python Kivy library adding widgets

For example we created with kivy language simple BoxLayout1 that contains button1 and another BoxLayout2. When you click button1 it adds a new button in BoxLayout2 but it's written in python.
Is it possible to acces existing layout in kv language, in python code? Or only solution is writting whole window in python?
I couldn't find any info in kivy docs, maybe I've just missed something.
EDIT:
I've something like this
Kv:
<CreateWindow>:
BoxLayout:
Button:
text: "test"
on_release: root.press()
BoxLayout:
Label:
text: "Label"
Python:
class CreateWindow(Screen):
def press(self):
I want to add a new button near Label by activating press function
in python will be like this
class CreateWindow(Screen):
def press(self):
# the first box is the first child in the children list of the CreateWindow widget so
box1=self.children[0]
box2=self.children[1]
# and now you can decide which box you want to use add_widget method with
# in your case it should be
box2.add_widget(Button())

How to add a textinput value to a value in a dictionary in kivy python?

I'm building a simple gui it's a "bank account manager" and I need to add the value that the user inputs in a textinput in a deposit screen then add it to a balance which the user inputs in the sign up screen .... The sign up screen's info is saved into a dictionary.. each key has a value of a list and that list has 9 values ...the name, the last name the balance etc...but the balance is a string and I can't even get to it to run any operations on it.
I would recommend showing code in an attempt at solving the issue. However to answer your question with the information provided, I would recommend using a button widget and in it's properties in a .kv file use. on_press:/on_release: root.myfunction()
then in that screen/application class below use something like this:
.kv/Builder_loadstring:
TextInput:
id: mywidget
text: 'mywidget'
Button:
text: 'Run a Function'
on_release: root.myfunction()
python:
class MyApp(App):
def myfunction(self):
*whatever you want it to do*
value = self.ids.mywidget.text
*value = self.ids.mywidget.data* #For when you are using lists for recycleview/spinners#
*value = self.ids.mywidget.some_type_of_widget_property*
*whatever you want it to do*

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.

Why does this not load the ids of the contents in the BoxLayout? (Kivy)

Why does this code get a KeyError, from line #21?
I've tried different versions of similar code, but this is the only file that gets the KeyError.
Gist: https://gist.github.com/Crowbrammer/464ae3ae3ddd7d33a9eb64d856acacd0
Why is it missing the id's of each element in the Kivy file?
How come the function beneath the init() function works, with that exact same line of code--but the init() function doesn't?
I don't think record_new_model gets called.
Your constructor fails so the rest doesn't matter.
You aren't setting the ids properly.
You need to do something like this
<ModelAddLayout>:
model_add_name: model_add_name
orientation: 'vertical'
padding: 20, 20
Label:
id: title_label
text: 'Model Add Screen'
font_size: '30dp'
# text_size: '15dp'
TextInput:
id: model_add_name
text: 'Add your model name here'
multiline: False
When you are adding an id to a child it doesn't get added to the parent. You also need to add the id to the parent: model_add_name: model_add_name.
The root determines what elements get loaded into the code first.
For this code is ScreenManagement. The root for others is ModelAddLayout.
So, for the code I linked, it loads the elements of the kv file later than I expect, so there are no keys in the ids attribute to call.
What I did get to work, then, was to put everything except super() into a new function, late_init(self, keys, **largs).
After that, I put Clock.schedule_once(self.late_init, 0) after init()'s super.
This gave the app time to populate the ids, enabling my dropdown list to become a reality.
(From the comment to Radu Dita's answer.)

Kivy text markup printing its own syntax

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'

Categories

Resources