How to get, use and change values from a .kv file - python

So, I've been working on a very simple kivy app and am having trouble with the backend. How would you go about getting the values from text_input and changing label values? I've watched a few tutorials but their methods vary and I get error messages.
Here is my python code:
import kivy
from kivy.app import App
kivy.require('1.9.1')
class MyWindowApp(App):
pass
window = MyWindowApp()
window.run()
And here is the .kv file:
Screen:
side: side
FloatLayout:
Label:
text: "Side:"
pos_hint: {"x": 0.1, "y": 0.7}
text_size: self.size
Label:
text: "Volume:"
pos_hint: {"x": 0.1, "y": 0.65}
text_size: self.size
Label:
text: "Surface Area:"
pos_hint: {"x": 0.1, "y": 0.6}
text_size: self.size
TextInput:
size_hint: (.4, None)
height: 26
multiline: False
pos_hint: {"x": 0.24, "y": 0.7}
id: side
Label:
text: "0cm"
id: volume
pos_hint: {"x": 0.27, "y": 0.65}
text_size: self.size
Label:
text: "0cm"
id: surface_area
pos_hint: {"x": 0.355, "y": 0.6}
text_size: self.size

As mentioned in the other answer, place Builder.load_file or Builder.load_string in your build method.
To handle changes e.g. if the user hits enter in the side input use on_text_validate: app.on_side_change(self) or place a button to trigger the calculation and use on_press method.
The on_side_change method in your class MyApp will handle the change. It will be called once the user hits enter. See the example code below, for a basic calculation.
For getting/setting values from the labels/inputs you can use ObjectProperty or StringProperty from kivy.properties. In the below code I'm using StringProperty.
Important you need the StringProperty in your app class and use it in the kv file.
For the mode of your calculation I've added a property, so you can use it in your calculation method.
The mode is also used in the kv file so it's displaying the current mode.
The switcher dictionary is the Python way to do a switch/case statement.
Example code
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = """
Screen:
side: side
GridLayout:
rows: 1
cols:2
spacing:0
GridLayout:
rows: 5
cols:1
Button:
text: "Cube"
# on_press: app.mode = self.text
on_press: app.setMode(self)
Button:
text: "Cuboid"
on_press: app.setMode(self)
Button:
text: "Cylinder"
on_press: app.setMode(self)
Button:
text: "Cone"
on_press: app.setMode(self)
Button:
text: "Sphere"
on_press: app.setMode(self)
FloatLayout:
Label:
text: "The Volume and surface area of a {}:".format(app.mode)
pos_hint: {"x":0.1, "y":0.8}
text_size: self.size
Label:
text:"Side:"
pos_hint: {"x":0.1, "y":0.7}
text_size: self.size
Label:
text:"Volume:"
pos_hint: {"x":0.1, "y":0.65}
text_size: self.size
Label:
text:"Surface Area:"
pos_hint: {"x":0.1, "y":0.6}
text_size: self.size
TextInput:
size_hint: (.4, None)
height: 26
multiline: False
pos_hint: {"x":0.24, "y":0.7}
id: side
text: app.sideText
on_text_validate: app.on_side_change(self)
Label:
text: app.volume
pos_hint: {"x":0.27, "y":0.65}
text_size: self.size
Label:
text: app.area
pos_hint: {"x":0.355, "y":0.6}
text_size: self.size
"""
class MyApp(App):
sideText = StringProperty("")
area = StringProperty("0 cm²")
volume = StringProperty("0 cm³")
mode = StringProperty("Cube")
def build(self):
return Builder.load_string(kv)
def setMode(self, btn):
self.mode = btn.text
def on_side_change(self, instance):
print(instance.text)
result = 0
try:
value = float(instance.text)
except:
# failed to convert
return
def cubeCalc(val):
return {
"volume": val * val * val,
"area": val * val
}
switcher = {
"Cube": cubeCalc
}
method = switcher.get(self.mode, "Unknown mode")
if method is not "Unknown mode":
result = method(value) # result is a dictionary with volume & area
#print(result)
print(self.volume)
self.volume = "{:.2f} cm³".format(result["volume"])
self.area = "{:.2f} cm²".format(result["area"])
if __name__ == "__main__":
MyApp().run()

