How do I create unique dropdown menus in the same screen? - python

I am currently working on creating a pretty basic fitness app in Python using Kivy. It's been going pretty smoothly but I recently ran into a problem while trying to implement a dropdown menu into one of my screens. I am trying to put 3 different dropdown menus into one screen which will return certain values that I will later use for the main function of the app (which will be to generate a daily fitness routine). The problem is, each dropdown menu has the same option. For example, two of the dropdown menus I want to use are 'time availability'(30 mins, 60 mins...120 mins) and 'Fitness Level' (scale of 1-3). But each dropdown menu ends up having the same contents as whichever one I could first (such as time availability 30 mins, 60mins...120 mins and then the same for the contents inside of fitness level.)
Does anyone have any suggestions for how I can keep each dropdown menu unique within the same screen? The code for my 2 files (one .py and one .kv) are attached. In the code I have below, I removed the contents of the dropdown menus so they are basically empty buttons.
.py file:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.base import runTouchApp
import webbrowser
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import ObjectProperty
######################################################################
class KivyTutorRoot(BoxLayout):
def __init__(self, **kwargs):
super(KivyTutorRoot, self).__init__(**kwargs)
#list of previous screens
self.screen_list = []
def changeScreen(self, next_screen):
operations = "Get Fit, Create User".split(',')
question = None
#if screen is not already in the list of previous screens...
if self.ids.kivy_screen_manager.current not in self.screen_list:
self.screen_list.append(self.ids.kivy_screen_manager.current)
if next_screen == 'about this app':
self.ids.kivy_screen_manager.current = "about_screen"
elif next_screen == 'get fit':
self.ids.kivy_screen_manager.current = "getFitScreen"
def onBackBtn(self):
#check if there are any screens to go back to
if self.screen_list:
#if there are screens we can go back to. Then go back to that screen
self.ids.kivy_screen_manager.current = self.screen_list.pop()
#the pop() will return the last item from the list, aka the last screen we visited
#say we don't want to close
return True
#no more screens to go back to, so we close
return False
###############################################################################
#dropdown menu classes here:
class CustomDropDownTime(DropDown):
pass
class CustomDropDownGym(DropDown):
pass
##############################################################################
#This will be a screen for all of the fitness functions
class getFitScreen(Screen):
top_layout = ObjectProperty(None)
dd_btn = ObjectProperty(None)
top_layout2 = ObjectProperty(None)
dd_btn2 = ObjectProperty(None)
def __init__(self,*args,**kwargs):
super(getFitScreen, self).__init__(*args, **kwargs)
#everything undere this is new code from stackover flow and it works for one. Stops working at GYM
self.drop_down = CustomDropDownTime()
dropdown = DropDown()
#time availability dropdown
time = ['15-30mins', '30-60mins', '60-90mins','90-120mins']
for times in time:
btn = Button(text='%r' %times, size_hint_y=None, height=30)
btn.bind(on_release=lambda btn: dropdown.select(btn.text))
dropdown.add_widget(btn)
mainbutton = Button(text='Time Available', size_hint=(1, 1))
mainbutton.bind(on_release=dropdown.open)
dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
###############################################################################
#This will be a screen for the charts
class graphScreen(Screen):
def __init__(self,**kwargs):
super(graphScreen, self).__init__(**kwargs)
################################################################################
class KivyTutorApp(App):
def __init__(self, **kwargs):
super(KivyTutorApp, self).__init__(**kwargs)
Window.bind(on_keyboard=self.onBackBtn)
def onBackBtn(self, window,key,*args):
#if user presses back button
#27 is the numerical code for back button
if key == 27:
return self.root.onBackBtn()
def build(self):
return KivyTutorRoot()
# this next part is so that we can 'get text' from this .py file when running from our .kv file
def getText(self):
# you need markup: True to use references like these
return ("Hey there! \nThis App was built using "
"[b][ref=kivy]kivy[/ref][/b]\n"
"Feel free to look at the source code "
"[b][ref=sour"
"ce]here[/ref][/b].\n"
"This app is under the [b][ref=mit]MIT License[/ref][/b]\n"
"Me: [b][ref=website]#kevin_adrian95[/ref][/b]")
# this next part is going to make the actual references
def on_ref_press(self, instance, ref):
dict = {
"source": "https://github.com/gopar/Kivy-Tutor",
# youre going to want to change this to your own github when you finish.
"website": "https://www.instagram.com/kevin_adrian95/",
"kivy": "https://kivy.org/#home",
"mit": "https://github.com/gopar/kivy-Tutor/blob/master/LICENSE"
}
webbrowser.open(dict[ref])
KivyTutorApp().run()
.kv file:
<WrappedLabel#Label>:
size_hint_y: None
height: self.texture_size[1]+(self.texture_size[1]/2)
markup: True
<CustomDropDownTime>:
Button:
text: '15-30 mins'
size_hint_y: None
height: 44
on_release: root.select('15-30mins')
Button:
text: '30-60 mins'
size_hint_y: None
height: 44
on_release: root.select('30-60min')
Button:
text: '60-90 mins'
size_hint_y: None
height: 44
on_release: root.select('60-90mins')
Button:
text: '90-120 mins'
size_hint_y: None
height: 44
on_release: root.select('90-120mins')
<CustomDropDownGym>:
Button:
text: 'Yes'
size_hint_y: None
height: 44
on_release: root.select('Yes')
Button:
text: 'No'
size_hint_y: None
height: 44
on_release: root.select('No')
< KivyTutorRoot >:
orientation: "vertical"
ActionBar:
ActionView:
ActionPrevious:
title: 'Kevin Adrian'
with_previous: False
ActionOverflow:
ActionButton:
text: "Settings"
on_press: app.open_settings()
ScreenManager:
id: kivy_screen_manager
StartScreen:
name: "start_screen"
AboutScreen:
id: about_screen
name: "about_screen"
getFitScreen:
id: getFitScreen
name: "getFitScreen"
<StartScreen#Screen>:
BoxLayout:
#settings
orientation: "vertical"
padding: root.width * .2, root.height*.1
spacing: min(root.width, root.height)*.1
WrappedLabel:
text: "[b] Kevin Adrian [/b]"
font_size: min(root.height, root.width) /10
Button:
text: "Get Fit"
font_size: 35
on_release: app.root.changeScreen(self.text.lower())
Button:
text: "Create User"
font_size: 20
Button:
text: "About this app"
on_release: app.root.changeScreen(self.text.lower())
<AboutScreen#Screen>:
BoxLayout:
padding: root.width * .02, root.height*.02
Label:
text: app.getText()
halign: "center"
markup: True
font_size: root.height / 20
text_size: self.width, None
center_y: .5
on_ref_press: app.on_ref_press(*args)
<getFitScreen>:
id: getFitScreen
top_layout: topLayoutID
dd_btn: btn_ddID
BoxLayout:
id: topLayoutID
size_hint: 1, .05
pos_hint: {'x': 0, 'y': .95}
Button:
id: btn_ddID
text: 'Time Availablity'
on_release: root.drop_down.open(self)
Button:
id: btn_ddID2
text: 'Gym Access'
on_release: root.drop_down.open(self)
Button:
text: 'Training Level'
on_release: root.drop_down.open(self)

