Changing widget from another screen in Kivy - python

I'm trying to update one widget in through a button in another view at Kivy.
Kivy code:
<MainScreen>:
relatorios : relatorios
despesas : despesas
GridLayout:
size: root.width, root.height
cols : 1
Button:
id: relatorios
text: 'Relatorios'
on_press: root.change_screen(rep = 'Report')
Button:
id: despesas
text: 'Despesa'
on_press: root.change_screen(rep = 'Expend')
<ReportScreen>:
GridLayout:
size: root.width, root.height
cols : 1
Button:
id: semanal
text: 'Semanal'
Button:
id: mensal
text: 'Mensal'
Button:
id: anual
text: 'Anual'
Button:
id: voltar
text: 'Voltar'
on_press: root.change_screen(rep = 'Main')
<ExpendScreen>:
kind_selec1 : kind_select
GridLayout:
size: root.width, root.height
cols : 2
Label:
id: kind
text: 'Tipo de Despesa'
Button:
id: kind_select
text: 'Selecionar'
on_press: root.change_screen(rep = 'Kind')
Label:
id: way
text: 'Meio de Pagamento'
Button:
id: selec_way
text: 'Selecionar'
on_press: root.change_screen(rep = 'Way')
Label:
id: parcelas
text: 'Quantidade de Parcelas'
TextInput:
id: qtdy
Label:
id: value
text: 'Valor Total'
TextInput:
id: qtdy
Button:
id: back
text: 'Voltar'
on_press: root.change_screen(rep = 'Main')
Button:
id: submit
text: 'Registrar'
on_press: root.change_name()
<DropDown1>:
cols : 1
orientation: 'vertical'
<DropDown2>:
cols : 1
orientation: 'vertical'
Python code:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from functools import partial
from functions import Expense
from kivy.properties import ObjectProperty
import kivy
import time
class MainScreen(Widget):
relatorios = ObjectProperty(None)
despesas = ObjectProperty(None)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
return
class ReportScreen(Widget):
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
return
class ExpendScreen(Widget):
kind_selec1 = ObjectProperty()
def __init__(self, **kwargs):
super(ExpendScreen, self).__init__(**kwargs)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
def change_name(self):
self.kind_selec1.text= 'porra'
class Dropdown1(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown1, self).__init__(**kwargs)
list = ['Alimentação', 'Transporte', 'Entretenimento','Moradia','Doação']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It,arg = item))
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
ExpendScreen.change_name(ExpendScreen)
class Dropdown2(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown2, self).__init__(**kwargs)
list = ['Crédito', 'Débito', 'Dinheiro']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It, arg=item))
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainscreen = MainScreen()
screen = Screen(name='Main')
screen.add_widget(self.mainscreen)
self.screen_manager.add_widget(screen)
self.reportscreen = ReportScreen()
screen = Screen(name='Report')
screen.add_widget(self.reportscreen)
self.screen_manager.add_widget(screen)
self.expendscreen = ExpendScreen()
screen = Screen(name='Expend')
screen.add_widget(self.expendscreen)
self.screen_manager.add_widget(screen)
self.dropdown1 = Dropdown1()
screen = Screen(name='Kind')
screen.add_widget(self.dropdown1)
self.screen_manager.add_widget(screen)
self.dropdown2 = Dropdown2()
screen = Screen(name='Way')
screen.add_widget(self.dropdown2)
self.screen_manager.add_widget(screen)
return self.screen_manager
def change_screen():
app_finan_con.screen_manager.current ='Report'
return
if __name__ == '__main__':
app_finan_con = MyApp()
app_finan_con.run()
The idea is to use screen Dropdown1 to modify screen ExpendScreen through this code:
class Dropdown1(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown1, self).__init__(**kwargs)
list = ['Alimentação', 'Transporte', 'Entretenimento','Moradia','Doação']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It,arg = item)) (this line activates It method)
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
ExpendScreen.change_name(ExpendScreen) (this line activates method in ExpendScreen):
class ExpendScreen(Widget):
kind_selec1 = ObjectProperty()
def __init__(self, **kwargs):
super(ExpendScreen, self).__init__(**kwargs)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
def change_name(self):
self.kind_selec1.text= 'porra'
but I get the following error: AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
but when I call the same method from a button in the same screen (ExpendScreen) it works as expected, kivy code:
Button:
id: submit
text: 'Registrar'
on_press: root.change_name()
Image examples:
Can anyone help me?