why you don't have a build method in your App class?
try this:
import kivy
from kivy.app import App
from kivy.lang.builder import Builder
kivy.require('1.9.1')
class MyWindowApp(App):
def build(self):
self.root = Builder.load_file("YOUR_KV_FILE.kv")
return self.root
window = MyWindowApp()
window.run()
Don't forget to change YOUR_KV_FILE.kv to your own kv filename
since you are not using Builder.load_file method to load your kv file you your kv file name must be something like mywindow.kv but im recommended to use Builder.load_file because you can save the whole window in a variable and access it later easy and it also make you free to change your kv design filename

Related

Python Kivy Dyanmic Class not defined

I keep getting a Name Error reporting "name TripButton is not defined" although I have a button called TripButton defined in the same .kv file.
#:kivy 1.11.1
<TripButton#Button>:
size_hint: (0.15,0.15)
pos_hint: {'y':0.84}
text:"Test Text"
<MyPopup>:
size_hint: 0.5, 0.5
auto_dismiss: False
title: 'New Trip'
BoxLayout:
orientation: 'vertical'
TextInput:
id: trip_name
multiline: False
BoxLayout:
size_hint_y: None
height: 30
Button:
text: 'Submit'
on_release:
root.parent.parent.add_widget(TripButton(text=trip_name.text, size_hint=(1,0.1)))
root.dismiss()
Button:
text: 'Cancel'
on_release: root.dismiss()
<FirstScreen>:
name: 'first'
FloatLayout:
BoxLayout:
size_hint: (0.95, 0.95)
pos_hint: {'center_x':0.5, 'center_y':0.5}
orientation: "vertical"
canvas.before:
Color:
rgba: (1, 0, 0, 1) # Red color
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
TripButton:
Button:
text: 'Add Trip'
font_size: 12
size_hint: (0.1, 0.1)
pos_hint: {'right':1, 'bottom':1}
on_release: app.open_popup()
pos_hint: {'right':1, 'bottom':1}
on_release: app.open_popup()
I tried declaring the TripButton class above and below the MyPopup class and that didn't work. I compared my code with the kivy documentation for dynamic classes and the from what I can tell I'm matching the syntax. I tried importing the Button class in both the python file and the .kv file and that hasn't worked.
To clarify all of this is in the same .kv file in the same directory as the python file. the app runs until I click the submit button in MyPopup then the program crashes and I get the NameError. This is what I have in my python file.
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
class FirstScreen(Screen):
pass
class MyPopup(Popup):
pass
class MyApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(FirstScreen(name='first'))
sm.current = 'first'
return sm
def open_popup(self):
p = MyPopup()
p.open()
def add_button(self):
pass
if __name__ == '__main__':
MyApp().run()
What am I doing wrong? What else I can try? Thanks for your time.
To access classes defined within kv file use kivy.factory.Factory
Add id attribute to your button's BoxLayout, so replace following part of kv file:
BoxLayout:
TripButton:
with
BoxLayout:
id: trip_buttons
TripButton:
then replace:
root.parent.parent.add_widget(TripButton(text=trip_name.text, size_hint=(1,0.1)))
with:
app.root.current_screen.ids.trip_buttons.add_widget(kivy.factory.Factory.TripButton(text=trip_name.text, size_hint=(1,0.1)))
You also duplicated following lines within last button definition on first screen. Replace:
pos_hint: {'right':1, 'bottom':1}
on_release: app.open_popup()
pos_hint: {'right':1, 'bottom':1}
on_release: app.open_popup()
with
pos_hint: {'right':1, 'bottom':1}
on_release: app.open_popup()

How to get the selected value from my kivy dropdown menu