class getFitScreen(Screen):
def __init__(self, *args, **kwargs):
super(getFitScreen, self).__init__(*args, **kwargs)
self.dropdown = DropDown()
self.dropdown1 = DropDown()
self.dropdown2= DropDown()
time = ['15-30mins', '30-60mins', '60-90mins', '90-120mins']
level = [1, 2, 3]
access = [True, False]
for times in time:
btn = Button(text='%r' % times, size_hint_y=None, height=30)
btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(btn)
mainbutton = Button(text='Time Available', size_hint=(1, 1))
mainbutton.bind(on_release=self.dropdown.open)
self.dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
for levels in level:
btn1 = Button(text='%r' % levels, size_hint_y=None, height=30)
btn1.bind(on_release=lambda btn1: self.dropdown1.select(btn1.text))
self.dropdown1.add_widget(btn1)
mainbutton1 = Button(text='Training Level', size_hint=(1, 1))
mainbutton1.bind(on_release=self.dropdown1.open)
self.dropdown1.bind(on_select=lambda instance, x: setattr(mainbutton1, 'text', x))
for bool in access:
btn2 = Button(text = '%r' % bool, size_hint_y=None,height=30)
btn2.bind(on_release = lambda btn2 : self.dropdown2.select(btn2.text))
self.dropdown2.add_widget(btn2)
mainbutton2 = Button(text = 'Gym Access',size_hint=(1,1))
mainbutton2.bind(on_release=self.dropdown2.open)
self.dropdown2.bind(on_select=lambda instance, x: setattr(mainbutton2,'text', x))
So this is the change to the .py file above. I left out the rest of the code, just the code for the screen that I am trying to put the dropdown menus into.
<getFitScreen>:
id: getFitScreen
top_layout: topLayoutID
dd_btn: btn_ddID
BoxLayout:
id: topLayoutID
size_hint: 1, .05
pos_hint: {'x': 0, 'y': .95}
Button:
id: btn_ddID
text: 'Time Availablity'
on_release: root.dropdown.open(self)
Button:
id: btn2_ddID2
text: 'Training Level'
on_release: root.dropdown1.open(self)
Button:
id: btn3_ddID3
text: 'Gym Access'
on_release: root.dropdown2.open(self)
Then the only change I made to the .kv file was at the very end, corresponding to the screen I wanted to make adjustments to. Basically I just had to change a couple variable names.

