Kivy ResponsiveView and widget notifications, how to keep all views synched?
Question:
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()
Answers:
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
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