I'm back with my bartender!
So now everything works, I can control the pumps, and I can update everything as I want. Now I want to add a popup or a progress bar after clicking which drink to get.
My problem is that the popup window doesn't appear until it shortly shows up after the function 'pour_drink' has finished. As I understand it, it has to do with not giving the popup enough time to show up before doing the rest of the tasks. I have tried the time.sleep() but that pauses everything, and I've tried looking for other solutions, but to no avail.
Now the code is not a nice code, I have used the evil global variables, but it works, so I'm happy with that and you don't need to give feedback on the general structure of the code (though it is welcome if you want to look through all the poorly written code). Also, ignore all the comments.. they are of no help and I will make better ones before finishing the project.
The function pour_drink is in the class DrinkMenu, and I have written the function popup_func for the popup window. So if anyone has a nice (or ugly) solution I am all ears.. this project is taking time away from school work and I am dumb enough to let it.
EDIT: Clarification: I want to add a popup window (or a progress bar), preferably with a cancel button to cancel the drink being poured.
The function that handles the pumps for the pouring of the drink is the 'pour_drink' in the class drinkMenu. So what I want is for the popup window to show up when the pour function is accessed and then disappear when it is done.
And if possible it would be nice with a cancel button that makes the program jump out from the function pour_drink, but this is not really necessary if it is too difficult to implement.
So far I have tried playing around with multiprocessing but I couldn't make it work. The popup_func in the drinkMenu class is from my attempt at splitting the two functions pour_drink and popup_func into two processes. But the problem persisted and the popup window only shows up briefly after the pour_drink function is finished.
Thanks for the help!
.py file:
#Necessary files: bartender.py, bartenderkv.kv pump_config.py, drinks_doc.py
from kivy.core.window import Window
#Uncomment to go directly to fullscreen
Window.fullscreen = 'auto'
#Everything needed for kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.clock import mainthread
from functools import partial
from kivy.uix.popup import Popup
from kivy.uix.label import Label
#Import drink list
from drinks_doc import drink_list, drink_options
from pump_config import config
pins_ingredients = {}
#import gpio control library
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
import time
#Define the different screens
class MainMenu(Screen):
def use_last(self,*args,**kwargs):
global pins_ingredients
#Write a function that use the last used settings,
#i.e that the pumps have corresponding ingredients.
try:
from pump_config import config
for pmp in config:
pins_ingredients[config[str(pmp)]['value']] = [config[str(pmp)]['pin'],config[str(pmp)]['flowrate']]
self.load_available_drinks(pins_ingredients.keys())
self.parent.current = "DrinkMenu"
except:
bttn = Button(text = "Close", font_size=24, size_hint_x = 0.5, size_hint_y = 0.5)
self.popup = Popup(title='No Previous Settings Found!', title_size = '32sp',
content=bttn,
auto_dismiss=False)
bttn.bind(on_release = self.close_n_open)
self.popup.open()
def close_n_open(self,*args,**kwargs):
self.popup.dismiss()
def load_available_drinks(self, ingredients_list):
global drinks
drinks = []
for dri in drink_list:
dr = list(dri["ingredients"].keys())
result = all(elem in ingredients_list for elem in dr)
if result:
drinks.append(dri["name"])
class DrinkMenu(Screen):
#mainthread
def on_enter(self):
self.buttons = []
self.ids.drinks.clear_widgets()
for btn in range(len(drinks)):
self.buttons.append(Button(text=str(drinks[btn])))
self.buttons[btn].bind(on_press = self.pour_drink)
self.ids.drinks.add_widget(self.buttons[btn])
def popup_func(self, onoff):
if onoff == "ON":
self.popup = Popup(title='No Previous Settings Found!', title_size = '32sp',
auto_dismiss=False)
self.popup.open()
elif onoff == "OFF":
self.popup.dismiss()
for k in range(10):
pass
def pour_drink(self, button):
self.popup_func("ON")
#The pins_ingredient dictionary, each value has a list, first number is pin on raspberry pi, the second is the flowrate in ml/min =>
#temp_ingredients = drink_list[button.text]
for num in range(len(drink_list)):
if drink_list[num]['name'] == str(button.text):
temp_ingredients = drink_list[num]['ingredients']
for ing in temp_ingredients:
flow_rate = pins_ingredients[ing][1]
amount = temp_ingredients[ing]
temp_ingredients[ing] = amount/(flow_rate/60)
continue
#The pins_ingredients has ingredients as keys, list as value
#with first pin on RPi, second flowrate of that pump
#The temp_ingredients has ingredients as keys, and a list as
#value, first: how long the pump needs to be on for,
#second is primed for "ON"
ings = len(list(temp_ingredients.keys()))
#print(temp_ingredients)
start = time.time()
state = True
for ing in temp_ingredients:
GPIO.output(pins_ingredients[str(ing)][0],GPIO.HIGH)
deleted = []
while state:
for ing in temp_ingredients:
if ing not in deleted:
if time.time()-start >= temp_ingredients[ing]:
#print(temp_ingredients[ing])
GPIO.output(pins_ingredients[str(ing)][0],GPIO.LOW)
ings -= 1
deleted.append(ing)
if ings == 0:
state = False
self.popup_func("OFF")
class TopUpMenu(Screen):
global pins_ingredients
#mainthread
def on_enter(self):
global pins_ingredients
#print(pins_ingredients)
listed = list(pins_ingredients.keys())
self.buttons = []
self.ids.topupdrinks.clear_widgets()
counter = 0
for btn in range(len(listed)):
if str(listed[btn]) != "None":
self.buttons.append(Button(text=str(listed[btn])))
self.buttons[counter].bind(on_press = partial(self.top_up, str(listed[btn]), "ON"))
self.buttons[counter].bind(on_release = partial(self.top_up,str(listed[btn]),"OFF"))
self.ids.topupdrinks.add_widget(self.buttons[counter])
counter += 1
def top_up(self, *args, **kwargs):
global pins_ingredients
#If state is "ON" set pump pin high
#If state is "OFF" set pump pin low
ingredient = str(args[0])
state = str(args[1])
if state == "ON":
#Set the pin high to turn on corresponding pump
#print(str(ingredient)+ " ON")
GPIO.output(pins_ingredients[ingredient][0],GPIO.HIGH)
if state == "OFF":
#Set the pin low to turn off corresponding pump
#print(str(ingredient)+" OFF")
GPIO.output(pins_ingredients[ingredient][0],GPIO.LOW)
class LoadNewIngredients(Screen):
global drinks
global ingredients
global pins_ingredients
#I want to use the ingredients list in the kivy file
temp_list = [None]*len(list(config.keys()))
def anydup(self, thelist):
seen = set()
for x in thelist:
if x != None:
if x in seen: return True
seen.add(x)
return False
def get_ingredients(self,*args,**kwargs):
global ingredients
ingredients = []
for drink in range(len(drink_list)):
ings = list(drink_list[drink]['ingredients'].keys())
for ing in range(len(ings)):
elem = ings[ing]
if elem not in ingredients:
ingredients.append(elem)
return ingredients
def spinner_clicked(self, ident, value):
if ident == 1:
self.temp_list[0] = str(value)
if ident == 2:
self.temp_list[1] = str(value)
if ident == 3:
self.temp_list[2] = str(value)
if ident == 4:
self.temp_list[3] = str(value)
if ident == 5:
self.temp_list[4] = str(value)
if ident == 6:
self.temp_list[5] = str(value)
if ident == 7:
self.temp_list[6] = str(value)
if ident == 8:
self.temp_list[7] = str(value)
if ident == 9:
self.temp_list[8] = str(value)
def continued(self,*args,**kwargs):
global pins_ingredients
if self.temp_list[0] != "Pump_1":
config["pump_1"]["value"] = self.temp_list[0]
else:
config["pump_1"]["value"] = None
if self.temp_list[1] != "Pump_2":
config["pump_2"]["value"] = self.temp_list[1]
else:
config["pump_2"]["value"] = None
if self.temp_list[2] != "Pump_3":
config["pump_3"]["value"] = self.temp_list[2]
else:
config["pump_3"]["value"] = None
if self.temp_list[3] != "Pump_4":
config["pump_4"]["value"] = self.temp_list[3]
else:
config["pump_4"]["value"] = None
if self.temp_list[4] != "Pump_5":
config["pump_5"]["value"] = self.temp_list[4]
else:
config["pump_5"]["value"] = None
if self.temp_list[5] != "Pump_6":
config["pump_6"]["value"] = self.temp_list[5]
else:
config["pump_6"]["value"] = None
if self.temp_list[6] != "Pump_7":
config["pump_7"]["value"] = self.temp_list[6]
else:
config["pump_7"]["value"] = None
if self.temp_list[7] != "Pump_8":
config["pump_8"]["value"] = self.temp_list[7]
else:
config["pump_8"]["value"] = None
if self.temp_list[8] != "Pump_8":
config["pump_9"]["value"] = self.temp_list[8]
else:
config["pump_9"]["value"] = None
if not self.anydup(self.temp_list):
#print(self.temp_list)
pins_ingredients = {}
filehandler = open('pump_config.py', 'wt')
filehandler.write("config = " + str(config))
filehandler.close()
for pmp in config:
if config[str(pmp)]['value'] != None:
pins_ingredients[config[str(pmp)]['value']] = [config[str(pmp)]['pin'],config[str(pmp)]['flowrate']]
#print(pins_ingredients)
self.load_available_drinks(pins_ingredients.keys())
self.ids.spinner_id_1.text = "Pump_1"
self.ids.spinner_id_2.text = "Pump_2"
self.ids.spinner_id_3.text = "Pump_3"
self.ids.spinner_id_4.text = "Pump_4"
self.ids.spinner_id_5.text = "Pump_5"
self.ids.spinner_id_6.text = "Pump_6"
self.ids.spinner_id_7.text = "Pump_7"
self.ids.spinner_id_8.text = "Pump_8"
self.ids.spinner_id_8.text = "Pump_9"
self.parent.current = "DrinkMenu"
else:
bttn = Button(text = "Ok, I'm sorry!", font_size=24, size_hint_x = 0.5, size_hint_y = 0.5)
self.popup = Popup(title='There can be NO duplicate ingredients!', title_size = '32sp',
content=bttn,
auto_dismiss=False)
bttn.bind(on_release = self.close_n_open)
self.popup.open()
self.parent.current = "LoadNewIngredients"
def close_n_open(self,*args,**kwargs):
self.popup.dismiss()
def load_available_drinks(self, ingredients_list):
global drinks
drinks = []
for dri in drink_list:
dr = list(dri["ingredients"].keys())
result = all(elem in ingredients_list for elem in dr)
if result:
drinks.append(dri["name"])
#Define the ScreenManager
class MenuManager(ScreenManager):
pass
#Designate the .kv design file
kv = Builder.load_file('bartenderkv.kv')
class BartenderApp(App):
def build(self):
return kv
if __name__ == '__main__':
#set up all gpio pins
for pmp in config:
GPIO.setup(config[pmp]['pin'],GPIO.OUT)
GPIO.output(config[pmp]['pin'],GPIO.LOW)
BartenderApp().run()
and for completeness: .kv file:
#Need to define everything, the ScreenManager is the entity that keeps tabs
#on all the different menu windows
#This is for the popup, lets you instansiate a class from anywhere
#:import Factory kivy.factory.Factory
#:import ScrollView kivy.uix.scrollview
MenuManager:
MainMenu:
LoadNewIngredients:
DrinkMenu:
TopUpMenu:
<MainMenu>:
#name defines the signature, referenced when we want to move to it
name: "MainMenu"
GridLayout:
rows: 3
size: root.width, root.height
padding: 10
spacing: 10
Label:
text: "Main Menu"
font_size: 32
GridLayout:
cols: 2
size: root.width, root.height
spacing: 10
Button:
text: "Use Last Settings"
font_size: 32
on_press: root.use_last()
#on_release: app.root.current = "DrinkMenu"
Button:
text: "Load New Ingredients"
font_size: 32
on_release: app.root.current = "LoadNewIngredients"
Button:
text: "See Permissable Ingredients"
font_size: 32
#on_press: print("It Works")
on_release: Factory.PermissablePopup().open()
<LoadNewIngredients>:
name: "LoadNewIngredients"
GridLayout:
cols: 2
size: root.width, root.height
padding: 10
spacing: 10
#size hint sets relative sized, x-dir, y-dir
GridLayout:
size: root.width, root.height
size_hint_x: 0.2
rows: 2
Button:
text: "Continue"
font_size: 24
on_release: root.continued()
Button:
text: "Main Menu"
font_size: 24
on_release: app.root.current = "MainMenu"
GridLayout:
id: choices
rows: 3
cols: 6
orientation: 'tb-lr'
Label:
id: pump_1
text: "Pump 1"
font_size: 24
Label:
id: pump_2
text: "Pump 2"
font_size: 24
Label:
id: pump_3
text: "Pump 3"
font_size: 24
#Spinner is the easy drop down version in kivy, lets see how it looks.
Spinner:
id: spinner_id_1
text: "Pump_1"
#This references the get_ingredients function in the main p
values: root.get_ingredients()
on_text: root.spinner_clicked(1,spinner_id_1.text)
Spinner:
id: spinner_id_2
text: "Pump_2"
values: root.get_ingredients()
on_text: root.spinner_clicked(2,spinner_id_2.text)
Spinner:
id: spinner_id_3
text: "Pump_3"
values: root.get_ingredients()
on_text: root.spinner_clicked(3,spinner_id_3.text)
Label:
id: pump_4
text: "Pump 4"
font_size: 24
Label:
id: pump_5
text: "Pump 5"
font_size: 24
Label:
id: pump_6
text: "Pump 6"
font_size: 24
Spinner:
id: spinner_id_4
text: "Pump_4"
values: root.get_ingredients()
on_text: root.spinner_clicked(4,spinner_id_4.text)
#Spinner is the drop down version, lets see how it looks.
Spinner:
id: spinner_id_5
text: "Pump_5"
values: root.get_ingredients()
on_text: root.spinner_clicked(5,spinner_id_5.text)
Spinner:
id: spinner_id_6
text: "Pump_6"
values: root.get_ingredients()
on_text: root.spinner_clicked(6,spinner_id_6.text)
Label:
id: pump_7
text: "Pump 7"
font_size: 24
Label:
id: pump_8
text: "Pump 8"
font_size: 24
Label:
id: pump_9
text: "Pump 9"
font_size: 24
Spinner:
id: spinner_id_7
text: "Pump_7"
values: root.get_ingredients()
on_text: root.spinner_clicked(7,spinner_id_7.text)
Spinner:
id: spinner_id_8
text: "Pump_8"
values: root.get_ingredients()
on_text: root.spinner_clicked(8,spinner_id_8.text)
Spinner:
id: spinner_id_9
text: "Pump_9"
values: root.get_ingredients()
on_text: root.spinner_clicked(9,spinner_id_9.text)
<DrinkMenu>:
name: "DrinkMenu"
GridLayout:
cols: 2
width: root.width
height: self.minimum_height
padding: 10
spacing: 10
GridLayout:
height: root.height
size_hint_x: 0.4
rows: 2
Button:
text: "Top Up"
font_size: 24
on_release: app.root.current = "TopUpMenu"
Button:
text: "Main Menu"
font_size: 24
on_release: app.root.current = "MainMenu"
ScrollView:
size_hint_y: 0.1
pos_hint: {'x':0, 'y': 0.11}
do_scroll_x: False
do_scroll_y: True
GridLayout:
id: drinks
orientation: 'lr-tb'
size_hint_y: None
size_hint_x: 1.0
cols: 3
height: self.minimum_height
#row_default_height changes the buttons height
row_default_height: 150
row_force_default: True
<TopUpMenu>:
name: "TopUpMenu"
GridLayout:
cols: 2
width: root.width
height: self.minimum_height
padding: 10
spacing: 10
GridLayout:
height: root.height
size_hint_x: 0.4
rows: 1
Button:
text: "Back"
font_size: 24
on_release: app.root.current = "DrinkMenu"
ScrollView:
size_hint_y: 0.1
pos_hint: {'x':0, 'y': 0.11}
do_scroll_x: False
do_scroll_y: True
GridLayout:
id: topupdrinks
orientation: 'lr-tb'
size_hint_y: None
size_hint_x: 1.0
cols: 3
height: self.minimum_height
#row_default_height changes the buttons height
row_default_height: 150
row_force_default: True
#Create a rounded button, the #Button is what it inherits
<RoundedButton#Button>
background_color: (0,0,0,0)
background_normal: ''
canvas.before:
Color:
rgba:
(48/255,84/255,150/255,1)\
if self.state == 'normal' else (0.6,0.6,1,1) # Color is red if button is not pressed, otherwise color is green
RoundedRectangle:
size: self.size
pos: self.pos
radius: [58]
#Create a home button
#Create a drink button
#Create a new ingredients button
#Create a use last settings button
#Create a permissable ingredients button
<PermissablePopup#Popup>
auto_dismiss: False
#size_hint: 0.6,0.2
#pos_hint: {"x":0.2, "top":0.9}
title: "Permissable Ingredients"
GridLayout:
rows: 2
size: root.width, root.height
spacing: 10
GridLayout:
cols: 2
Label:
text: "Sodas"
font_size: 32
#Add list of sodas
Label:
text: "Alcohol"
font_size: 32
#Add list of alcohols
Button:
text: "Done"
font_size: 24
on_release: root.dismiss()
You could use a decorator to display the popup then run the function in a thread. Here is a self-contained example (try running this to see the output for yourself):
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.properties import BooleanProperty
import threading
from time import sleep
def show_popup(function):
def wrap(app, *args, **kwargs):
popup = CustomPopup() # Instantiate CustomPopup (could add some kwargs if you wish)
app.done = False # Reset the app.done BooleanProperty
app.bind(done=popup.dismiss) # When app.done is set to True, then popup.dismiss is fired
popup.open() # Show popup
t = threading.Thread(target=function, args=[app, popup, *args], kwargs=kwargs) # Create thread
t.start() # Start thread
return t
return wrap
class CustomPopup(Popup):
pass
kv = Builder.load_string( # Generic kv stuff
"""
<CustomPopup>:
size_hint: .8, .4
auto_dismiss: False
progress: 0
text: ''
title: "Drink Progress"
BoxLayout:
orientation: 'vertical'
Label:
text: root.text
size_hint: 1, 0.8
ProgressBar:
value: root.progress
FloatLayout:
Button:
text: 'Pour me a Drink!'
on_release: app.mix_drinks()
"""
)
class MyMainApp(App):
done = BooleanProperty(False)
def build(self):
return kv
#show_popup
def mix_drinks(self, popup): # Decorate function to show popup and run the code below in a thread
popup.text = 'Slicing limes...'
sleep(1)
popup.progress = 20
popup.text = 'Muddling sugar...' # Changing the popup attributes as the function runs!
sleep(2)
popup.progress = 50
popup.text = 'Pouring rum...'
sleep(2)
popup.progress = 80
popup.text = 'Adding ice...'
sleep(1)
popup.progress = 100
popup.text = 'Done!'
sleep(0.5)
self.done = True
if __name__ == '__main__':
MyMainApp().run()
The App.mix_drinks function runs in a thread while updating the progress bar and label in the CustomPopup instance.
Adding a cancel button is a different kind of beast. If you really wanted to do this you could kill the thread (see Is there any way to kill a Thread?) but I can't think of an easy way to do this.
Related
Here I have program that calculates profit based on expenses and revenue.
It works just fine as shown in the image. What I'm trying to do is change the text of buttons in the bottom most boxlayout every time the calc() function is triggered in the MyGrid() class. I tried to use id's but I'm doing it wrong. Also I tried using the on_press method in my .kv file but I actually want the button.text to change EVERYTIME the calc() is triggered irrespective of the button being pressed or not.
NOTE: only the MyGrid() class and MyScroll: is worth looking at everything else works as expected
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.metrics import dp
Builder.load_file("sample1.kv")
class MyLayout(BoxLayout):
pass
class HeaderBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint = (1,0.1)
self.orientation = "horizontal"
self.headers = ["Name","Price","Quantity","Name","Price","Quantity"]
for i in range(6):
b = Button(text=str(self.headers[i]))
self.add_widget(b)
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 6
self.textinputs=[0] #i put a 0 in that list so that my object indexing can start with 1
#made a diff list full of indexes so i can use them in my calculating algo
self.expense_price_index = [2,8,14,20,26,32,38,44,50,56]
self.expense_quantity_index = [3,9,15,21,27,33,39,45,51,57]
self.revenue_price_index = [5,11,17,23,29,35,41,47,53,59]
self.revenue_quantity_index = [6,12,18,24,30,36,42,48,54,60]
#initializing some values
self.expense_total = 0
self.revenue_total = 0
self.profit = 0
#making a grid full of textinputs and adding them to self.textinputs list
for i in range(60):
b = TextInput(multiline=False,font_size=dp(20),size_hint=(1,None),height=50)
b.bind(on_text_validate=self.calc) #binding a function to make my calculations
self.textinputs.append(b)
self.add_widget(b)
#FUNCTION THAT DOES THE CALCULATIONS
def calc(self,ti_instance):
default_quantity = 1
self.expense_total = 0
self.revenue_total = 0
# CALCULATING EXPENSE TOTAL
for i in self.expense_price_index:
if self.textinputs[i].text == "":
continue
elif self.textinputs[i+1].text == "":
self.expense_total += int(self.textinputs[i].text) * default_quantity
else:
self.expense_total += int(self.textinputs[i].text) * int(self.textinputs[i+1].text)
# CALCULATING REVENUE TOTAL
for i in self.revenue_price_index:
if self.textinputs[i].text == "":
continue
elif self.textinputs[i+1].text == "":
self.revenue_total += int(self.textinputs[i].text) * default_quantity
else:
self.revenue_total += int(self.textinputs[i].text) * int(self.textinputs[i+1].text)
# CALCULATING PROFIT DOING BASIC ARITHMETIC
self.profit = str(self.revenue_total - self.expense_total)
print("Profit: " + self.profit)
class MyScroll(ScrollView):
pass
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyApp().run() ```
```#:import dt datetime.date
#:import dp kivy.metrics.dp
#:set navbar_button_color (59/255, 68/255, 75/255, 1)
<MyLayout>:
orientation: "vertical"
BoxLayout:
size_hint: 1,0.1
Button:
text: "Back"
size_hint: 0.1,1
Button:
text: "Home"
size_hint: 0.1,1
Button:
text: "Daily"
size_hint: 0.1,1
Button:
text: "Weekly"
size_hint: 0.1,1
Button:
text: "Monthly"
size_hint: 0.1,1
Label:
text: dt.today().strftime("%d %B %Y")
size_hint: 0.5,1
canvas.before:
Color:
rgb: 59/255, 78/255, 85/255,1
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
size_hint: 1,0.15
Button:
text: "EXPENSES"
Button:
text: "REVENUE"
HeaderBox:
MyScroll:
BoxLayout:
orientation:"horizontal"
size_hint: 1,0.2
BoxLayout:
orientation: "vertical"
Button:
text: "Expense Total:"
font_size: dp(20)
on_press:
self.text: "Expense Total: " + str(my_grid.expense_total)
Button:
text: "Revenue Total:"
font_size: dp(20)
on_press:
self.text: "Revenue Total: " + str(my_grid.revenue_total)
Button:
text: "Profit:"
font_size: dp(40)
on_press:
self.text: "Profit: " + str(my_grid.profit)
<MyScroll>:
my_grid: my_grid
MyGrid:
id: my_grid
size_hint: 1, None
height: self.minimum_height```
#for #ApuCoder
```Button:
id: revenue_button
text: "Revenue Total: "
font_size: dp(20)
on_press:
self.text = "Revenue Total: " + str(my_grid.revenue_total)```
```class MyGrid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
expense_button = ObjectProperty()```
```# CALCULATING PROFIT DOING BASIC ARITHMETIC
self.profit = str(self.revenue_total - self.expense_total)
self.expense_button.text = "Expense Total: " + str(self.expense_total) <-----
print("Profit: " + self.profit) ``````
You can access your MyGrid in python or kvlang by creating a reference. One of the few ways is simply using an id.
With this and other necessary modifications your full code in kvlang looks like,
#:import dt datetime.date
#:import dp kivy.metrics.dp
#:set navbar_button_color (59/255, 68/255, 75/255, 1)
<MyLayout>:
orientation: "vertical"
BoxLayout:
size_hint: 1,0.1
Button:
text: "Back"
size_hint: 0.1,1
Button:
text: "Home"
size_hint: 0.1,1
Button:
text: "Daily"
size_hint: 0.1,1
Button:
text: "Weekly"
size_hint: 0.1,1
Button:
text: "Monthly"
size_hint: 0.1,1
Label:
text: dt.today().strftime("%d %B %Y")
size_hint: 0.5,1
canvas.before:
Color:
rgb: 59/255, 78/255, 85/255,1
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
size_hint: 1,0.15
Button:
text: "EXPENSES"
Button:
text: "REVENUE"
HeaderBox:
MyScroll: # Add the grid directly here.
MyGrid:
id: my_grid
size_hint: 1, None
height: self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint: 1,0.2
BoxLayout:
orientation: "vertical"
Button:
# text: "Expense Total:"
text: "Expense Total: " + str(my_grid.expense_total)
font_size: dp(20)
on_press:
self.text = "Expense Total: " + str(my_grid.expense_total)
Button:
# text: "Revenue Total:"
text: "Revenue Total: " + str(my_grid.revenue_total)
font_size: dp(20)
on_press:
self.text = "Revenue Total: " + str(my_grid.revenue_total)
Button:
# text: "Profit:"
text: "Profit: " + str(my_grid.profit)
font_size: dp(40)
on_press:
self.text = "Profit: " + str(my_grid.profit)
#<MyScroll>:
# my_grid: my_grid
# MyGrid:
# id: my_grid
# size_hint: 1, None
# height: self.minimum_height
Now create some properties for those attributes using NumericProperty in python.
Modified full code in python,
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import NumericProperty
Builder.load_file("sample1.kv")
class MyLayout(BoxLayout):
pass
class HeaderBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint = (1,0.1)
self.orientation = "horizontal"
self.headers = ["Name","Price","Quantity","Name","Price","Quantity"]
for i in range(6):
b = Button(text=str(self.headers[i]))
self.add_widget(b)
class MyGrid(GridLayout):
expense_total = NumericProperty(0)
profit = NumericProperty(0)
revenue_total = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 6
self.textinputs=[0] #i put a 0 in that list so that my object indexing can start with 1
#made a diff list full of indexes so i can use them in my calculating algo
self.expense_price_index = [2,8,14,20,26,32,38,44,50,56] # range(2, 60, 6)
self.expense_quantity_index = [3,9,15,21,27,33,39,45,51,57] # range(3, 60, 6) etc.
self.revenue_price_index = [5,11,17,23,29,35,41,47,53,59]
self.revenue_quantity_index = [6,12,18,24,30,36,42,48,54,60]
#initializing some values
# self.expense_total = 0
# self.revenue_total = 0
# self.profit = 0
#making a grid full of textinputs and adding them to self.textinputs list
for i in range(60):
b = TextInput(multiline=False,font_size=dp(20),size_hint=(1,None),height=50)
b.bind(on_text_validate=self.calc) # binding a function to make my calculations
# b.bind(text=self.calc) # Bound to the property text.
self.textinputs.append(b)
self.add_widget(b)
#FUNCTION THAT DOES THE CALCULATIONS
def calc(self,ti_instance):
# def calc(self,ti_instance, text): # If bound to the property text.
default_quantity = 1
self.expense_total = 0
self.revenue_total = 0
# CALCULATING EXPENSE TOTAL
for i in self.expense_price_index:
if self.textinputs[i].text == "":
continue
elif self.textinputs[i+1].text == "":
self.expense_total += int(self.textinputs[i].text) * default_quantity
else:
self.expense_total += int(self.textinputs[i].text) * int(self.textinputs[i+1].text)
# CALCULATING REVENUE TOTAL
for i in self.revenue_price_index:
if self.textinputs[i].text == "":
continue
elif self.textinputs[i+1].text == "":
self.revenue_total += int(self.textinputs[i].text) * default_quantity
else:
self.revenue_total += int(self.textinputs[i].text) * int(self.textinputs[i+1].text)
# CALCULATING PROFIT DOING BASIC ARITHMETIC
self.profit = self.revenue_total - self.expense_total
print("Profit: " + str(self.profit))
class MyScroll(ScrollView):
pass
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyApp().run()
Once again fiddling with kivy and encountering problems.
How do I communicate a variable (the text from a spinner) into a popup's button (it has two and has to be custom made) OR communicate said variable to another page? I need this to run a song on a piano, the other scripts are mostly all done and working.
Thanks in advance, here is the popup's code:
<Custpopup#Popup>:
size_hint: .8, .8
auto_dismiss: False
title: 'Play me a song'
BoxLayout:
spacing: 10
Button:
id: play
text: 'Play'
on_press:
# what to do here?
Button:
id: close
text: 'Close popup'
on_press:
root.dismiss()
print('closed')
Edit: Smallest reproducible example here: https://pastebin.com/y7sW8ByH
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.app import App
from os import listdir
DEBUG = False
Builder.load_string('''
<Custpopup#Popup>:
size_hint: .7, .6
auto_dismiss: False
title: 'Play me a song'
BoxLayout:
spacing: 10
Button:
id: play
text: 'Play'
on_press:
# what to do here?
Button:
id: close
text: 'Close popup'
on_press:
root.dismiss()
print('closed')
<JukeBoxMode>:
name: 'juke'
id: jukebox
FloatLayout:
id: layout
Button:
id: back
text: 'Return'
size_hint: None, None
size: 150, 44
pos: 0, layout.height - self.height
on_press: print('Pressed RETURN button')
on_release:
print(app.root.current)
Spinner:
id: spin
size_hint: None, None
size: 400, 44
pos: (layout.center_x - self.width/2, layout.height - self.height)
text: 'Music'
values: root.musicList
on_text:
root.selMus(self.text)
''')
class CustPopup(Popup):
pass
class JukeBoxMode(Screen):
musicList = []
musdir = r'/home/pi/Desktop/Music21/midi/'
muslist = listdir(musdir)
for file in muslist:
if file.endswith('.mid'):
musicList.append(file[:-4])
if DEBUG:
print(musicList)
musicList.sort()
if DEBUG:
print(musicList)
def selMus(self,sel):
custpop = CustPopup()
if sel != 'Music':
custpop.open()
def playPiano(self):
dicmusic = {self.musicList.index(self.ids['spin'].text): self.ids['spin'].text}
if DEBUG:
print(dicmusic)
song, fin, instr, seq = infoMorceau(dicmusic[0])
print(song, fin, instr, seq)
class HudPianoApp(App):
def build(self):
return JukeBoxMode()
if __name__ == '__main__':
HudPianoApp().run()
Not sure exactly what you want to do, but modifyingyour selMus() method to use the sel in your CustPopup might help:
def selMus(self,sel):
print(sel)
custpop = CustPopup()
if sel != 'Music':
# Use 'sel' in the Popup
custpop.ids.play.text = 'Play ' + sel
custpop.ids.play.on_release = partial(self.do_something, sel)
custpop.open()
def do_something(self, sel):
# Do whatever you want with `sel` here
print('do something with', sel)
I'm trying to build an app that can calculate the sum of two values. I have a screen called Therdwindow that has three text input widgets.
from kivy.app import App
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen`
class MainWindow(Screen):
pass
class SecondWindow(Screen):
pass
class Therdwindow(Screen):
pass
class Fourwindow(Screen):
pass
class FunfthWindow(Screen):
def calculate3(self,bibi,mimi):
kiki = str(float(bibi) + float(mimi))
if kiki:
try :
self.result_1.text = str(eval(kiki))
except Exception:
self.result_1.text = 'Error'
class Sixwindow(Screen):
pass
class WindowManager(ScreenManager):
psss
kv = Builder.load_file("layout.kv")
class MyMainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MyMainApp().run()
A person is supposed to enter the Password 'gin' than click on a button 'NEXT' to go to the next screen named SecondWindow , then click on a button 'NEXT' to go to the next screen named TherdWindow ,
Then enter a First Value in the Box then click on a button 'NEXT' to go to the next screen named fourWindow ,
than enter the second Value and click on a button 'NEXT' to go to the next screen named funfthWindow .
there should have the 'Result' if he click on a button result. In this screen there is a Label that should print the volume of the sum that the person specified.
layout.kv
<CustButton#Button>:
font_size: 40
WindowManager:
MainWindow:
SecondWindow:
Therdwindow:
Fourwindow:
FunfthWindow:
Sixwindow:
<MainWindow>:
name: "main"
<MainWindow>:
name: "main"
GridLayout:
cols:1
GridLayout:
cols: 2
orientation: 'vertical'
Label:
text: "Password: "
font_size: "40sp"
background_color: (1,1,0,1)
font_name: 'RobotoMono-Regular.ttf'
TextInput:
id: passw
multiline: False
font_size: "40sp"
CustButton:
text: "Submit"
background_color: (0.8,0.8,0,1)
on_press:
app.root.current = "second" if passw.text == "gin" else "six"
root.manager.transition.duration = 1
root.manager.transition.direction = "left"
<SecondWindow>:
name: "second"
GridLayout:
cols: 1
spacing: 10
CustButton:
text: "Go Back"
on_press:
app.root.current = "main"
root.manager.transition.direction = "right"
CustButton:
text: "next"
on_press:
app.root.current = "therd"
root.manager.transition.direction = "left"
<Therdwindow>:
id:lulu
name: "therd"
nani:feras1
rows: 20
padding: 0
spacing: 2
GridLayout:
spacing: 10
cols:2
Label:
text: "Enter The First Value : "
font_size: "30sp"
TextInput:
id:first_Value
font_size: 40
multiline: True
CustButton:
text: "go back"
on_press:
app.root.current = "second"
root.manager.transition.direction = "right"
CustButton:
text: "Next"
on_press:
app.root.current = "four"
root.manager.transition.direction = "left"
<Fourwindow>:
id:lala
name: "four"
nani21:feras2
rows: 20
padding: 0
spacing: 2
GridLayout:
spacing: 10
cols:2
Label:
text: "Enter The second Value : "
font_size: "30sp"
TextInput:
id: second_Value
font_size: 40
multiline: True
CustButton:
text: "go back"
on_press:
app.root.current = "therd"
root.manager.transition.direction = "right"
CustButton:
text: "NEXT"
on_press:
app.root.current = "funfth"
root.manager.transition.direction = "left"
<FunfthWindow>:
id:CalcmGridLayout
name: "funfth"
result_1:label_id
rows: 20
padding: 0
spacing: 2
GridLayout:
spacing: 10
cols:2
CustButton:
text: "Result : "
font_size: "30sp"
on_press:CalcmGridLayout.calculate3(first_Value.text,second_Value.text)
Label:
id: label_id
font_size: 40
multiline: True
CustButton:
text: "go back"
on_press:
app.root.current = "four"
root.manager.transition.direction = "right"
CustButton:
text: "NEXT"
on_press:
app.root.current = "main"
root.manager.transition.direction = "left"
<Sixwindow>:
name: "six"
GridLayout:
cols: 1
spacing: 10
Label:
text: 'das Password ist falsch'
font_size: "40sp"
CustButton:
text: "nochmal"
on_press:
app.root.current = "main"
root.manager.transition.direction = "right"
When I click of 'Result' I get this Error NameError : first_Value is not defined
please help. I would really appreciate any advice.
Well, there's a lot of ways to do that, for the simplicity I'll do all operations in the MyMainApp class:
from kivy.properties import ObjectProperty
from Kivy.clock import Clock
...
class Therdwindow(Screen):
first_Value = ObjectProperty(None)
def getvalue(self):
return self.first_Value
class Fourwindow(Screen):
second_Value = ObjectProperty(None)
def getvalue(self):
return self.second_Value
class FunfthWindow(Screen):
label_id = ObjectProperty(None)
def getlabel(self):
return self.label_id
class WindowManager(ScreenManager):
pass
class MyMainApp(App):
def build(self):
with open('layout.kv', encoding='utf-8', errors='ignore') as f:
Builder.load_string(f.read())
# creating objects of screens
self.mainwindow = MainWindow()
self.secondwindow = SecondWindow()
self.therdwindow = Therdwindow()
self.fourwindow = Fourwindow()
self.funfthwindow = FunthWindow()
# creating object of screen manager
self.sm = WindowManager()
# getting all object properties
self.first_Value = self.therdwindow.getvalue()
self.second_Value = self.fourwindow.getvalue()
self.label_id = self.funfthwindow.getlabel()
# connecting object properties to widgets from kv file
# sometimes we need to delay that, because the app can't load so quickly, so let's make a method for it
Clock.schedule_once(self.getids)
# adding screens to screen manager
self.sm.add_widget(self.mainwindow)
self.sm.add_widget(self.secondwindow)
self.sm.add_widget(self.therdwindow)
self.sm.add_widget(self.fourwindow)
self.sm.add_widget(self.funfthwindow)
# in case you want screen manager be able to work you should return it
return self.sm
def getids(self):
self.first_Value = self.therdwindow.ids.first_Value
self.second_Value = self.fourwindow.ids.second_Value
self.label_id = self.funfthwindow.ids.label_id
# method that does what you want
def count(self):
self.label_id.text = str(int(self.first_Value.text) + int(self.second_Value.text))
if __name__ == "__main__":
MyMainApp().run()
And you need to edit your kv file, change all that
on_press:
app.root.current = "second"
to that:
on_press:
app.sm.current = "second"
And change this:
CustButton:
text: "Result : "
font_size: "30sp"
on_press:CalcmGridLayout.calculate3(first_Value.text,second_Value.text)
to this:
CustButton:
text: "Result : "
font_size: "30sp"
on_press: app.count
Also you need to add that lines in classes:
<Therdwindow>:
...
first_Value: first_Value.__self__
<Fourwindow>:
...
second_Value: second_Value.__self__
<FunfthWindow>:
...
label_id: label_id.__self__
And delete this:
WindowManager:
MainWindow:
SecondWindow:
Therdwindow:
Fourwindow:
FunfthWindow:
Sixwindow:
You are trying to use ids from one rule inside another rule, but ids are only available for the current rule. So, this line in your kv will not work:
on_press:CalcmGridLayout.calculate3(first_Value.text,second_Value.text)
A way to fix this, is to access the ids by first getting the Screen where that id is defined, and that is more easily done outside of kv. So, I would recommend replacing the above line with:
on_press:root.calculate3()
And then rewrite your calculate3() method slightly to access the other values:
class FunfthWindow(Screen):
def calculate3(self):
bibi = App.get_running_app().root.get_screen('therd').ids.first_Value.text
mimi = App.get_running_app().root.get_screen('four').ids.second_Value.text
kiki = str(float(bibi) + float(mimi))
if kiki:
try :
self.ids.label_id.text = str(eval(kiki))
except Exception:
self.ids.label_id.text = 'Error'
Newbie to Python - I am writing a raspberry pi app with touch screen. Manage to make it work but the Increment function is being executed twice when pressing the + button (same for decrement). These buttons are enabled only while another button is pressed but this is not the problem.
Any help please?
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.core.window import Window
Window.show_cursor = False
import serial
import time
port = serial.Serial("/dev/ttyS0", baudrate=19200, timeout=3.0)
btnNum = '0'
b1Tmp = 0; b2Tmp = 0; b3Tmp = 0; b4Tmp = 0; b5Tmp = 0; b6Tmp = 0; b7Tmp = 0; b8Tmp = 0
b1Max =50; b2Max =50; b3Max =50; b4Max =50; b5Max =50; b6Max =50; b7Max =50; b8Max =50;
class Container(Widget):
labR1= ObjectProperty(None)
labR2= ObjectProperty(None)
labR3= ObjectProperty(None)
labR4= ObjectProperty(None)
labR5= ObjectProperty(None)
labR6= ObjectProperty(None)
labR7= ObjectProperty(None)
labR8= ObjectProperty(None)
butUP= ObjectProperty(None)
butDN= ObjectProperty(None)
labM1= ObjectProperty(None)
labM2= ObjectProperty(None)
labM3= ObjectProperty(None)
labM4= ObjectProperty(None)
labM5= ObjectProperty(None)
labM6= ObjectProperty(None)
labM7= ObjectProperty(None)
labM8= ObjectProperty(None)
def bpressed(self,instance):
global btnNum
btnNum = instance.text
self.butDN.disabled = False
self.butUP.disabled = False
def breleased(self,instance):
global btnNum
btnNum = '0'
self.butDN.disabled = True
self.butUP.disabled = True
def increment(self,instance):
global btnNum,b1Max,b2Max,b3Max,b4Max,b5Max,b6Max,b7Max,b8Max
if (btnNum == '1'):
if (b1Max<100): b1Max+=2
self.labM1.text = str(b1Max)
if (btnNum == '2'):
if (b2Max<100): b2Max=b2Max+1
self.labM2.text = str(b2Max)
if (btnNum=='3'):
if (b3Max<100): b3Max+=1
self.labM3.text = str(b3Max)
if (btnNum=='4'):
if (b4Max<100): b4Max+=1
self.labM4.text = str(b4Max)
if (btnNum=='5'):
if (b5Max<100): b5Max+=1
self.labM5.text = str(b5Max)
if (btnNum=='6'):
if (b6Max<100): b6Max+=1
self.labM6.text = str(b6Max)
if (btnNum=='7'):
if (b7Max<100): b7Max+=1
self.labM7.text = str(b7Max)
if (btnNum=='8'):
if (b8Max<100): b8Max+=1
self.labM8.text = str(b8Max)
def decrement(self,instance):
global btnNum,b1Max,b2Max,b3Max,b4Max,b5Max,b6Max,b7Max,b8Max
if (btnNum == '1'):
if (b1Max>20): b1Max-=1
self.labM1.text = str(b1Max)
if (btnNum == '2'):
if (b2Max>20): b2Max-=1
self.labM2.text = str(b2Max)
if (btnNum=='3'):
if (b3Max>20): b3Max-=1
self.labM3.text = str(b3Max)
if (btnNum=='4'):
if (b4Max>20): b4Max-=1
self.labM4.text = str(b4Max)
if (btnNum=='5'):
if (b5Max>20): b5Max-=1
self.labM5.text = str(b5Max)
if (btnNum=='6'):
if (b6Max>20): b6Max-=1
self.labM6.text = str(b6Max)
if (btnNum=='7'):
if (b7Max>20): b7Max-=1
self.labM7.text = str(b7Max)
if (btnNum=='8'):
if (b8Max>20): b8Max-=1
self.labM8.text = str(b8Max)
def serialsend(self,instance):
port.write("\r\nSay something:".encode())
def close(self , obj):
App.get_running_app().stop()
Window.close()
class BoilerApp(App):
def build(self):
self.title = 'Boiler Monitor'
return Container()
if __name__ == "__main__":
from kivy.core.window import Window
Window.fullscreen = True
app = BoilerApp()
app.run()
kv file
<MyButton#Button>:
color: .8,.9,0,1
font_size: 30
<MyLabel#Label>:
color: .8,.9,0,1
font_size: 30
<Container>
labR1: labR1
labR2: labR2
labR3: labR3
labR4: labR4
labR5: labR5
labR6: labR6
labR7: labR7
labR8: labR8
butUP: butUP
butDN: butDN
labM1: labM1
labM2: labM2
labM3: labM3
labM4: labM4
labM5: labM5
labM6: labM6
labM7: labM7
labM8: labM8
GridLayout:
cols: 1
size: root.width, root.height
Label:
text: "Boiler Alarm"
size_hint: .5, .2
font_size: 40
background_color: 1,0,0,1
GridLayout:
cols:10
MyLabel:
text: "Boiler"
MyButton:
id: butB1
text: "1"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB2
text: "2"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB3
text: "3"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB4
text: "4"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB5
text: "5"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB6
text: "6"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB7
text: "7"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyButton:
id: butB8
text: "8"
on_press: root.bpressed(self)
on_release: root.breleased(self)
MyLabel:
text: ""
Label:
text: "Reading"
font_size: 25
MyLabel:
id: labR1
text: "0"
MyLabel:
id: labR2
text: "0"
MyLabel:
id: labR3
text: "0"
MyLabel:
id: labR4
text: "0"
MyLabel:
id: labR5
text: "0"
MyLabel:
id: labR6
text: "0"
MyLabel:
id: labR7
text: "0"
MyLabel:
id: labR8
text: "0"
MyButton:
id: butUP
text: "+"
disabled: True
on_press: root.increment(self)
Label:
text: "Minimum"
font_size: 25
MyLabel:
id: labM1
text: "50"
MyLabel:
id: labM2
text: "50"
MyLabel:
id: labM3
text: "50"
MyLabel:
id: labM4
text: "50"
MyLabel:
id: labM5
text: "50"
MyLabel:
id: labM6
text: "50"
MyLabel:
id: labM7
text: "50"
MyLabel:
id: labM8
text: "50"
MyButton:
id: butDN
text: "-"
disabled: True
on_release: root.decrement(self)
Button:
id: btnexit
text: "Exit"
size_hint: .5, .1
on_press: root.close(self)
I had the same problem and found multiple workarounds after fighting this for a while.
The issue seems to be that kivy interprets both the mouse and the touch events simultaneously. This means when you touch the display both a "mouse" click and a "touch" press event is fired, resulting in double presses.
Solution 1 – change the kivy config.ini
The easiest fix is to edit the [input]-section in ~/.kivy/config.ini in su a way:
[input]
%(name)s = probesysfs,provider=hidinput
mouse = mouse
# mtdev_%(name)s = probesysfs,provider=mtdev
hid_%(name)s = probesysfs,provider=hidinput
Just comment out the line starting with mtdev_ and restart the program.
Solution 2 – manually check the event-kind
If you want to use the mouse somewhere still, there is a different way to do this. Let's assume you create a button somewhere like this:
button = ToggleButton(text="Cool Button")
button.bind(on_release=on_toggle)
self.add_widget(button)
You can then check whether our on_toggle-function is activated by a mouse- or a touch-event as follows:
from kivy.input.providers.mouse import MouseMotionEvent
def on_toggle(button_instance):
if isinstance(button_instance.last_touch, MouseMotionEvent):
print(f"Last Touch was from the Mouse {button_instance.last_touch}")
You should not use timeout with kivy. You should instead use the Clock function. The timeout function would halt all your program which may lead to these issues. Whereas Clock schedules specific events without halting the program altogether.
I need to create a list of items with a custom row layout. Every row should have an image, one main button and two other small buttons (side by side), after pressing the main button I would like to display a short text in a scroll view under the row.
I am using a custom BoxLayout that looks like this:
<InfoRow#BoxLayout>:
landmarkName: self.landmarkName
pozaId: self.pozaId
rowId: self.rowId
starId: self.starId
svId: self.svId
show_infoId: self.show_infoId
orientation: 'vertical'
size_hint_y: None
height:self.minimum_height
BoxLayout:
size_hint_y: None
height: 50
pos_hint: {"x": 0, 'top': 1}
AsyncImage:
size_hint_x: 0.2
id: root.pozaId
source: root.srcImg
ToggleButton:
id: root.rowId
font_size: self.height * 0.5
text_size: self.size
text: root.landmarkName
on_release: root.toggle_info()
ToggleButton:
#on_release: self.parent.parent.ids.calc_input.text += self.text
size_hint: None, None
width: 50
height: 50
background_down: 'data/check.png'
background_normal: 'data/full_plus.png'
ToggleButton:
id: root.starId
size_hint: None, None
height: 50
width: 50
background_down: root.starImg
background_normal: root.starImg
on_release: root.saveLocation()
ScrollView:
id: root.svId
size_hint_y: None
pos_hint: {"x": 0.12, 'top': 1}
height: 0
InfoTextInput:
id: root.show_infoId
text: ""
size_hint_y: None
height: self.minimum_height
And my python code:
class InfoRow(BoxLayout):
pozaId = StringProperty(' ')
rowId = StringProperty(' ')
starId = StringProperty(' ')
svId= StringProperty(' ')
show_infoId=StringProperty(' ')
landmarkName = StringProperty(' ')
pressed = False
starImg = StringProperty('data/white.png')
srcImg = StringProperty('data/white.png')
def __init__(self, **kwargs):
super(InfoRow, self).__init__(**kwargs)
def toggle_info(self):
self.pressed = not self.pressed
if self.pressed:
height = self.height * .25
else:
height = 0
Animation(height=height, d=.25, t='out_quart').start(
self.ids[self.svId])
if not self.pressed:
self.ids[self.show_infoId].focus = False
return
self.ids[self.show_infoId].text = "TO DO"
self.ids[self.svId].scroll_y = 1
def saveLocation(self):
pass
Where I create my list:
class MainScreen(Screen):
def getSpecificInfo(self, category):
...
self.ids["extraInfo"].clear_widgets()
for i in range (0, 15):
wid = InfoRow(pozaId = pozaId, landmarkName=landmarkName, starImg = starImg ,rowId = rowId, starId = starId, svId= svId, show_infoId=show_infoId)
self.ids["extraInfo"].add_widget(wid)
For now I managed to create dynamically my list, but after pressing the button the ScrollView doesn't show and I have the following error:
self.ids[self.svId])
KeyError: 'sv0'
I have no idea what I am doing wrong.
Any ideas?
Problem - KeyError
self.ids[self.svId])
KeyError: 'sv0'
Root Cause
It means that the value, 'sv0' in self.svId does not exist in self.ids dictionary type property.
Note
Widget created dynamically in Python code, the id assigned to the widget is not added to self.ids. You might want to create your own dictionary to store them for accessing it later.