Related

Return value from dropdown button in kivy

I currently have a code that runs a dropdown button in kivy. I would like to send the value 1,2,3,4 into the main class when the value is selected.
Specifying chain of events:
click dropdown button
four options are displayed
select one of the four options ==> this is when the value gets sent back to the main class
dropdown button text replaced with the selection
I have tried using get_screen(), which is equivalent to get_running_app when using windows, but this was delayed. I also tried creating an external variable but this was delayed too.
Below is my code. Thanks.
'''
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
class WindowManager(ScreenManager):
pass
class Drop_down(DropDown):
pass
class MenuWindow(Screen):
General_select_button_size = (100, 100)
General_select_button_pos = (400, 400)
class GeneralWindow(Screen):
drop_size = (100,100)
drop_pos = (400,400)
xax=''
def on_touch_up(self,touch):
app = self.manager.get_screen('General')
print('use get',app.ids['btn'].text)
print('use variable',self.xax)
KV = Builder.load_string("""
WindowManager:
MenuWindow:
GeneralWindow:
<MenuWIndow>:
name: 'Menu'
RelativeLayout:
Button:
allow_stretch: True
keep_ratio: True
size_hint: None,None
id: general_select_button
text: 'move to general mode'
size: root.General_select_button_size
pos: root.General_select_button_pos
on_release:
app.root.current = "General"
root.manager.transition.direction = "left"
<GeneralWindow>:
name: 'General'
RelativeLayout:
Button:
allow_stretch: True
keep_ratio: True
size_hint: None,None
id: btn
text: 'select number'
size: root.drop_size
pos: root.drop_pos
on_parent: drop_content.dismiss()
on_release:
drop_content.open(self)
root.xax=btn.text
DropDown:
id: drop_content
on_select: btn.text = '{}'.format(args[1])
Button:
id: btn1
text: '1'
size_hint_y: None
height: 30
on_release: drop_content.select('1')
Button:
id: btn2
text:'2'
size_hint_y: None
height: 30
on_release: drop_content.select('2')
Button:
id: btn3
text: '3'
size_hint_y: None
height: 30
on_release: drop_content.select('3')
Button:
id: btn4
text: '4'
size_hint_y: None
height: 30
on_release: drop_content.select('4')
""")
class ImageChangeApp(App):
def build(self):
return KV
if __name__ == '__main__':
ImageChangeApp().run()
'''

Don't understand how to change screen in python kivy