I have a dropdown menu in which I would like to know which value the user has selected so I can use it later in my program. This is my .kv code:
BoxLayout:
orientation: 'horizontal'
size_hint_x: 1
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: units_num_btn
text: '0'
size_hint_y: None
height: 44
on_parent: drop_player_numbers_units.dismiss()
on_release: drop_player_numbers_units.open(self)
DropDown:
id: drop_player_numbers_units
on_select: units_num_btn.text = '{}'.format(args[1])
on_select: app.return_player_numbers()
Button:
id: units_num_btn_1
text: '1'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('1')
Button:
id: units_num_btn_2
text: '2'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('2')
and so on.
My .py code is here:
class drop_content(DropDown):
pass
class PlayerScreen(Screen):
pass
class TheApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(PlayerScreen(name='player_setup'))
sm.current = 'player_setup'
return sm
def main():
Builder.load_file('menu.kv')
app = TheApp()
app.run()
if __name__ == '__main__':
main()
I have previously used a function such as this:
# .py example
def return_text(self):
text = self.root.get_screen('screen').ids.specific_id.text
print(text)
# .kv example
TextInput:
id: input
text: "2"
on_text: app.return_text()
which did return text using a Textinput type in my .kv file. I know it doesn't work for the dropdown menu since the text is not inputted in the same way. Do you know how I would do this?
Thanks in advance
from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.lang.builder import Builder
class TestScreen(Screen):
def return_player_numbers(self,play_number):
print('Test : ',play_number)
kv = '''
ScreenManager:
TestScreen:
<TestScreen>:
BoxLayout:
orientation: 'horizontal'
size_hint_x: 1
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: units_num_btn
text: '0'
size_hint_y: None
height: 44
on_parent: drop_player_numbers_units.dismiss()
on_release: drop_player_numbers_units.open(self)
DropDown:
id: drop_player_numbers_units
on_select:
units_num_btn.text = '{}'.format(args[1])
app.root.current_screen.return_player_numbers(args[1])
app.return_player_numbers(args[1])
Button:
id: units_num_btn_1
text: '1'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('1')
Button:
id: units_num_btn_2
text: '2'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('2')
'''
class TheApp(App):
def return_player_numbers(self,play_number):
print('App : ',play_number)
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
TheApp().run()
I have found a way that I think is simpler than the current answer provided. This method uses that fact that the text on the button used to initiate the drop down changes as another button is selected. This is to show the user what they have selected. Since I want to know the text the user has selected from the drop down menu, and this is in fact the same thing that is updated on the initial button, I can just read the text from the button to obtatin my result like so:
(in .kv file)
<player_setup>
BoxLayout:
orientation: 'horizontal'
size_hint_x: 0.2
pos_hint: {'x':.4, 'y':0}
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: tens_num_btn
text: '0'
size_hint_y: None
# size_hint_x: 0.1
height: 44
on_parent: drop_player_numbers_tens.dismiss()
on_release: drop_player_numbers_tens.open(self)
DropDown:
id: drop_player_numbers_tens
on_select:
#######
tens_num_btn.text = '{}'.format(args[1])
# this line here ^ is the one that re-formats the text of the button above to
# the selected text
app.return_player_numbers()
max_height: 120
(.py file)
def return_player_numbers(self):
player_number = self.root.get_screen('player_setup').ids.tens_num_btn.text
return player_number
This also allows me to concatenate multiple dropdown results using a single function, however it is menu specific. In my case, this works better for me

Kivy widget generation very slow

