Added SimpleListAdapter to be the bare-bones, no selection, list adapter. ListAdapter now uses SimpleListAdapter if no adapter is provided. ListAdapter implements SelectionSupport, as before, but now subclasses SimpleListAdapter. Renamed SelectableListsAdapter to ListsAdapter. Updated examples and tests accordingly. Updated and fixed code examples in the docs for ListView.

This commit is contained in:
Jeff Pittman 2012-07-31 17:44:37 -05:00
parent b27adda021
commit 397619e58d
5 changed files with 135 additions and 115 deletions

View File

@ -1,5 +1,5 @@
from kivy.adapters.listadapter import ListAdapter, \
SelectableListsAdapter
ListsAdapter
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.listview import ListView, ListItemButton
@ -78,9 +78,9 @@ class CascadingView(GridLayout):
# Fruits, for a given category, in the middle:
#
fruits_list_adapter = \
SelectableListsAdapter(
ListsAdapter(
observed_list_adapter=fruit_categories_list_adapter,
selectable_lists_dict=fruit_categories,
lists_dict=fruit_categories,
data=fruit_categories[categories[0]],
args_converter=list_item_args_converter,
selection_mode='single',

View File

@ -1,11 +1,8 @@
from kivy.adapters.listadapter import ListAdapter
from kivy.adapters.mixins.selection import SelectableItem
from kivy.uix.listview import ListItemButton, ListItemLabel, \
CompositeListItem, ListView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty, ListProperty
class MainView(GridLayout):
@ -35,7 +32,7 @@ class MainView(GridLayout):
'kwargs': {'text': "Right",
'merge_text': True,
'delimiter': '-'}}]}
# Here we create a list adapter with some item strings, passing our
# CompositeListItem as the list item view class, and then we create a
# list view using this adapter:

View File