I have spent a reasonable amount of time struggling to understand how to change the screen in python since I had to move the kv builder inside the build(self) function. I have tried using self.parent.current, app.root.current, kv.current (Which is what used to work) and self.manager.current functions and followed other stack overflow solutions such as:
Kivy: changing screen from python code
However, I get to a stage with each of these where I either have the same problem or it does not work.
I am attempting to change to the same screen no matter which button is pressed in a for loop and am still fairly new to this. Any help would be appreciated
The line of code in question is marked as:#THIS NEEDS TO CHANGE TO THE SPECIFIC FIND WINDOW
I changed some code due to adding string properties and it has thus stopped working.
main.py:
***
> import pickle
from click import command
from kivy.app import runTouchApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from datetime import datetime, date
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import StringProperty
from kivy.metrics import dp
from functools import partial
from settingsjson import settings_json
FindsFileName = "Finds.obj"
# Recipe for new find object created by instancing.
class NewFindBlueprint:
def __init__(self, Name=None, Date=None, Time=None, GPS=None, Photos=None, Description=None, Depth=None, Soil_Conditions=None, Weather_Conditions=None) -> None:
self.Name = Name
self.Date = Date
self.Time = Time
self.GPS = GPS
self.Photos = Photos
self.Description = Description
self.Depth = Depth
self.Soil_Conditions = Soil_Conditions
self.Weather_Conditions = Weather_Conditions
# The window on which the app is loaded, this can be used to help explain the purpose and use of the app.
class BaseWindow(Screen):
pass
# The base screen used for inputting a new find into the collection [base information of GPS, photos, name, date and time, as well as ony other information desired].
class InFieldFindInputWindow(Screen):
def __init__(self, **kw):
super().__init__(**kw)
# Keep for reference
#def HowToCollectDataFunction(self):
# print("val={0}".format(self.ids.NewFindName.text))
def RefreshDateAndTime(self):
pass
def AddNewFind(self):
NewFindName = self.ids.NewFindName.text
Current_Time = datetime.now()
Current_Time_HHMMSS = Current_Time.strftime("%H:%M:%S")
Current_Date = date.today()
Current_Date_DDMMYYYY=Current_Date.strftime("%d %B %y")
NewFindDate = Current_Date_DDMMYYYY
NewFindTime = Current_Time_HHMMSS
NewFindGPS = "1"
NewFindPhoto = "1"
if False:
NewFindDepth = self.ids.Depth.text
NewFindObject = NewFindBlueprint(Name=NewFindName,Date=Current_Date_DDMMYYYY,Time=Current_Time_HHMMSS)
print(NewFindObject.Name, NewFindObject.Date, NewFindObject.Time, NewFindObject.GPS)
InputFileObject = open(FindsFileName,"ab")
pickle.dump(NewFindObject,InputFileObject)
InputFileObject.close()
# Show all of the finds in a steack layout window with scroll compatibility, no matter the state.
class ViewFindsWindow(Screen):
pass
class AllFindsGridLayout(GridLayout):
FindButton={} # Create dictionary, used for holding the ids!!
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
def __init__(self, **kwargs):
super().__init__(**kwargs)
BackButton = Button(text="Back",color=(1,1,0,1))
BackButton.bind(on_release=self.BackToMainMenu)
self.add_widget(BackButton)
for Find in self.AllFinds:
self.FindButton[Find.Name] = Button(text = Find.Name, size_hint_y=None,height=dp(100))
self.add_widget(self.FindButton[Find.Name])
self.FindButton[Find.Name].bind(on_release=partial(self.ViewSpecificFindInfo, Find.Name, Find.Date, Find.Time ))
def LoadAllFinds(filename):
with open(filename, "rb") as f:
while True:
try:
yield pickle.load(f)
except EOFError:
break
AllFinds = LoadAllFinds(FindsFileName)
def ViewSpecificFindInfo(self,FindName,FindDate,FindTime,ObjectInfo):
print(FindName,FindDate,FindTime)
# First thing is code to change the info to display.
App.get_running_app().FindInfoName = FindName
App.get_running_app().FindInfoDate = FindDate
App.get_running_app().FindInfoTime = FindTime
# Secondly go to the screen to show the specific find.
#THIS NEEDS TO CHANGE TO THE SPECIFIC FIND WINDOW
# Return to Main Menu
def BackToMainMenu(self,event):
App.root_window="Base Window"
# Shows only finds which have been entered in the field but not updated at home yet. Works based on a set of standard key conditions to qualify as updated.
class AtHomeUpdateFindsWindow(Screen):
pass
# Shows the information of one specific find including images, 3D scans, name, gps coords, location in collection, etc.
class SpecificFindInfoWindow(Screen):
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
pass
# Allows to view all inputted permissions.
class PermissionsWindow(Screen):
pass
# Enables the addition of a permission zone, including name, area, phone number, date and time.
class AddPermissionsWindow(Screen):
pass
# Options window for disabling more opportunities.
class OptionsWindow(Screen):
pass
###
class WindowManager(ScreenManager):
pass
###
# Designating the design .kv file
#kv = Builder.load_file('MetalDetectorsFriend.kv')
class MetalDetectorsFriendApp(App):
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
def build(self):
pass #This loads the kv file as it must load before due to string properties
def build_config(self, config):
config.setdefaults('In Field Find Input',{
'DepthBoolean':True,
'GroundConditionBoolean':True,
'WeatherConditionsBoolean':True
})
def build_settings(self, settings):
settings.add_json_panel('In Field Find Options',self.config,data=settings_json)
if __name__ == "__main__":
MetalDetectorsFriendApp().run()
***
MetalDetectorsFriend.kv
***
> WindowManager:
BaseWindow:
InFieldFindInputWindow:
ViewFindsWindow:
AtHomeUpdateFindsWindow:
SpecificFindInfoWindow:
PermissionsWindow:
AddPermissionsWindow:
OptionsWindow:
<BaseWindow>:
name:"Base Window"
BoxLayout:
orientation:"vertical"
Label:
text:"Welcome to Metal Detectors Friend"
font_size:16
Button:
text:"Add new find"
on_release:
app.root.current = "In Field Find Input Window"
Button:
text:"View all finds"
on_release:
app.root.current = "View Finds Window"
Button:
text:"Options"
on_release:
app.open_settings()
Button:
text:"test"
on_release:
app.root.current = "Specific Find Info Window"
<InFieldFindInputWindow>:
name: "In Field Find Input Window"
BoxLayout:
orientation:"vertical"
BoxLayout:
orientation:"horizontal"
Button:
id: IFFIWBackButton
text: "Back"
on_release:
app.root.current = "Base Window"
size_hint:0.1,1
TextInput:
id: NewFindName
size_hint:0.9,1
BoxLayout:
orientation:"horizontal"
Label:
text: "Photos"
Label:
text: "Map"
Label:
text:"GPS coordinates"
TextInput:
id: Depth
disabled:True
TextInput:
id: SoilConditions
disabled:True
TextInput:
id: WeatherConditions
disabled:True
Button:
id: AddNewFindButton
text: "Add find to collection"
on_release:
root.AddNewFind()
# root.HowToCollectDataFunction() # Reference: How to use the function from Python
#app.root.current = "View Finds Window"
<ViewFindsWindow>:
name: "View Finds Window"
ScrollView:
do_scroll_x:False
do_scroll_y:True
GridLayout:
cols:1
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Button:
size_hint:0.2,1
text: "Back"
on_release:
app.root.current="Base Window"
Label:
text:"All Finds"
size_hint:0.8,1
AllFindsGridLayout:
cols: 2
padding: 10
spacing: 10
size_hint_y:None
height:self.minimum_height
##ScrollView:
##do_scroll_x:False
##do_scroll_y:True
##AllFindsGridLayout:
## cols: 2
## padding: 10
## spacing: 10
## size_hint_y:None
## height:self.minimum_height
#BoxLayout:
# orientation:"vertical"
# size_hint_y:None
# height:self.minimum_height
#BoxLayout:
# orientation:"horizontal"
# Button:
# text:"Back"
# on_release:
# app.root.current = "Base Window"
# size_hint:0.2,1
# Label:
# text:"All Finds"
# size_hint: 0.8,1
#TEST
<AtHomeUpdateFindsWindow>:
name: "At Home Update Finds Window"
<SpecificFindInfoWindow>:
name: "Specific Find Info Window"
ScrollView:
do_scroll_x:False
do_scroll_y:True
GridLayout:
cols:1
size_hint:1, None
height:self.minimum_height
#Test this:::
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"50dp"
Button:
on_release:app.root.current = "Base Window"
text:"Back"
size_hint_x:0.2
Label:
text: "View specific find"
size_hint_x:0.8
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"100dp"
Label:
id: SpecificFindInfoName
text: app.FindInfoName
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
id: SpecificFindInfoDate
text:app.FindInfoDate
Label:
id: SpecificFindInfoTime
text:app.FindInfoTime
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"400dp"
Button:
text:"Photos"
Button:
text:"Map"
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"400dp"
Button:
text:"WAYYYY YEEAH"
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"400dp"
Button:
text:"WAYYYY YEEAH"
#####
<PermissionsWindow>:
name: "Permissions Window"
<AddPermissionsWindow>:
name: "Add Permissions Window"
<OptionsWindow>:
name: "Options Window"
ScrollView:
do_scroll_x: False
do_scroll_y: True
GridLayout:
cols:1
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:self.minimum_height
Button:
text:"Back"
on_release:
app.root.current = "Base Window"
size_hint_x:0.2
Label:
text:"In field input settings"
size_hint_y:None
height:"100dp"
size_hint_x:0.8
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text:"Depth enabled"
Switch:
id:Depth_Enabled
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text:"Soil conditions enabled"
Switch:
id:Soil_Conditions_Enabled
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text: "Weather Conditions enabled"
Switch:
id: Weather_Conditions_Enabled
Thanks for any help
If I understand your question correctly, you just need to use the current attribute of the ScreenManager and the name of the desired Screen. In your ViewSpecificFindInfo() method, just add:
App.get_running_app().root.current = "Specific Find Info Window"
This code get the current running App, then gets its root (which is WindowManager, in your code), and sets its current property to the name of the desired Screen.