I'm making a kivy app to find the rhyming words for a word entered by the user. It displays all the rhyming words as OneLineListItems in an MDList which is inside a kivy RecycleView. On clicking on one of these OneLineListItems it displays the definition of the word on the right-hand side of the screen. However, when I click on a OneLineListItem its definition takes very long to appear and sometimes it lags so badly that the app closes. Am I doing something wrong or is it just my computer? Code below:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.label import MDLabel
from kivymd.uix.list import OneLineListItem
import pronouncing
import enchant
from PyDictionary import PyDictionary
dictionary = PyDictionary()
d = enchant.Dict("en_US")
kv = """
Screen:
input:input
scroll:scroll
word:word
defs:defs
MDGridLayout:
rows: 1
cols: 2
MDGridLayout:
rows: 2
cols: 1
MDFloatLayout:
MDTextField:
id:input
size_hint: (.4, None)
height: 26
multiline: False
on_text_validate: app.rhyme()
hint_text: "Search"
mode: "rectangle"
pos_hint: {"center_x": .25, "center_y": .85}
font_name: "DejaVuSans.ttf"
text_size: self.size
MDFloatLayout:
RecycleView:
size_hint: 0.85,1.5
bar_width: dp(15)
bar_height: dp(40)
scroll_type: ["content"]
pos_hint: {"center_x": 0.45, "center_y": 0.93}
MDList:
id: scroll
MDBoxLayout:
id:defs
orientation: "vertical"
md_bg_color: 0, 1, 0.2, 1
MDLabel:
id: word
text: ""
text_size: self.size
"""
class RhymeApp(MDApp):
played = []
x_turn = True
def build(self):
self.screen = Builder.load_string(kv)
return self.screen
def rhyme(self):
raw_rhymes = pronouncing.rhymes(self.screen.input.text)
rhymes = []
[rhymes.append(x) for x in raw_rhymes if x not in rhymes and x[-1] != "." and d.check(x)]
self.screen.scroll.clear_widgets()
for i in rhymes:
self.screen.scroll.add_widget(
OneLineListItem(text=f"{i}".capitalize(), on_release=self.dictionary)
)
def dictionary(self, btn):
nl = '\n'
self.screen.defs.clear_widgets()
self.screen.word.text = btn.text.capitalize()
meaning = dictionary.meaning(btn.text, disable_errors=True)
if meaning is None:
self.screen.defs.add_widget(
MDLabel(text=f"Sorry, no meaning for that word.",
text_size="self.size")
)
else:
for key in meaning:
self.screen.defs.add_widget(
MDLabel(text=f"Part of speech: {key} {nl}Meaning: {nl}{nl}{meaning[key][0].capitalize()}.",
text_size="self.size")
)
if __name__ == "__main__":
RhymeApp().run()
Can someone please help?
First create a custom class for the data-class like following:
class ListItem(OneLineListItem):
# Here define all the necessary attrs., methods apart from the defaults (if you need any).
Now in your .kv initialize RecycleView as,
RecycleView:
id: scroll
#size_hint: 0.85,1.5
bar_width: dp(15)
bar_height: dp(40)
scroll_type: ["content"]
#pos_hint: {"center_x": 0.45, "center_y": 0.93}
viewclass: "ListItem"
RecycleBoxLayout:
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Now you are ready to feed RecycleView with you data as,
def rhyme(self):
...
self.screen.ids.scroll.data = [
{"text" : f"{i}".capitalize()}
for i in rhymes]

Kivy - Get InputText From Other Screen