@ -18,23 +18,17 @@ from kivy.adapters.adapter import Adapter
from kivy.adapters.mixins.selection import SelectionSupport
class ListAdapter(SelectionSupport, Adapter):
class SimpleListAdapter(Adapter):
'''ListAdapter is an adapter around a simple Python list.
From the SelectionSupport mixin, ListAdapter has these properties:
- selection
- selection_mode
- allow_empty_selection
and several methods used in selection operations.
From Adapter, ListAdapter gets these properties:
From Adapter, SimpleListAdapter gets these properties:
- cls, for the list item class to use to instantiate row view
instances
- template, an optional kv template to use instead of a python class
(to use instead of cls)
- args_converter, an optional function to transform data item argument
sets, in preparation for either a cls instantiation,
or a kv template invocation
@ -49,13 +43,10 @@ class ListAdapter(SelectionSupport, Adapter):
def __init__(self, **kwargs):
if 'data' not in kwargs:
raise Exception('ListAdapter: input must include data argument')
raise Exception('list adapter: input must include data argument')
if type(kwargs['data']) not in (tuple, list):
raise Exception('ListAdapter: data must be a tuple or list')
super(ListAdapter, self).__init__(**kwargs)
# Reset and update selection, in SelectionSupport, if data changes.
self.bind(data=self.initialize_selection)
raise Exception('list adapter: data must be a tuple or list')
super(SimpleListAdapter, self).__init__(**kwargs)
def get_count(self):
return len(self.data)
@ -65,11 +56,31 @@ class ListAdapter(SelectionSupport, Adapter):
return None
return self.data[index]
class ListAdapter(SelectionSupport, SimpleListAdapter):
'''From the SelectionSupport mixin, ListAdapter has these properties:
- selection
- selection_mode
- allow_empty_selection
and several methods used in selection operations.
If you wish to have a bare-bones list adapter, without selection, use
SimpleListAdapter.
'''
def __init__(self, **kwargs):
super(ListAdapter, self).__init__(**kwargs)
# Reset and update selection, in SelectionSupport, if data changes.
self.bind(data=self.initialize_selection)
def get_view(self, index):
'''This method is identical to the one in Adapter, but here we pass
self to the list item class (cls) instantiation, so that the list
item class, required to mix in SelectableItem, will have access to
ListAdapter for calling to SelectionSupport methods.
'''This method is identical to the one in Adapter and
SimpleListAdapter, but here we pass self to the list item class (cls)
instantiation, so that the list item class, required to mix in
SelectableItem, will have access to ListAdapter for calling to
SelectionSupport methods.
'''
item = self.get_item(index)
if item is None:
@ -91,27 +102,31 @@ class ListAdapter(SelectionSupport, Adapter):
return Builder.template(self.template, **item_args)
def on_selection_change(self, *args):
'''on_selection_change() is the default handler for the
on_selection_change event, which is registered in SelectionSupport.
'''
pass
class SelectableListsAdapter(ListAdapter):
'''SelectableListsAdapter is specialized for use in chaining
list_adapters in a "cascade," where selection of the first,
changes the selection of the next, and so on.
class ListsAdapter(ListAdapter):
'''ListsAdapter is specialized for managing a dict of lists. It has wide
application, because a list of lists is a common need. ListsAdapter may
be used for chaining several list_adapters in a "cascade," where selection
of the first, changes the selection of the next, and so on.
'''
selectable_lists_dict = DictProperty({})
lists_dict = DictProperty({})
'''The selection of the observed_list_adapter, which must be single
selection here, is the key into selectable_lists_dict, which is a dict
selection here, is the key into lists_dict, which is a dict
of list item lists.
'''
observed_list_adapter = ObjectProperty(None)
selectable_lists_dict = DictProperty({})
lists_dict = DictProperty({})
def __init__(self, **kwargs):
super(SelectableListsAdapter, self).__init__(**kwargs)
super(ListsAdapter, self).__init__(**kwargs)
self.observed_list_adapter.bind(
on_selection_change=self.on_selection_change)
@ -122,7 +137,7 @@ class SelectableListsAdapter(ListAdapter):
self.data = []
return
self.data = self.selectable_lists_dict[str(observed_selection[0])]
self.data = self.lists_dict[str(observed_selection[0])]
class AccumulatingListAdapter(ListAdapter):

View File

@ -10,7 +10,7 @@ from kivy.uix.button import Button
from kivy.uix.listview import ListView, ListItemButton
from kivy.properties import NumericProperty, ListProperty, StringProperty
from kivy.adapters.mixins.selection import SelectableItem
from kivy.adapters.listadapter import ListAdapter, SelectableListsAdapter, \
from kivy.adapters.listadapter import ListAdapter, ListsAdapter, \
AccumulatingListAdapter
@ -192,7 +192,7 @@ class ListAdapterTestCase(unittest.TestCase):
self.assertEqual(selection_observer.call_count, 2)
class SelectableListsAdapterTestCase(unittest.TestCase):
class ListsAdapterTestCase(unittest.TestCase):
def setUp(self):
self.args_converter = lambda x: {'text': x,
@ -220,13 +220,13 @@ class SelectableListsAdapterTestCase(unittest.TestCase):
# Fruits, for a given category, are shown based on the fruit category
# selected in the first categories list above. The selected item in
# the first list is used as the key into a dict of lists of list
# items to reset the data in SelectableListsAdapter's
# items to reset the data in ListsAdapter's
# observed_selection_changed() method.
#
# data is initially set to the first list of list items.
#
fruits_l_a = \
SelectableListsAdapter(observed_list_adapter=fruit_categories_l_a,
ListsAdapter(observed_list_adapter=fruit_categories_l_a,
selectable_lists_dict=fruit_categories,
data=fruit_categories[categories[0]],
args_converter=self.args_converter,

View File

@ -21,29 +21,57 @@ From AbstractView we have these properties and methods:
Basic Example
-------------
The list items you put in a list view can be totally custom, but to have a
simple button, we may use the :class:`ListItem` class.
Here we make a list view with 100 items.
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
class MainView(GridLayout):
def __init__(self, **kwargs):
kwargs['cols'] = 2
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
list_view = ListView([str(index) for index in xrange(100)])
self.add_widget(list_view)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
Using a ListAdapter
-------------------
The basic example above uses :class:`SimpleListAdapter` internally, an adapter
that does not offer selection support.
For most uses of a list, however, selection support is needed. It is built in
to :class:`ListAdapter`.
The view used for items in a list view can be totally custom, but to have a
simple button, we may use the :class:`ListItemButton` class.
from kivy.adapters.list_adapter import ListAdapter
from kivy.uix.listview import ListItem, ListView
data = ["Item {0}".format(index) for index in xrange(100)]
# A list view needs a list adapter to provide a mediating service to the
# data, in the case here our data list, passed as the first
# argument. We choose single selection mode. We may also set this for
# allowing multiple selection. We set allow_empty_selection to False so
# that there is always an item selected if at least one is in the list.
# Setting this to true will make the list useful for display only.
# Finally, we pass ListItem as the class (cls) to be instantiated by the
# list adapter for each list item. ListItem is based on Button. When an
# item is selected, its background color will change to red.
# data, which is the first argument. We choose single selection mode. We
# may also set this for allowing multiple selection. We set
# allow_empty_selection to False so that there is always an item
# selected if at least one is in the list. Setting this to true will make
# the list useful for display only. Finally, we pass ListItem as the class
# (cls) to be instantiated by the list adapter for each list item.
# When an item is selected, its background color will change to red.
list_adapter = ListAdapter(data=data,
selection_mode='single',
allow_empty_selection=False,
cls=ListItem)
cls=ListItemButton)
# The list view is a simple component. Just give it the list adapter that
# will provide list item views based on its data.
@ -55,65 +83,13 @@ Composite List Item Example
Let's say you would like to make a list view with composite list item views
consisting of a button on the left, a label in the middle, and a second button
on the right. Perhaps the buttons could be made as toggle buttons for two
separate properties pertaining to the label. First, we need to make a custom
list item, which we will base on the required SelectableItem mixin and our
choice of layout, BoxLayout:
separate properties pertaining to the label. We add default buttons and a
label in this example.
from kivy.adapters.listadapter import ListAdapter
from kivy.adapters.mixins.selection import SelectableItem
from kivy.uix.listview import ListItemButton, ListItemLabel, ListView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListItemButton, ListItemLabel, \
CompositeListItem, ListView
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty, ListProperty
class CompositeListItem(SelectableItem, BoxLayout):
# ListItem sublasses Button, which has background_color.
# For a composite list item, we must add this property.
background_color = ListProperty([1, 1, 1, 1])
selected_color = ListProperty([1., 0., 0., 1])
deselected_color = ListProperty([.33, .33, .33, 1])
left_button = ObjectProperty(None)
middle_label = ObjectProperty(None)
right_button = ObjectProperty(None)
def __init__(self, **kwargs):
super(CompositeListItem, self).__init__(**kwargs)
# For the component list items in our composite list item, we want
# their individual selection to select the composite. Here we pass
# an argument for the list_adapter, required by the SelectableItem
# mixing, and a selection_target argument, setting it to self (to
# this composite list item).
kwargs['selection_target'] = self
kwargs['text']="left-{0}".format(kwargs['text'])
self.left_button = ListItemButton(**kwargs)
kwargs['text']="middle-{0}".format(kwargs['text'])
self.middle_label = ListItemLabel(**kwargs)
kwargs['text']="right-{0}".format(kwargs['text'])
self.right_button = ListItemButton(**kwargs)
self.add_widget(self.left_button)
self.add_widget(self.middle_label)
self.add_widget(self.right_button)
def select(self, *args):
self.background_color = self.selected_color
def deselect(self, *args):
self.background_color = self.deselected_color
def __repr__(self):
if self.middle_label is not None:
return self.middle_label.text
else:
return 'empty'
class MainView(GridLayout):
@ -123,18 +99,50 @@ choice of layout, BoxLayout:
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
args_converter = \
lambda x: \
{'text': x,
'size_hint_y': None,
'height': 25,
'cls_dicts': [{'cls': ListItemButton,
'kwargs': {'text': "Left",
'merge_text': True,
'delimiter': '-'}},
{'cls': ListItemLabel,
'kwargs': {'text': "Middle",
'merge_text': True,
'delimiter': '-',
'is_representing_cls': True}},
{'cls': ListItemButton,
'kwargs': {'text': "Right",
'merge_text': True,
'delimiter': '-'}}]}
# Here we create a list adapter with some item strings, passing
# our CompositeListItem as the list item view class, and then we
# create list view using this adapter:
data = ["{0}".format(index) for index in xrange(100)]
list_adapter = ListAdapter(data=data,
# create a list view using this adapter:
item_strings = ["{0}".format(index) for index in xrange(100)]
# And now the list adapter, constructed with the item_strings as
# the data, and our args_converter() that will operate one each
# item in the data to produce list item view instances from the
# :class:`CompositeListItem` class.
list_adapter = ListAdapter(data=item_strings,
args_converter=args_converter,
selection_mode='single',
allow_empty_selection=False,
cls=CompositeListItem)
# Pass the adapter to ListView.
list_view = ListView(adapter=list_adapter)
self.add_widget(list_view)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
Using With kv
-------------
@ -333,10 +341,10 @@ class ListView(AbstractView):
if 'adapter' not in kwargs:
if item_strings is None:
raise Exception('ListView: input needed, or an adapter')
list_adapter = ListAdapter(data=item_strings,
selection_mode='single',
allow_empty_selection=False,
cls=ListItemButton)
list_adapter = SimpleListAdapter(data=item_strings,
selection_mode='single',
allow_empty_selection=False,
cls=ListItemButton)
kwargs['adapter'] = list_adapter
super(ListView, self).__init__(**kwargs)