Kivy ResponsiveView and widget notifications, how to keep all views synched? - python

Trying to implement an application using ResponsiveView and having trouble both linking all the widgets and keeping all three view consistent. I began with a prototype using the backdrop view, which was fine for a mobile device, but fell short for a tablet or desktop. In the initial prototype I was able to identify the widget hierarchy of the status bar(s) and simply call them from a bound application level method. That is obviously not going to work in the ResponseView implementation, as each view may have different instances of the widgets. I expect that the best solution may be to create my own custom events, but verification or the suggestion of another better approach would be appreciated.
I would like to be able to update the right status bar to display properties about the selected items in list one. Since each of the three views may have its own instance of the status bar and lists, I need to update each for every event. Or figure out how to have a single widget for each item that can be used in each view. The implementation below will work for the tablet view, but not the mobile or desktop views. The mobile view is incomplete, as the full backdrop implementation is not present.
Below is a rather lengthy prototype that should display the problem(s):
from kivymd.app import MDApp
from kivymd.uix.list import MDList
from kivymd.uix.scrollview import ScrollView
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.uix.responsivelayout import MDResponsiveLayout
from kivymd.uix.toolbar import MDTopAppBar
from kivy.properties import StringProperty, BooleanProperty, ObjectProperty
Version = '0.1'
MainAppKV = '''
#:import NoTransition kivy.uix.screenmanager.NoTransition
#:import Window kivy.core.window.Window
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget
<NotificationBar>:
id: top_bar
title: "Title"
anchor_title: "center"
<LeftStatusBar>:
id: left_status_bar
IconLeftWidget:
id: status_icon
icon: ''
<RightStatusBar>:
id: right_status_bar
IconLeftWidget:
id: status_icon
icon: ''
<TableOneHeader>:
id: "table_one_header"
size_hint: [1, .12]
orientation: "horizontal"
Button:
id: "column_1"
text: "Column 1"
on_press: root.button_press(self)
Button:
id: "column_2"
text: "Column 2"
Button:
id: "column_3"
text: "Column 3"
<TableTwoHeader>:
id: "table_two_header"
size_hint: [1, .12]
orientation: "horizontal"
Button:
id: "column_1"
text: "Column 1"
Button:
id: "column_2"
text: "Column 2"
<ScrollViewOne>:
id: "scrollview_one"
MDSelectionList:
id: "mdselection_list"
spacing: "12dp"
on_selected: app.on_selected(*args)
on_unselected: app.on_unselected(*args)
OneLineIconListItem:
text: "List One, Item One"
OneLineIconListItem:
text: "List One, Item Two"
<ScrollViewTwo>:
id: "scrollview_two"
ListTwo:
id: "list_two"
OneLineIconListItem:
text: "List Two, Item One"
OneLineIconListItem:
text: "List Two, Item Two"
<ListOneLayout>:
id: "list_one_layout"
orientation: "vertical"
TableOneHeader:
ScrollViewOne:
<ListTwoLayout>:
id: "list_two_layout"
orientation: "vertical"
TableTwoHeader:
ScrollViewTwo:
<BackDropLayout>:
id: "backdrop_layout"
orientation: "vertical"
NotificationBar:
BoxLayout:
orientation: "horizontal"
size_hint: (1, .1)
LeftStatusBar:
size_hint: (.6, 1)
RightStatusBar:
size_hint: (.4, 1)
ListOneLayout:
<MyBackdropBackLayer#ScreenManager>
transition: NoTransition()
MDScreen:
ListOneLayout:
<SideBySideLayout>
orientation: "vertical"
NotificationBar:
BoxLayout:
orientation: "horizontal"
BoxLayout:
orientation: "vertical"
size_hint: (.5, 1)
LeftStatusBar:
ListTwoLayout:
BoxLayout:
orientation: "vertical"
size_hint: (.5, 1)
RightStatusBar:
ListOneLayout:
<MobileView>
id: "mobile_view"
name: "mobile"
MDScreen:
name: "backdrop"
BackDropLayout:
id: "mobile_layout"
<TabletView>
id: "tablet_view"
name: "tablet"
SideBySideLayout:
id: "tablet_layout"
<DesktopView>
id: "desktop_view"
name: "desktop"
SideBySideLayout:
id: "desktop_layout"
ResponsiveView:
'''
class ScrollViewOne(ScrollView):
pass
class TableOneHeader(MDBoxLayout):
pass
class TableTwoHeader(MDBoxLayout):
pass
class BackDropLayout(MDBoxLayout):
pass
class SideBySideLayout(MDBoxLayout):
pass
class MobileView(MDScreen):
pass
class TabletView(MDScreen):
pass
class DesktopView(MDScreen):
pass
class ResponsiveView(MDResponsiveLayout, MDScreen):
def __init__(self, **kw):
super().__init__(**kw)
self.mobile_view = MobileView()
self.tablet_view = TabletView()
self.desktop_view = DesktopView()
class LeftStatusBar(OneLineIconListItem):
status_text = StringProperty("Left-Status")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.text = self.status_text
class RightStatusBar(OneLineIconListItem):
in_selection = BooleanProperty(False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.text = "None selected"
def update(self, item, remove=False):
if remove:
self.text = 'None selected'
return
self.text = "update selection"
class NotificationBar(MDTopAppBar):
pass
class ListOneLayout(MDBoxLayout):
pass
class ListTwoLayout(MDBoxLayout):
pass
class ScrollViewTwo(ScrollView):
pass
class ListTwo(MDList):
pass
class MainApp(MDApp):
status_left = ObjectProperty()
status_right = ObjectProperty()
def __init__(self):
super().__init__()
self.title = "Title (v{})".format(Version)
def build(self):
self.root = Builder.load_string(MainAppKV)
self.status_right = self.root.tablet_view.children[0].children[0].children[0].children[1]
self.status_left = self.root.tablet_view.children[0].children[0].children[1].children[1]
return self.root
def on_selected(self, instance_selection_list, instance_selection_item):
print("on_selected {}".format(instance_selection_item))
self.status_right.update(0)
def on_unselected(self, instance_selection_list, instance_selection_item):
print("on_unselected {}".format(instance_selection_item))
self.status_right.update(0, True)
if __name__ == "__main__":
MainApp().run()

If you define a Property in your App class, like this:
class MainApp(MDApp):
left_status_text = StringProperty('Left-Status from App')
Then, you can use that same property in every instance of LeftStatusBar by using it in the kv:
<LeftStatusBar>:
id: left_status_bar
text: app.left_status_text # refers to Property in App
IconLeftWidget:
id: status_icon
icon: ''

Looking further into the ResponseView, I found that it isn't necessarily intended to support synchronizing the distinct views, but rather allowing a single app to display one of 3 views. The other problem that I had was to update the subordinate widgets appropriate for the selected view. To accomplish this, I added Object properties to the ResponsiveView and set them in the on_change_screen_type().
class ResponsiveView(MDResponsiveLayout, MDScreen):
active_view = ObjectProperty()
status_left = ObjectProperty()
status_right = ObjectProperty()
def on_change_screen_type(self, *args):
# first child is the Active screen
self.active_view = self.children[0]
self.status_left = self.active_view.ids.view_layout.ids.left_status
self.status_right = self.active_view.ids.view_layout.ids.right_status

Related

How to access a specific content with ids on kivy

my problem is that I am doing recycle view of async Images that have a special url. I am loading them thanks to a recycle view. My problem is that I want to print the source of the async image thanks to an app function, on another screen. All I did, didn"t work. The idea is only to recup the source of the async image and to show it on UserInfo screen. Only that. Nb : (The images are taken on an API server.)
Thanks !
The code :
class ReviewinApp(MDApp):
new_dialog = None
notdialog = None
dialog =None
dialoge = None
asyncimage = None
doc = None
data = ListProperty()
id_ = ListProperty()
def build(self):
dialog = None
self.title = "ReviewinApp"
global sm
sm = ScreenManager()
sm.add_widget(Builder.load_file("splash.kv"))
sm.add_widget(Builder.load_file("accueil.kv"))
sm.add_widget(Builder.load_file('conditions.kv'))
sm.add_widget(Builder.load_file("register.kv"))
sm.add_widget(Builder.load_file("faq.kv"))
sm.add_widget(Builder.load_file("login.kv"))
sm.add_widget(Builder.load_file("User2.kv"))
sm.add_widget(Builder.load_file("rules.kv"))
sm.add_widget(Builder.load_file("commentinput.kv"))
sm.add_widget(Builder.load_file("UserInfo.kv"))
sm.add_widget(Builder.load_file('test.kv'))
return sm
def load_products(self):
token = self.token
json = {"token":token}
res = requests.post('http://127.0.0.1:2223/products/list', json=json)
self.doc = res.json()
self.data = [{'source': 'http://127.0.0.1:2223/products/' + str(self.doc[i].replace('.png', ''))} for i in range(len(self.doc))]
def en(self):
app = App.get_running_app()
print(app.data)
```
MDScreen:
name: 'test'
md_bg_color: 41/255, 34/255, 34/255, 1
on_enter: app.load_products()
image1: ""
RecycleView:
viewclass: "OwnWidget"
data: app.data
RecycleBoxLayout:
default_size: None, dp(300)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(20)
<OwnWidget#BoxLayout>:
id: recycle
orientation: 'vertical'
text: ''
source:''
MDIconButton:
icon: 'arrow-left'
icon_color: 'white'
theme_icon_color: 'Custom'
pos_hint: {'center_x':0.3, 'center_y': 0.1}
size_hint_x: 0.3
height: dp(40)
on_press:
app.reload_datas()
app.root.current = "UserInfo"
print(product.source)
AsyncImage:
id: product
source: root.source
```
MDScreen:
name: "UserInfo"
on_enter: app.en()
MDFloatLayout:
md_bg_color: 41/255, 34/255, 34/255, 1
MDLabel:
id: test_text
text: ""
```
All you need to access objects in a different screen is the .get screen() method, the name of the screen, object id, and object property.
This family of question comes up a lot with kivy, so I have put together a single-file runnable example that you can hopefully get some ideas from.
Regarding the specific question about linking an id; the ids in the .kv code are not available directly in Python but one can make a cross-reference at the top of the kivy object. Here are 4 kivy object ids being connected to Python objects of the same name
<MainScreen>:
# python names on the left: kivy id on the right
box_layout_a: box_layout_a
box_layout_b: box_layout_b
my_button: my_button
my_text_input: my_text_input
and here is the reference to the Python objects in the class definition. I like using the type-hints because the IDE can help with auto completion and type checking.
class MainScreen(BoxLayout):
# python names on the left: kivy id on the right
box_layout_a: BoxLayout
box_layout_b: BoxLayout
my_button: Button
my_string_property = StringProperty()
Runnable code:
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from functools import partial
Builder.load_string('''
<MainScreen>:
# python names on the left: kivy id on the right
box_layout_a: box_layout_a
box_layout_b: box_layout_b
my_button: my_button
my_text_input: my_text_input
orientation: "vertical"
BoxLayout:
id: box_layout_a
orientation: "vertical"
Button:
text: "button connected to widget"
on_press: root.kv_button(1)
Button:
text: "button connected to app, increments a counter"
on_press: app.kv_app_button("method in the top level app")
BoxLayout:
id: box_layout_b
orientation: "horizontal"
Button:
id: my_button
Label:
id: my_text_input
text: root.my_string_property
Label:
text: app.string_property_bound_in_app
'''
)
class MainScreen(BoxLayout):
# python names on the left: kivy id on the right
box_layout_a: BoxLayout
box_layout_b: BoxLayout
my_button: Button
my_string_property = StringProperty()
def function_to_bind(self, my_argument, *args, **kwargs):
print(f"{self}bound function {my_argument} {args}")
def __init__(self, **kwargs):
super().__init__(**kwargs)
# often you can define the buttons and on_press method in the kivy file
# but sometimes there are a lot of buttons, and it could makse sense to create them dynamically
_late_button = Button()
_late_button.text = "added programatically"
_late_button.bind(on_press=partial(self.function_to_bind, "custom parameter sent"))
self.box_layout_a.add_widget(_late_button)
def kv_button(self, _input, *args) -> None:
print(f"{self} input {_input}, {args}")
# This is a little strange, but down here in the widget, we don't have a reference to the App object,
# however, it can be obtained
App.get_running_app().string_property_bound_in_app = "hi"
class KivyDemo(App):
string_property_bound_in_app = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.count = 0
# keep a reference to the main screen object
self.main_box_layout: MainScreen = MainScreen()
def kv_app_button(self, _input, *args) -> None:
self.count += 1
self.main_box_layout.my_button.text = f"{self.count}"
self.main_box_layout.my_string_property = f"bound string property {self.count + 1}"
print(f"{self} input: {_input}, {args}")
def build(self) -> MainScreen:
return self.main_box_layout
def on_start(self):
self.main_box_layout.my_string_property = "initial text"
self.string_property_bound_in_app = "!"
test: KivyDemo = KivyDemo()
test.run()

How to select only one button in kivy recycleview

I'm creating an mp3 player using kivy recycleview, the app has a lot of buttons in the playlist screen and whenever you click on a button, icon of that button change from 'play' to 'pause' and vice versa.
I would like to know how to make it be in a way that clicking another button changes all the other buttons icon to 'play' only that selected button should be with icon of 'pause'.
.py file:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.properties import StringProperty, ObjectProperty
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivy.uix.behaviors import ButtonBehavior
from kivy.clock import Clock
Builder.load_file('playlist.kv')
KV = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManager:
transition: FadeTransition()
Playlist:
name: "playlist screen"
"""
class Playlist(ThemableBehavior, MDScreen):
rv = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._finish_init)
def music_list(self):
return ['audio '+str(i) for i in range(1, 121)]
def _finish_init(self, dt):
self.set_list_musics()
def set_list_musics(self):
"""Builds a list of audios for the screen Playlist."""
print(self.ids)
def add_music_item(num, sura, secText, icon):
self.ids.rv.data.append(
{
"viewclass": "MusicListItem",
"number": num,
"text": sura,
"secondary_text": secText,
"icon": icon,
"callback": lambda x:x})
for i in range(len(self.music_list())):
music = self.music_list()
add_music_item(str(i+1), music[i], '00:00:00', 'play')
class MusicListItem(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDBoxLayout):
text = StringProperty()
secondary_text = StringProperty()
number = StringProperty()
icon = StringProperty()
def on_release(self, *args):
if self.icon == "play":
self.icon = "pause"
else:
self.icon = "play"
class Mp3Player(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
self.theme_cls.primary_palette = "Purple"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
if '__main__' == __name__:
Mp3Player().run()
.kv file:
#: import gch kivy.utils.get_color_from_hex
#: import StiffScrollEffect kivymd.effects.stiffscroll.StiffScrollEffect
<Playlist>
md_bg_color: gch("#5D1049")
MDGridLayout:
cols: 1
MDToolbar:
left_action_items: [["menu", lambda x: x]]
right_action_items: [["magnify", lambda x: x]]
elevation: 10
md_bg_color: 75/255, 6/255, 54/255, 1
title: 'Playlist'
pos_hint: {'top':1}
MDBoxLayout:
orientation: 'vertical'
RecycleView:
id: rv
effect_cls: 'ScrollEffect'
viewclass: 'MusicListItem'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(60)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<MusicListItem>
size_hint_y: None
padding: dp(14)
height: dp(60)
canvas:
Color:
rgba:
self.theme_cls.divider_color
Line:
points: (root.x+dp(10), root.y, root.x+self.width-dp(10)-0, root.y)
MDBoxLayout:
orientation: "horizontal"
pos_hint: {"center_x": .5, "center_y": .5}
MDBoxLayout:
orientation: 'horizontal'
MDBoxLayout:
orientation: 'vertical'
size_hint_x: .2
MDLabel:
text: root.number
font_style: "H6"
adaptive_height: True
MDLabel:
size_hint_y: .3
MDBoxLayout:
orientation: 'vertical'
MDLabel:
text: root.text
font_style: "Subtitle2"
adaptive_height: True
MDLabel:
text: root.secondary_text
font_style: "Caption"
theme_text_color: "Hint"
adaptive_height: True
MDIconButton:
icon: root.icon
Thank you
So, as I've understood, you want to set an icon as 'pause' while all other as 'play'. One way of doing this could be like, you have to reload the RecyclView data each time the icon changes.
Now to provide data with icon reference (i.e. 'play' or 'pause') I found the number property suitable, so I change it to NumericProperty. Thus number = NumericProperty().
Also this requires some change in kv,
MDLabel:
text: str(int(root.number))
font_style: "H6"
adaptive_height: True
To let Playlist know about the number reference,
def set_list_musics(self, music_no = 0):
"""Builds a list of audios for the screen Playlist."""
print(self.ids)
self.ids.rv.data = [ ] # Since you are appending data and we need to reload everytime.
Make required changes in data,
for i in range(len(self.music_list())):
new_icon = 'pause' if i+1 == music_no else 'play'
music = self.music_list()
add_music_item(str(i+1), music[i], '00:00:00', new_icon)
Now the final part, trigger the change via the button,
def on_release(self, *args):
if self.icon == "play":
self.icon = "pause"
pl = self.parent.parent.parent.parent.parent # Accessing the Playlist according to your design pattern.
pl.set_list_musics(self.number)
else:
self.icon = "play"
Note that I made this change in 'pause' icon (i.e. in if self.icon == "play"), thus you can also freely toggle this icon. Placing it otherwise could not make it possible.
Perhaps this could have been done more systematically with other design styles. I've found some issues with your design pattern. This (such as calling function in for loop repeatedly etc.) may make it a little bit slower as the data increases.

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)

Fail to get kivy widgets by ids

I made this to code to show what I want but fail to do. I get the message: "AttributeError: 'super' object has no attribute 'getattr'" when I try to acces a widget.
Someone please explain how to use widget id:s so that you can acces them from anywhere using python code, i'm sure I'm doing something fundamentally wrong.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager, SlideTransition
from kivy.lang.builder import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
import random
Builder.load_string('''
<Manager>:
id: screen_manager
SettingScreen:
id: settings
name: 'settings'
manager: 'screen_manager'
MainScreen:
id: mainscreen
name: 'mainscreen'
manager: 'screen_manager'
ThirdScreen:
id: thirdscreen
name: 'thirdscreen'
manager: 'screen_manager'
<SettingScreen>:
BoxLayout:
id: settingbox
orientation: "vertical"
TextInput:
id: getthis
text: "this is the data"
font_size: 40
size_hint: 1,0.1
Button:
text: "NEXT SCREEN"
font_size: 40
size_hint: 1,0.1
on_release:
app.root.current = "mainscreen"
app.root.transition.direction="left"
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Label:
id: changethis
text: "to be changed"
Button:
text: "Push this to make above into the text from TextInput on last screen"
on_release:
app.root.ids.changethis.text = app.root.ids.getthis.text #<<<<<
Button:
text: "Go to third Screen"
on_release:
app.root.current = "thirdscreen"
app.root.transition.direction="left"
<ThirdScreen>
put_label_here: put_label_here
BoxLayout:
orientation: 'horizontal'
Button:
text: 'Make make label'
on_release:
self.parent.parent.makelabel()
BoxLayout:
orientation: 'vertical'
id: put_label_here
''')
class MainScreen(Screen):
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
class SettingScreen(Screen):
def __init__(self, **kwargs):
super(SettingScreen, self).__init__(**kwargs)
class ThirdScreen(Screen):
def __init__(self, **kwargs):
super(ThirdScreen, self).__init__(**kwargs)
def makelabel(self): #this should make a Label with text from TextInput on SettingScreen
print('Runs function makelabel')
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
self.put_label_here.add_widget(thelabel)
class Manager(ScreenManager):
pass
sm = ScreenManager(transition=SlideTransition())
sm.add_widget(MainScreen(name='mainscreen'))
sm.add_widget(SettingScreen(name='settings'))
sm.add_widget(SettingScreen(name='thirdscreen'))
class testApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
testApp().run()
Well, I can fix this for you, but honestly I don't think you are making the right use of kv templates. But it will work anyway.
A new template is a rule by itself, it has its own root, so in return the application root (app.root) won't have direct access to children's ids defined inside their own templates. So you have to reach its direct child which makes a template containing the child you want to access then you reach that child by its id.
For instance, in your kv code, you will need to change line 63 (kv) from:
app.root.ids.changethis.text = app.root.ids.getthis.text
to:
app.root.ids.mainscreen.ids.changethis.text = app.root.ids.settings.ids.getthis.text
and line 99 (python) from:
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
to:
thelabel=Label(text=self.parent.ids.settings.ids.getthis.text)
Why shouldn't root (or any parent widget) access the ids defined inside separate templates ?
Templates, or Dynamic classes are made for re-usability, just like any none-static class.
Consider the following scenario:
BoxLayout:
CustomWidget:
id: wdg1
CustomWidget:
id: wdg2
Button:
text: 'change text of first label'
on_press: root.ids.lbl.text = 'new text'
<CustomWidget#Widget>:
Label:
text: 'some text'
id: lbl
Now The root widget have two instances of CustomWidget as children, thus two children Labels having the id 'lbl'. Which one should be called if the root called lbl directly?
To handle this, you can call lbl from its direct template instance. Thus: root.ids.wdg1.ids.lbl.text for the label inside the first widget, or root.ids.wdg2.ids.lbl.text for the label inside the second one.

How to reference objects in other screens using kivy screenmanager

I am trying to update a field that exists in another screen but am not succeeding.
I would be very very pleased when someone could tell me what I am doing wrong here.
myscreenskv.py:
style = r'''
# File: myscreenskv.py
#: import myscreens myscreens
<ScreenManagement>:
MainScreen:
Screen1:
Screen2:
<MainScreen>:
name: 'main'
mainlog:mainlog
id: scrmain
BoxLayout:
orientation: "vertical"
Label:
text: "Main"
Label:
id: mainlog
Button:
text: "go to screen 1"
on_press:
app.root.current = "screen1"
root.action1()
Button:
text: "go to screen 2"
on_press:
app.root.current = "screen2"
root.action2()
<Screen1>:
name: 'screen1'
sc1log:sc1log
id: scr1
BoxLayout:
orientation: "vertical"
Label:
text: "Screen1"
Label:
id: sc1log
Button:
text: "go to main screen"
on_press: app.root.current = "main"
Button:
text: "go to screen 2"
on_press: app.root.current = "screen2"
<Screen2>:
name: 'screen2'
id: scr2
sc2log:sc2log
BoxLayout:
orientation: "vertical"
Label:
text: "Screen2"
Label:
id: sc2log
Button:
text: "go to main screen"
on_press: app.root.current = "main"
Button:
text: "go to screen 1"
on_press: app.root.current = "screen1"
'''
.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from myscreenskv import style
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def action1(self):
self.ids.scr1.sc1log.text = 'Coming from main'
def action2(self):
self.ids.scr2.sc2log.text = 'Coming from main'
class Screen1(Screen):
def __init__(self, **kwargs):
super(Screen1, self).__init__(**kwargs)
def action1(self):
self.ids.main.mainlog.text = 'Coming from screen1'
def action2(self):
self.ids.scr2.sc2log.text = 'Coming from screen1'
class Screen2(Screen):
def __init__(self, **kwargs):
super(Screen2, self).__init__(**kwargs)
def action1(self):
self.ids.main.mainlog.text = 'Coming from screen2'
def action2(self):
self.ids.scr1.sc1log.text = 'Coming from screen2'
class MyscreensApp(App):
def build(self):
Builder.load_string(style)
sm = ScreenManager()
sm.add_widget(MainScreen())
sm.add_widget(Screen1())
sm.add_widget(Screen2())
sm.current = 'main'
return sm
if __name__ == '__main__':
MyscreensApp().run()
You are trying to access ids dictionary, that's nice, yet in a completely different instance, that's why this error:
AttributeError: 'super' object has no attribute '__getattr__'
You need to access the right instance, to be able to access its properties, which in your case you need to access the ScreenManager, to access its screens property (a list of instances), from which you can do the desired edits of for example text:
MainScreen.action1():
self.manager.screens[1].sc1log.text = 'Coming from main'
# no ids, because you put it into a variable before
To understand why it works let's look at the widget tree:
<MainScreen>:
id: scrmain
BoxLayout:
Label:
Label:
id: mainlog
Button:
Button:
here the ids are a dictionary in MainScreen accessible from MainScreen().ids(an instance) and this is the output:
{'mainlog': <WeakProxy to <kivy.uix.label.Label object at 0x12345678>>}
which means you can't really assign the root widget to its own dictionary - at least not this way + makes no sense anyway, because you can just call root, which gives you the instance of the root widget.

Categories