Your line of code:
ExpendScreen.change_name(ExpendScreen)
is calling an instance method of ExpendScreen without an instance. That won't work. You need to get the instance of ExpendScreen that is part of your gui, and call its change_name method. I haven't tested this, but I think replacing the above line with:
app_finan_con.expendscreen.change_name()
will do what you want.

Related

Get Values between Kivy screens on button click

I have an Kivy app that has 2 screens. Screen 1 (ProblemWindow) will get the user input and screen 2 (StepsWindow) will show some images based on screen1 input. However, I need to pass one of the values (Spinner id: problem_id) from screen 1 (ProblemWindow) into screen 2 (StepsWindow) and also use the value in the python file for additional logic.
I can pass the value between the screens but I am not able to use it in python. I need to get the updated Label Text of the StepsWindow into my python code every time I change it in my ProblemWindow and press the button "ShowSteps".
Could anyone please guide me how to achieve this?
This is my python code:
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.ids.click_label.text = f'selected problem: {value}'
return value
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
This is my kv file for reference:
WindowManager:
id: window_manager
ProblemWindow:
id: pw
StepsWindow:
id: sw
<ProblemWindow>:
name: "problem_window"
GridLayout:
rows: 4
Label:
text: "TEST"
font_size: 24
Label:
id: click_label
text: "Select a problem"
Spinner:
id: problem_id
text: "Problems List (Click here)"
font_size: 24
values: ["Problem_1", "Problem_2"]
on_text: root.selected_problem(problem_id.text)
size_hint: 0.1, 0.1
width: 300
Button:
text: "Show Steps"
font_size: 28
size_hint: 0.2, 0.2
pos_hint: {"center_x": 0.5, "center_y": 0.5}
on_release:
app.root.current = "steps_window"
root.manager.transition.direction = "left"
<StepsWindow>:
name: "steps_window"
GridLayout:
rows: 3
size: root.width, root.height
Label:
id: problem_name
text: root.manager.ids.pw.ids.problem_id.text
font_size: '42'
size_hint_y: None
height: 50
Button:
text: "Back"
on_release:
app.root.current = "problem_window"
root.manager.transition.direction = 'right'
Something like this?
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.value = value
self.ids.click_label.text = f'selected problem: {value}'
return value
#logic here
def logic_here(self):
if self.value == "Problem_1":
print(f'The solution for "Problem_1" is:')
else:
print(f'The solution for "Problem_2" is:')
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
And for the <ProblemWindow> button in the .kv file add this:
root.logic_here()

How can display data from the database to kivy table