I am trying to type in text from one screen. Press a button and move to another screen and have that text be shown in a label. I've seen a few questions that are similar to mine, but have not been able to figure out how to use the posted solutions and have been stuck for hours (Link One, Link Two, Link Three). I believe that I need to use the __init__ method somewhere because this is an instance? I tried using the first link, but the label ends up blank (the code does run). Any Advice?
main.py
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.lang.builder import Builder
class SecondWindow(Screen):
def get_unique_text(self):
x = self.manager.get_screen("first")
y = x.ids.unique.text
return str(y)
class FirstWindow(Screen):
pass
class MainWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv_main = Builder.load_file('main.kv')
class MyApp(App):
def build(self):
return kv_main
if __name__ == '__main__':
MyApp().run()
main.kv
#:include First.kv
#:include Second.kv
WindowManager:
MainWindow:
FirstWindow:
SecondWindow:
<MainWindow>
name: "main"
BoxLayout:
Button:
text: "Press"
on_release:
app.root.current = "first"
First.kv
<FirstWindow#Screen>:
name: "first"
BoxLayout:
orientation: "vertical"
Label:
text: "Enter Unique Text for Saving"
font_size: 20
text_size: self.width, None
halign: 'center'
TextInput:
id: unique
hint_text: 'example: Stand25'
Button:
text: "Press"
on_release:
app.root.current = "second"
Second.kv
<SecondWindow#Screen>:
name: "second"
BoxLayout:
orientation: "vertical"
Label:
text: "Unique Text"
font_size: 20
text_size: self.width, None
halign: 'center'
Label:
text: root.get_unique_text()
font_size: 16
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
color: 0,0,0,1
Button:
text: "Go Back"
on_release:
app.root.current = "first"
Another approach is to use the on_enter() method of a Screen in order to fetch the text. This also requires an id for the unique Label:
<SecondWindow#Screen>:
name: "second"
BoxLayout:
orientation: "vertical"
Label:
text: "Unique Text"
font_size: 20
text_size: self.width, None
halign: 'center'
Label:
id: unique # added id
# text: root.get_unique_text()
font_size: 16
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
color: 0,0,0,1
Button:
text: "Go Back"
on_release:
app.root.current = "first"
Just add an on_enter() method to the SecondWindow class:
class SecondWindow(Screen):
def on_enter(self, *args):
self.ids.unique.text = self.get_unique_text()
def get_unique_text(self):
x = self.manager.get_screen("first")
y = x.ids.unique.text
return str(y)
In your Second.kv you can reference the text of the TextInput in the First.kv by making a couple changes to the kv files. First, in the main.kv, add an id for the FirstWindow (and eliminate the SecondWindow for now):
WindowManager:
MainWindow:
FirstWindow:
id: first # added id
# SecondWindow: # this gets added later
<MainWindow>
name: "main"
BoxLayout:
Button:
text: "Press"
on_release:
app.root.current = "first"
Then, in the Second.kv, set up the reference to the text of the TextInput:
<SecondWindow#Screen>:
name: "second"
BoxLayout:
orientation: "vertical"
Label:
text: "Unique Text"
font_size: 20
text_size: self.width, None
halign: 'center'
Label:
text: app.root.ids.first.ids.unique.text # reference to unique text
font_size: 16
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
color: 0,0,0,1
Button:
text: "Go Back"
on_release:
app.root.current = "first"
Since the kv for SecondWindow uses app.root, it will cause an error if SecondWindow is instantiated before the root widget of the App is assigned. To avoid that, add the SecondWindow after a slight delay:
class MyApp(App):
def build(self):
Clock.schedule_once(self.add_second_screen)
return kv_main
def add_second_screen(self, dt):
self.root.add_widget(SecondWindow())

kivy Error when trying to add a button on gridlayot in a different class

My app wants to make a list from the strings typed in the TextInput field and show them in the gridlayout in the middle of the window after pressing the button "Buscar". I'm sharing variables and functions between classes but when i try to add a new button with the TextInput.text inside the gridlayout shows the error message:
"AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'add_widget'"
Thank you
The interface looks like this
This is my .py file
from kivy.app import App
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, NumericProperty, StringProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
class Lista(ScrollView):
lista_repuestos = ObjectProperty()
class CustomWidget(FloatLayout):
campo_de_busqueda_text_input = ObjectProperty()
repuesto = StringProperty('')
def submit_repuesto(self):
self.repuesto = self.campo_de_busqueda_text_input.text
Lista.lista_repuestos.add_widget(Button(text=self.repuesto))
class CustomWidgetApp(App):
def build(self):
return CustomWidget()
if __name__ == "__main__":
CustomWidgetApp().run()
this is my .kv file
CustomWidget:
<CustomWidget>:
campo_de_busqueda_text_input: campodebusqueda
TextInput:
id: campodebusqueda
size_hint: .7, .1
pos_hint: {"x": .15, "y": .85}
Button:
on_release: root.submit_repuesto()
size_hint: .1, .1
pos_hint: {"x": .85, "y": .85}
text: "Buscar"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .15}
text: "Descripción"
text_size: self.size
halign: "left"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .10}
text: "Referencia"
text_size: self.size
halign: "left"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .05}
text: "Cantidad"
text_size: self.size
halign: "left"
<Lista>:
lista_repuestos: listarepuestos
GridLayout:
id: listarepuestos
size_hint: .7, .6
pos_hint: {"x": .15, "y": .25}
cols: 1
row_default_height: 50
row_force_default: True
padding: 5
height: self.minimum_height
size_hint_y: None
Your line in the CustomWidget:
Lista.lista_repuestos.add_widget(Button(text=self.repuesto))
should reference an instance of Lista, not the class itself.

Categories