How to create an buttonloop with parameter in kivy, after the GUI has generated?

I have an problem with the Kivy-Libaray. I' m currently writing the front end, for a ShoppinglistApp. I have an main menu, where you can select what you want to do(add List/ Shop) from where you can get to the Shop selection.
In the shop selection Menu are 4 Shops.
all Items(Produkt) are stored in a list.
If you select the Shop, then for each Item(from this Shop) a Button should be created.
I tired this solution:
from kivy.app import App
from kivy.metrics import dp
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.stacklayout import StackLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
# the rest
class Produkt:
def __init__(self, name, amount, market):
self.name = name
self.amount = amount
self.market = market
eklist = []
for i in range(0, 100):
nn = "Banane" + str(i)
eklist.append(Produkt(nn, 0, "Baumarkt"))
# Define Screens
class FirstWindow(Screen):
pass
class SecondWindow(Screen):
pass
class ThirdWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
"""Anfang der Chose"""
class Box(BoxLayout):
pass
# Gvar is a class I created for the purpose of transporting variable(s). Especially the selected shop.
class Gvar:
def __init__(self, var1):
self.var1 = var1
def get(self):
return self.var1
gvar = Gvar("default")
# SetBox ist the Class for changing the variable through the shop selection menu.
class SetBox(BoxLayout):
def set(self, market):
gvar.var1 = market
print("Set")
print(gvar.var1)
class BLE(BoxLayout):
pass
# This is the Menus which makes Problems.
class SLE(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for p in eklist:
# In theory, this if request should always be triggerd. In praxis, I made the else Request to debug.
# for Text normally would appear p.name, but I changed it for debugging. Text shown(gvar.var1) is "default".
if p.market == str(gvar.get()):
b = Button(text=str(gvar.get()), size_hint=(None, None), color="blue", size=(dp(200), dp(100)),
on_press=lambda *args: self.test1(*args, str(p.name)))
b.prod = p
self.add_widget(b)
else:
b = Button(text=str(gvar.get()), size_hint=(None, None), color="green", size=(dp(200), dp(100)),
on_press=lambda *args: self.test1(*args, str(p.name)))
b.prod = p
self.add_widget(b)
market = None
# test 1 is a placeholder for a Backend funktion
# Here I print the Produkt and gvar.var1, to debug. Here it get for gvar.var1 the in the GUI selected Value.
def test1(self, b, i):
print("Initialize test2, please wait...")
print(b.prod.name, b.prod.amount, b.prod.market, gvar.var1)
"""Ende der Chose"""
kv = Builder.load_file('einkaufsliste.kv')
class EinkaufsApp(App):
def build(self):
return kv
if __name__ == '__main__':
EinkaufsApp().run()
+
Here is the Kivy file.
WindowManager:
FirstWindow:
SecondWindow:
ThirdWindow:
&ltFirstWindow&gt:
name: "first"
Box:
orientation: "vertical"
size :root.width, root.height
Label:
text: "Hauptmenü"
size_hint: 1, .2
font_size: 32
Button:
text: "Einkaufen"
font_size: 32
background_normal: ' '
background_color: "#666e00"
on_release:
app.root.current = "third"
root.manager.transition.direction = "left"
Button:
text: "Einkaufsliste updaten"
font_size: 32
background_normal: ' '
background_color: "#660000"
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
&ltSecondWindow&gt:
name: "second"
BLE:
id: BLE
orientation: "vertical"
Button:
text: "to first"
size_hint: 1, .1
font_size: 24
on_release:
app.root.current = "first"
root.manager.transition.direction = "right"
TextInput:
multiline: False
size_hint: 1, .1
SVE:
&ltThirdWindow&gt:
name: "third"
SetBox:
orientation: "vertical"
size :root.width, root.height
id:SetBox
Box:
orientation: "horizontal"
size_hint: 1, .4
Button:
text: "to first"
size_hint: .2, 1
font_size: 24
on_release:
app.root.current = "first"
root.manager.transition.direction = "right"
Label:
text: "Markt auswählen"
size_hint: .8, 1
font_size: 32
Button:
text: "Supermarkt"
font_size: 32
background_normal: ' '
background_color: "#a1b2e3"
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
on_press: SetBox.set("Supermarkt")
Button:
text: "Drogerie"
font_size: 32
background_normal: ' '
background_color: "#c7bbc9"
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
on_press: SetBox.set("Drogerie")
Button:
text: "Baumarkt"
font_size: 32
background_normal: ' '
background_color: "#b4eeb4"
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
on_press: SetBox.set("Baumarkt")
Button:
text: "Elektrogerätefachhandel"
font_size: 32
background_normal: ' '
background_color: "#f4c2c2"
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
on_press: SetBox.set("Elektrogerätefachhandel")
&ltSVE#ScrollView&gt:
SLE:
id: Scrollwin
size_hint: 1, None
height: self.minimum_height
&ltSLE&gt:
# orientation: "rl-bt"
# spacing: "1dp", "1dp"
</code></pre>
I think the Problem is, that kivy creates the whole GUI before I can change the variable.
Is there a way to reload the Screen or do I have to make for each market/shop another Screen?
It's possible to do what you want. You have to add the items to the corresponidng layout. I'll give you an example with a project I did some time ago:
class ExmapleApp(App):
def build(self):
#Create GridLayout where you will add and remove widgets
self.gridRooms = gridRooms = GridLayout(cols=10, padding=10, spacing=10,
row_force_default=True, row_default_height=50, size_hint_y=None)
gridRooms.bind(minimum_height=gridRooms.setter('height'))
# Generate 10 Rooms items
for i in range(10):
labl = NewLabel() #Instace of NewLabel
labl.num = str(i+1) #Number of room
labl.roomName = "Room "+str(labl.num) # Room name
labl.filename = '12916_sweet_trip_mm_kwik_mod_01.wav'
roomsList.append(labl) #This add the room to a list for further reference
gridRooms.add_widget(labl) #This add the rooms to a grid
Here's an image of how it looks so far:
Finally, you can add widgets to that GridLayout:
#Create the object you want to Add
labl = NewLabel() #Instace of NewLabel
labl.num = str(11) #Number of room
labl.roomName = "Room "+str(labl.num) # Room name
labl.filename = '12916_sweet_trip_mm_kwik_mod_01.wav'
roomsList.append(labl) #This add the room to a list for further reference
#This add the widgets 3 secs after the program starts
Clock.schedule_once(lambda x: gridRooms.add_widget(roomsList[10]), 3) #Add room 11
You can even have a callback to create a new widget everytime you press a button.
To remove a widget just:
gridRooms.remove_widget(roomsList[10]) #Removes Room 11

How do I pass an argument between KivyMD screens?

When going from the first screen to the second screen, I want to pass a variable as an argument so that kivyMD can update the second screen from text stored in an excel file. The following is a skeleton of my app's functionality:
The user reaches Screen 1 thru the navigation drawer in KivyMD, screen 1 presents the user with two options on two small clickable MDCards:
"Change text to 1"
"Change text to 2"
After clicking on one of these, the app switches to screen 2 with a single big MDCard, the text on this MDCard should change to reflect the option the user chose.
However, kivy is pulling the text that is to be displayed on the big MDCard from an excel file.
The variable that I want to pass from screen 1 to screen 2 is simply a number (1 or 2) that will tell kivy which row in the excel file it should pull the text from
If the user clicks "Change text to 1" then the first screen should pass "1" as the argument row_x to the function def change_text() (see screen 2 .py) so that the text in row 1 of excel can be displayed on the second screen. How can I achieve this?
I have 4 files in total; 3 are .py files (one for the main app, one for screen 1, and one for screen 2), and the excel file
NOTE: in the code below, Screen 1 & 2 are called Element 1 & 2 respectfully
Main.py:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from element_1 import element_1_screen
from element_2 import element_2_screen
MainNavigation = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: 'Go to Element 1'
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "go_to_element_1_screen"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "words_nav_item"
element_1_screen:
name: "go_to_element_1_screen"
element_2_screen:
name: "go_to_element_2_screen"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class mainApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Red"
return Builder.load_string(MainNavigation)
mainApp().run()
Screen 1 / Element 1
from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
element_1_contents = '''
<element_1_screen>:
MDGridLayout:
rows: 2
size: root.width, root.height
pos_hint: {"center_x": .8, "center_y": .2}
spacing: 40
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.manager.current = "go_to_element_2_screen"
MDLabel:
id: LabelTextID
text: "Change Text to 1"
halign: 'center'
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.manager.current = "go_to_element_2_screen"
MDLabel:
id: LabelTextID
text: "Change Text to 2"
halign: 'center'
'''
class element_1_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Builder.load_string(element_1_contents)
Screen 2 / Element 2
from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
import openpyxl
element_2_contents = '''
<element_2_screen>:
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "360dp"
pos_hint: {"center_x": .5, "center_y": .5}
ripple_behavior: True
focus_behavior: True
on_release: root.manager.current = "go_to_element_1_screen"
MDLabel:
id: TextID
text: "NOTHING HAS CHANGED"
halign: 'center'
MDLabel:
text: "(Click here to return)"
halign: 'center'
'''
class element_2_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
path = "data.xlsx"
self.wb_obj = openpyxl.load_workbook(path)
self.sheet_obj = self.wb_obj.active
Builder.load_string(element_2_contents)
def change_text(self, row_x=0):
row_number = self.sheet_obj.cell(row_x, column=1)
self.ids.TextID.text = str(row_number.value)
And the excel file only has two entries in Column A:
Row 1: You have chosen 1
Row 2: You have chosen 2
I found the answer and now it works flawlessly. Someone over on Reddit (u/Username_RANDINT) helped me, this is what they said:
The ScreenManager has a get_screen() method. You could use it to get
the instance of the second screen and call the change_text() method on
that. In the same place where you switch screens, add another line:
on_release:
root.manager.current = "go_to_element_2_screen"
root.manager.get_screen("go_to_element_2_screen").change_text(1)
Then the same for the other card, just pass in 2 instead of 1.

kivy spinner widget with multiple selection

I am looking for a kivy widget (preferrably in python + kv file) of type spinner (or something alike) where I can select multiple items through a checkbox for example. The selected items should become available in a tuple (?).
In the picture start.png you will find the starting situation.
In a form there is a label and a Textinput field. On click a list with available options should popup. For this I am using a Spinner widget. See picture select.png
From this list I want to select multiple items. In the example next to 'Nederlands' I have selected 'English'.
When done, the Text input field should show the selected items in a comma separated list. See picture result.png
I have tried this with e ListView using the multiple selection mode. But the ListView is bound in the Textfield area.
I have tried to put the ListView in a popup window. But this doesn't work-out either for some or other reason....
Any suggestions are highly appreciated.
Thanks in advance.
Kivy does not have such widget by default, but it is quite easy to create the custom one using Button+DropDown+ToggleButton.
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
class MultiSelectSpinner(Button):
"""Widget allowing to select multiple text options."""
dropdown = ObjectProperty(None)
"""(internal) DropDown used with MultiSelectSpinner."""
values = ListProperty([])
"""Values to choose from."""
selected_values = ListProperty([])
"""List of values selected by the user."""
def __init__(self, **kwargs):
self.bind(dropdown=self.update_dropdown)
self.bind(values=self.update_dropdown)
super(MultiSelectSpinner, self).__init__(**kwargs)
self.bind(on_release=self.toggle_dropdown)
def toggle_dropdown(self, *args):
if self.dropdown.parent:
self.dropdown.dismiss()
else:
self.dropdown.open(self)
def update_dropdown(self, *args):
if not self.dropdown:
self.dropdown = DropDown()
values = self.values
if values:
if self.dropdown.children:
self.dropdown.clear_widgets()
for value in values:
b = Factory.MultiSelectOption(text=value)
b.bind(state=self.select_value)
self.dropdown.add_widget(b)
def select_value(self, instance, value):
if value == 'down':
if instance.text not in self.selected_values:
self.selected_values.append(instance.text)
else:
if instance.text in self.selected_values:
self.selected_values.remove(instance.text)
def on_selected_values(self, instance, value):
if value:
self.text = ', '.join(value)
else:
self.text = ''
kv = '''
BoxLayout:
orientation: 'vertical'
BoxLayout:
Label:
text: 'Select city'
MultiSelectSpinner:
id: city
values: 'Sydney', 'Moscow', 'Warsaw', 'New York', 'Tokio'
BoxLayout:
Label:
text: 'Select your favorite food'
MultiSelectSpinner:
id: food
values: 'Fish and chips', 'Hot-dog', 'Hamburger'
Label:
text: 'You selected {} cities and {} as your favourite food.'.format(city.text, food.text)
<MultiSelectOption#ToggleButton>:
size_hint: 1, None
height: '48dp'
'''
runTouchApp(Builder.load_string(kv))
I get the solution.. Here is the path: In kv file first we can mention Dropdown widget, under Drop down we will mention check boxes and this is the answer.. Here is the kv file code:
DropDown:
padding: 0, 0, 0, root.width * 0.4
id: dropdown
on_select: btn.text = '{}'.format(args[1])
GridLayout:
size_hint_y: None
height: 44
cols: 2
row_default_height: '10dp'
Label:
id: person
text: 'Person'
text_size: self.size
valign: 'middle'
CheckBox:
text: 'check me'
on_active:
root.on_checkbox_active(person.text, self.active)
GridLayout:
size_hint_y: None
height: 44
cols: 2
row_default_height: '10dp'
Label:
id: vehicle
text: 'Vehicle'
text_size: self.size
valign: 'middle'
CheckBox:
id: vecle
text: 'check me'
on_active:
root.on_checkbox_active(vehicle.text, self.active)
GridLayout:
size_hint_y: None
height: 44
cols: 2
row_default_height: '10dp'
Label:
id: aircraft
text: 'Air_craft'
text_size: self.size
valign: 'middle'
CheckBox:
text: 'check me'
on_active:
root.on_checkbox_active(aircraft.text, self.active)
The .py file:
class My_class(BoxLayout):
def on_checkbox_active(checkbox_ref, name, checkbox_value):
if checkbox_value:
print('', name, 'is active')
else:
print('', name, 'is inactive')
pass

Categories

Resources