I want to display a table in one of the screen and the displayed data in the table should come from the loan database, loan table.
I have created the database and also there is no error which is being shown. The table is created but there is no data displayed on it.Its empty table.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
from loandatabase import Database
database = Database()
Builder.load_file('design.kv')
class MainScreen(Screen):
def goto_admin_page(self):
self.manager.current = "adminfirst_screen"
def goto_agent_page(self):
pass
def goto_customer_page(self):
pass
class AdminScreenFirst(Screen):
def go_to_adminsecond(self,uname,pword):
with open("users.json") as file:
users =json.load(file)
if uname in users and users[uname]['password'] == pword:
self.manager.current = "adminsecond_screen"
else:
self.ids.login_wrong.text = "Invalid Credentials.Please Contact the administrator"
class AdminScreenSecond(Screen):
def go_to_adminpending(self):
self.manager.current = "adminPending_screen"
def go_to_adminapproved(self):
self.manager.current = "adminsecond_screen"
def go_to_adminrejected(self):
self.manager.current = "adminsecond_screen"
class AdminPendingScreen(Screen):
def display(self):
self.manager.current = "rv_screen"
class RV(Screen):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
for row in database.view():
for col in row:
self.data_items.append(col)
class TextInputPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
def __init__(self, obj, **kwargs):
super(TextInputPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = obj.text
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
def on_press(self):
popup = TextInputPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
MainApp().run()
my kivy code :
<MainScreen>:
GridLayout:
cols:1
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Login"
font_size:"20sp"
Button:
text:"Admin"
on_press : root.goto_admin_page()
Button:
text:"Agent"
on_press : root.goto_agent_page()
Button:
text: "Customer"
on_press:root.goto_customer_page()
<AdminScreenFirst>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Admin Login"
font_size:"20sp"
TextInput:
id:username
hint_text:"Username"
TextInput:
id:password
password:True
hint_text:"Password"
RelativeLayout:
Button:
text:"Login"
on_press : root.go_to_adminsecond(root.ids.username.text,root.ids.password.text)
size_hint :0.3,0.5
pos_hint: {'center_x' : 0.5 , 'center_y' : 0.6}
Label:
id:login_wrong
text:""
<AdminScreenSecond>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loan"
font_size:"20sp"
Button:
text:"Pending Request"
on_press:root.go_to_adminpending()
Button:
text:"Approved"
on_press: root.go_to_adminapproved()
Button:
text:"Rejected"
on_press: root.go_to_adminrejected()
<AdminPendingScreen>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loans"
Button:
text:"Display"
on_press: root.display()
Label:
id:display
text:""
<TextInputPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
TextInput:
id: txtinput
text: root.obj_text
Button:
size_hint: 1, 0.2
text: "Save Changes"
on_release:
root.obj.update_changes(txtinput.text)
root.dismiss()
Button:
size_hint: 1, 0.2
text: "Cancel Changes"
on_release: root.dismiss()
<RV>:
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 11
Label:
text: "LoanID"
Label:
text: "Name"
Label:
text:"Tenure"
Label:
text:"Balance"
Label:
text:"LoanType"
Label:
text:"InterestType"
Label:
text:"Interest % p.a."
Label:
text:"Security"
Label:
text:"Total"
Label:
text:"EMI"
Label:
text:"Request"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_items]
SelectableRecycleGridLayout:
cols: 11
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
#orientation: 'vertical'
multiselect: True
touch_multiselect: True
<RootWidget>:
MainScreen:
name:"mainfirst_screen"
AdminScreenFirst:
name:"adminfirst_screen"
AdminScreenSecond:
name:"adminsecond_screen"
AdminPendingScreen:
name:"adminPending_screen"
RV:
name:"rv_screen"
my database code:
import sqlite3
class Database:
def __init__(self):
self.conn = sqlite3.connect("loandatabase.db")
self.cur = self.conn.cursor()
self.cur.execute("CREATE TABLE IF NOT EXISTS loan (id INTEGER PRIMARY KEY, customer_name TEXT, tenure integer, balance integer, loantype TEXT, interesttype TEXT,interest integer,security TEXT,totalpayment integer,emi integer,instruction TEXT)")
self.conn.commit()
def insert(self,cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr):
self.cur.execute("INSERT INTO loan VALUES (NULL,?,?,?,?,?,?,?,?,?,?)",(cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr))
self.conn.commit()
def view(self):
self.cur.execute("SELECT * FROM loan")
rows = self.cur.fetchall()
return rows
def __del__(self):
self.conn.close()
[![enter image description here][1]][1]
I would request you to help me with it. Thank You in Advance
I am using Kivy,Python,sqlite3
This is displayed:
[1]: https://i.stack.imgur.com/fkUvS.png
Try this:
class MainApp(App):
title = 'Title of the app'
def build(self):
sm = ScreenManager('''You can add transition here''')
sm.add_widget(MainScreen(name='main'))
sm.add_widget(AdminScreenFirst(name='admin1'))
sm.add_widget(AdminScreenSecond(name='admin2'))
sm.add_widget(AdminPendingScreen(name='pending'))
sm.add_widget(RV(name='rv'))
return sm

How to refresh GridLayout in Kivy with kv file

I need help.
I created a small mobile application with Kivy.
I have two screens: ScreenList and ScreenDetail.
However the screen(ScreenList) containing GridLayout does not refresh
ScreenList: contains a list of items
ScreenDetail: Contains the details of a single item.
How the app works:
When I click on the first item on button 1
I go to the details of the item.
I modify the second field. I replace the text: Firt element for First and update data
After recording, I redirect the application to the screens which contain (ScreenList) the list of elements.
But the list of elements remains unchanged then the data has been modified in the database.
6.And when I return to the screen (ScreenDetail) which contains the details, there I see that the data is updated.
How can I refresh the item list in ScreenList?
Here are the pictures as an example
List before update
before update
after update
List after update
Here is the python code:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, StringProperty
from kivy.lang import Builder
from kivymd.uix.picker import MDTimePicker
from kivymd.uix.picker import MDDatePicker
from kivymd.app import MDApp
import sqlite3
import os.path
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(BASE_DIR, "donnee/ProjetMaison.db")
def donnee_dic(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
cur_id = None
class ScreenList(Screen):
data_grid = ObjectProperty()
def go_to_detail(self, instance):
global cur_id
cur_id = int(instance.text)
self.manager.current = 'screen_detail'
def __init__(self, **kwargs):
super(ScreenList, self).__init__(**kwargs)
con_list_course = sqlite3.connect(db_path)
con_list_course.row_factory = donnee_dic
curss_list_course = con_list_course.cursor()
data_list_course = curss_list_course.execute("select * FROM courses")
self.data_grid.bind(minimum_height=self.data_grid.setter('height'))
for row in data_list_course:
template_screen = GridLayout(cols=2, size_hint_y=None, height=40)
template_screen.add_widget(Label(text=row['nom']))
template_screen.add_widget(Button(text=str(row['id']), on_press=self.go_to_detail))
self.data_grid.add_widget(template_screen)
con_list_course.close()
def Maj_colonne(id, nom):
try:
sqliteConnection = sqlite3.connect(db_path)
cursor = sqliteConnection.cursor()
sqlite_update_query = """Update courses set nom = ? where id = ?"""
columnValues = (nom, id)
cursor.execute(sqlite_update_query, columnValues)
sqliteConnection.commit()
sqliteConnection.commit()
cursor.close()
except sqlite3.Error as error:
print("Erreur de connexion", error)
finally:
if sqliteConnection:
sqliteConnection.close()
class ScreenDetail(Screen):
label_id = ObjectProperty()
label_nom = ObjectProperty()
def __init__(self, **kwargs):
super(ScreenDetail, self).__init__(**kwargs)
def on_enter(self):
global cur_id
conn = sqlite3.connect(db_path)
cursor = conn.execute("select * FROM courses where id=?",str(cur_id))
for row in cursor:
self.label_id.text = str(row[0])
self.label_nom.text = str(row[1])
cursor.close()
def update_course(self):
id_pk = self.ids['label_id'].text
nom = self.ids['label_nom'].text
if id_pk:
Maj_colonne(str(id_pk), str(nom))
self.ids['label_id'].text = ''
self.ids['label_nom'].text = ''
self.manager.current = 'screen_list'
class Listapp(MDApp):
def build(self):
screenmanager = ScreenManager()
screenmanager.add_widget(ScreenList(name='screen_list'))
screenmanager.add_widget(ScreenDetail(name='screen_detail'))
return screenmanager
if __name__ == '__main__':
Listapp().run()
Here is the kv code:
#:import utils kivy.utils
<ScreenList>:
data_grid: data_grid
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
radius: [25, 0, 0, 0]
ScrollView:
MDGridLayout:
id: data_grid
cols: 1
spacing:10
size_hint_y:None
<ScreenDetail>:
label_id: label_id
label_nom: label_nom
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
ScrollView:
GridLayout:
id: detail_grid
cols:2
Label:
text: 'Numéro:'
bold: True
TextInput:
id: label_id
text: ''
Label:
text: 'Nom:'
bold: True
TextInput:
id: label_nom
text: ''
Button:
text: 'OK'
size_hint_y: None
on_press: root.update_course()
Thank you
Few notes to take, in general, when working with Kivy
When you're trying to share data in between screens, it's often useful to use app methods instead of specific methods of screens.
And when you need to create lots of buttons, maybe inside a loop, and bind methods on its events( on_press, on_release), it's often bad to create button instances on the fly and bind methods on its events because you'll need to do extra work to make sure that those bound methods are called with right parameters when events are fired. Rather create a custom class template and use that instead.
Working solution for your problem (only showing sections that has been added/updated
Created custom GridLayout:
class MyGrid(GridLayout):
pass
Updated __init__ method inside ScreenList:
def __init__(self, **kwargs):
#...
app = MDApp.get_running_app()
for row in data_list_course:
template_screen = MyGrid()
template_screen.ids.lbl.text = row['nom']
template_screen.ids.btn.text = str(row['id'])
self.data_grid.add_widget(template_screen)
Added methods inside app class:
class Listapp(MDApp):
def build(self):
self.screenmanager = ScreenManager()
self.screenmanager.add_widget(ScreenList(name='screen_list'))
self.screenmanager.add_widget(ScreenDetail(name='screen_detail'))
return self.screenmanager
def go_to_detail_(self, inst):
self.inst = inst
self.screenmanager.current = 'screen_detail'
def update_course_(self, label_id, label_nom):
c = self.inst.children[::-1]
c[0].text = label_nom.text
c[1].text = label_id.text
print(label_id.text, label_nom.text)
if label_id.text:
Maj_colonne(str(id_pk), str(nom))
label_nom.text = ''
label_id.text = ''
self.screenmanager.current = 'screen_list'
Here's the updated kv code:
#:import utils kivy.utils
<ScreenList>:
data_grid: data_grid
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
radius: [25, 0, 0, 0]
ScrollView:
MDGridLayout:
id: data_grid
cols: 1
spacing:10
size_hint_y:None
<ScreenDetail>:
label_id: label_id
label_nom: label_nom
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
ScrollView:
GridLayout:
id: detail_grid
cols:2
Label:
text: 'Numéro:'
bold: True
TextInput:
id: label_id
text: ''
Label:
text: 'Nom:'
bold: True
TextInput:
id: label_nom
text: ''
Button:
text: 'OK'
size_hint_y: None
on_press: app.update_course_(label_id, label_nom)
<MyGrid>:
cols:2
size_hint_y:None
height:40
Label:
id: lbl
text: ''
Button:
id: btn
text: ''
on_press:
app.go_to_detail_(root)

Kivy: How to add widgets dinamically in kv, and how to refer to them from py script?

In a previous question, I asked how to have a row of text inputs which is dinamically added on press of a button, all from a py script.
I am now attempting at moving all my layout code to a kv file. While this was pretty straightforward for the widgets which appear on screen by default, I am not really sure of how to define the dinamically added text inputs from the kv file.
My solution, at present, is to create the 'default' widgets in the kv file, and to add the other ones from the py script through the addIngredient method. Below a minimal working version.
The kv file:
WMan:
AddWindow:
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
ing1: ing1
quant1: quant1
addIng: addIng
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: self.minimum_height
TextInput:
id: quant1
multiline: False
size_hint: .25, None
height: self.minimum_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: ing1.height
on_release: root.addIngredient(self)
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
The py script reads:
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def addIngredient(self, instance):
tmp_children_list = self.ingsGrid.children[:]
self.ingsGrid.clear_widgets()
# range(start,stop[, step])
for index in range(len(tmp_children_list)-1, -1, -1):
# the last item, then last-1, etc
child = tmp_children_list[index]
# add the last first (new ones will be added on top)
self.ingsGrid.add_widget(child)
# if child is the pressed button
if child == instance:
self.ing = TextInput(
size_hint=(.65, None),
height='30sp')
self.ingsGrid.add_widget(self.ing)
self.ingsList[self.i] = self.ing
self.quant = TextInput(
size_hint=(0.25, None),
height='30sp')
self.ingsGrid.add_widget(self.quant)
self.quants[self.i] = self.quant
self.i += 1
self.addNext = Button(
text="+",
size_hint=(0.1, None),
height='30sp')
self.addNext.bind(on_press=self.addIngredient)
self.ingsGrid.add_widget(self.addNext)
def saveRec(self, instance): # grab all inputs and send to SQLite db
print(self.ingsList)
print(self.ingsList[0].text)
print(self.ingsList[1].text)
class WMan(ScreenManager):
pass
kv = Builder.load_file("test.kv")
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
My problem here is twofold: first, while this way of dinamically adding rows works as it should, it is to me a bit messy to have half of the layout defined on the kv file, and the other half defined in the py script. So my first question is:
1. Is there a way to move the entire layout to the kv file?
Second questions is:
2. How do I access the content of textInput 'ing1' (the one created in the kv file)?
when I run print(self.ingsList), I get:
{0: <ObjectProperty name=>, 1: <kivy.uix.textinput.TextInput object at 0x000002077FB89C88>}
So while I can easily do print(self.ingsList[1].text), running print(self.ingsList[0].text) will give error:
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
Here is a modified version of your code that does what I think you want:
from kivy.app import App
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def saveRec(self, instance): # grab all inputs and send to SQLite db
for child in self.ids.ingsGrid.children:
if isinstance(child, OneRow):
print('ingedient name:', child.ids.ing1.text)
print('quantity:', child.ids.quant1.text)
print('')
class WMan(ScreenManager):
pass
class OneRow(BoxLayout):
inst_count = NumericProperty(0)
count = 0
def __init__(self, **kwargs):
OneRow.count += 1
self.inst_count = OneRow.count
super(OneRow, self).__init__(**kwargs)
def get_index(self):
par = self.parent
if par is None:
return None
index = 0
for index in range(len(par.children) - 1, -1, -1):
child = par.children[index]
if child == self:
return index
kv_str = '''
#:import Factory kivy.factory.Factory
WMan:
AddWindow:
id: add
#:set row_height 30
<OneRow>:
orientation: 'horizontal'
size_hint_y: None
height: row_height
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: row_height
TextInput:
id: quant1
multiline: False
text: str(root.inst_count)
size_hint: .25, None
height: row_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: row_height
on_release: app.root.ids.add.ids.ingsGrid.add_widget(Factory.OneRow(), index=root.get_index())
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
OneRow:
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
'''
# kv = Builder.load_file("test.kv")
kv = Builder.load_string(kv_str)
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
I used Builder.load_string() instead of load_file() just for my own convenience.
I have created a class named OneRow that holds a single row from the ingredients list, and the + Button now just adds an instance of that class. The get_index() method of that class is only used to position new ingredients below the row with the Button that was pressed. And the other code in that class is just to add some identifying info. If those things are not important to you, you can eliminate the OneRow class definition from the python, and just replace <OneRow>: in the kv with <OneRow#BoxLayout>: and where the OneRow is added you can just set index=1.

Using a spinner and button together to change screens

I found this code that showed me how to use a spinner to change screens. It works well in allowing me to switch screens, but I found that I also need to switch screens by using buttons on an individual screen.
Here is an example of what I mean:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
KV = """
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Edit'
on_press: root.goEdit()
Label:
text: 'Main Screen'
<HelpScreen>:
Label:
text: 'Help Screen'
<SettingsScreen>:
Label:
text: 'Settings Screen'
<EditScreen>:
name: 'edit_Screen'
text: 'Edit Screen'
<ScreenMenu>:
text: 'main'
values: ('main', 'help', 'settings')
size_hint: None, None
size: 200, 44
"""
class MainScreen(FloatLayout):
def goEdit(self):
MyApp.build.screen_layout.remove_widget(MyApp.screen)
screen = EditScreen()
MyApp.screen = screen
MyApp.screen_layout.add_widget(MyApp.screen)
class HelpScreen(FloatLayout):
pass
class SettingsScreen(FloatLayout):
pass
class EditScreen(FloatLayout):
pass
class ScreenMenu(Spinner):
pass
class MyApp(App):
def build(self):
Builder.load_string(KV)
self.screen = None
self.root = FloatLayout()
self.screen_layout = FloatLayout()
self.menu = ScreenMenu()
self.root.add_widget(self.screen_layout)
self.root.add_widget(self.menu)
self.menu.bind(text=self.select_screen)
self.show('main')
return self.root
def select_screen(self, *args):
self.show(self.menu.text)
def show(self, name='main'):
if self.screen is not None:
self.screen_layout.remove_widget(self.screen)
self.screen = None
if name == 'main':
screen = MainScreen()
elif name == 'help':
screen = HelpScreen()
elif name == 'settings':
screen = SettingsScreen()
else:
raise Exception('Invalid screen name')
self.screen = screen
self.screen_layout.add_widget(screen)
if __name__ == "__main__":
MyApp().run()
As you can see, this is almost the exact code as found in the link above, the only difference is that here, I create an "Edit" button that when clicked, refers to the goEdit() method of the "MainScreen" class.
on_press: root.goEdit()
My problem is I don't know how to create the goEdit() method so that, when called, it goes to the "EditScreen" while also having the spinner work ( it goes to the "MainScreen", "HelpScreen" and "SettingScreen" screens). The code I tried in goEdit() clearly doesn't work.
I have also tried changing the inherited classes from FloatLayout to Screen:
class MainScreen(Screen):
def goEdit(self):
self.parent.current = 'edit_Screen'
class HelpScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class EditScreen(Screen):
pass
class ScreenMenu(Spinner):
pass
Here I tried to switch screens using this code:
self.parent.current = 'edit_Screen'
but when clicking the 'edit' button, nothing happens, I don't even get error messages.
Essentially what I want is for the spinner to work as it does in the example link, but I also need the 'edit' button to also change screens. Any help would be greatly appreciated.
Solution
Please refer to the explanations, example and output for details.
kv String
Replace root.edit() with app.show(name='edit')
Replace name: 'edit_screen' with Label:
Indent text: 'Edit Screen'
Python Script
Replace all the codings in MainScreen with pass
Add elif name == 'edit': screen = EditScreen() statement in method show
Example
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
KV = """
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Edit'
on_press: app.show(name='edit')
Label:
text: 'Main Screen'
<HelpScreen>:
Label:
text: 'Help Screen'
<SettingsScreen>:
Label:
text: 'Settings Screen'
<EditScreen>:
Label:
text: 'Edit Screen'
<ScreenMenu>:
text: 'main'
values: ('main', 'help', 'settings')
size_hint: None, None
size: 200, 44
"""
class MainScreen(FloatLayout):
pass
class HelpScreen(FloatLayout):
pass
class SettingsScreen(FloatLayout):
pass
class EditScreen(FloatLayout):
pass
class ScreenMenu(Spinner):
pass
class MyApp(App):
def build(self):
Builder.load_string(KV)
self.screen = None
self.root = FloatLayout()
self.screen_layout = FloatLayout()
self.menu = ScreenMenu()
self.root.add_widget(self.screen_layout)
self.root.add_widget(self.menu)
self.menu.bind(text=self.select_screen)
self.show('main')
return self.root
def select_screen(self, *args):
self.show(self.menu.text)
def show(self, name='main'):
if self.screen is not None:
self.screen_layout.remove_widget(self.screen)
self.screen = None
if name == 'main':
screen = MainScreen()
elif name == 'help':
screen = HelpScreen()
elif name == 'settings':
screen = SettingsScreen()
elif name == 'edit':
screen = EditScreen()
else:
raise Exception('Invalid screen name')
self.screen = screen
self.screen_layout.add_widget(screen)
if __name__ == "__main__":
MyApp().run()
Output
Here is a very simple example using the ScreenManager:
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class Screen3(Screen):
pass
class MyScreenManager(ScreenManager):
def changescreen(self, value):
try:
if value!='Choose a Value...':
self.current = value
except:
print('No screen named ' + value)
#Main application
class TestApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
TestApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
Screen2:
name: 'screen2'
Screen3:
name: 'screen3'
<Screen1>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 2'
on_press: root.manager.current = 'screen2'
Button:
text: 'Go to Screen 3'
on_press: root.manager.current = 'screen3'
Spinner:
text: 'Choose a Value...'
values: ['screen2', 'screen3']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
Label:
text: 'You are on ' + root.name
<Screen2>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 1'
on_press: root.manager.current = 'screen1'
Button:
text: 'Go to Screen 3'
on_press: root.manager.current = 'screen3'
Spinner:
text: 'Choose a Value...'
values: ['screen1', 'screen3']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
Label:
text: 'You are on ' + root.name
<Screen3>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 1'
on_press: root.manager.current = 'screen1'
Button:
text: 'Go to Screen 2'
on_press: root.manager.current = 'screen2'
Spinner:
text: 'Choose a Value...'
values: ['screen1', 'screen2']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
Label:
text: 'You are on ' + root.name

Categories