Added docs in listview, and made a few related fixes in tests and examples.

This commit is contained in:
geojeff 2012-10-23 15:34:59 -05:00
parent 0a64656bdb
commit d5d15de84b
4 changed files with 205 additions and 28 deletions

View File

@ -18,18 +18,12 @@ class MainView(GridLayout):
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
# This is quite an involved args_converter, so we should go
# through the details. x here is a data item object, be it
# a string for a typical usage, as here, or some other object.
# x will become the text value when the class used in this
# example, CompositeListItem, is instantiated with the args
# returned by this converter. All of the rest, for size_hint_y,
# height, and the cls_dicts list, will be passed in the call
# to instantiate CompositeListItem for a data item. Inside the
# constructor of CompositeListItem is special-handling code that
# uses cls_dicts to create, in turn, the component items in the
# composite. This is a similar approach to using a kv template,
# which you might wish to explore also.
# This is quite an involved args_converter, so we should go through the
# details. A CompositeListItem instance is made with the args
# returned by this converter. The first three, text, size_hint_y,
# height are arguments for CompositeListItem. The cls_dicts list contains
# argument sets for each of the member widgets for this composite:
# ListItemButton and ListItemLabel.
args_converter = \
lambda rec: \
{'text': rec['text'],
@ -45,11 +39,6 @@ class MainView(GridLayout):
item_strings = ["{0}".format(index) for index in xrange(100)]
# And now the list adapter, constructed with the item_strings as
# the data, a dict to add the required is_selected boolean onto
# data records, and our args_converter() that will operate one each
# item in the data to produce list item view instances from the
# :class:`CompositeListItem` class.
dict_adapter = DictAdapter(sorted_keys=item_strings,
data=integers_dict,
args_converter=args_converter,

View File

@ -19,12 +19,9 @@ Factory.register('ListItemButton', cls=ListItemButton)
Builder.load_string('''
[CustomListItem@SelectableView+BoxLayout]:
index: ctx.index
size_hint_y: ctx.size_hint_y
height: ctx.height
is_selected: ctx.is_selected
ListItemButton:
index: ctx.index
text: ctx.text
is_selected: ctx.is_selected
''')

View File

@ -571,7 +571,38 @@ class AdaptersTestCase(unittest.TestCase):
self.assertTrue(isinstance(view, ListItemButton))
self.assertTrue(view.is_selected)
def test_list_adapter_with_custom_class(self):
def test_list_adapter_with_custom_data_item_class(self):
class DataItem(object):
def __init__(self, text='', is_selected=False):
self.text = text
self.is_selected = is_selected
data_items = []
data_items.append(DataItem(text='cat'))
data_items.append(DataItem(text='dog'))
data_items.append(DataItem(text='frog'))
list_item_args_converter = lambda obj: {'text': obj.text,
'size_hint_y': None,
'height': 25}
list_adapter = ListAdapter(data=data_items,
args_converter=list_item_args_converter,
selection_mode='single',
propagate_selection_to_data=True,
allow_empty_selection=False,
cls=ListItemButton)
data_item = list_adapter.get_data_item(0)
self.assertTrue(isinstance(data_item, DataItem))
self.assertTrue(data_item.is_selected)
view = list_adapter.get_view(0)
self.assertTrue(isinstance(view, ListItemButton))
self.assertTrue(view.is_selected)
def test_list_adapter_with_widget_as_data_item_class(self):
# Use a widget as data item.
class DataItem(Label):
@ -608,9 +639,9 @@ class AdaptersTestCase(unittest.TestCase):
list_item_args_converter)
self.assertEqual(list_adapter.template, None)
apple_data_item = list_adapter.get_data_item(0)
self.assertTrue(isinstance(apple_data_item, DataItem))
self.assertTrue(apple_data_item.is_selected)
data_item = list_adapter.get_data_item(0)
self.assertTrue(isinstance(data_item, DataItem))
self.assertTrue(data_item.is_selected)
view = list_adapter.get_view(0)
self.assertTrue(isinstance(view, ListItemButton))

View File

@ -39,7 +39,7 @@ Here we make a listview with 100 items.
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
Using a ListAdapter
Using an Adapter
-------------------
Behind the scenes, the basic example above uses :class:`SimpleListAdapter`.
@ -66,7 +66,7 @@ ListAdapter and DictAdapter
---------------------------
For many uses of a list, the data is more than a simple list of strings and
selection functionality is needed. :class:`ListAdapter` and
selection functionality is often needed. :class:`ListAdapter` and
:class:`DictAdapter` each contain functionality for selection.
See the :class:`ListAdapter` docs for details, but here are synopses of
@ -103,14 +103,14 @@ its arguments:
In narrative, we can summarize with:
A listview's list adapter takes data items and uses an args_converter
A listview's adapter takes data items and uses an args_converter
function to transform them into arguments for making list item view
instances, using either a cls or a kv template.
In a graphic, a summary of the relationship between a listview and its
list adapter, looks like this:
- ------------------- ListAdapter --------------------
- ------------ ListAdapter or DictAdapter ------------
- | |
- | <list item views> (cls or template) <data items> |
- ListView --> | [args_converter] |
@ -194,6 +194,166 @@ listview will only allow single selection -- additional touches will be
ignored. When the listview is first shown, the first item will already be
selected, because allow_empty_selection is False.
:class:`ListItemLabel` works much the same way as :class:`ListItemButton`.
Using a Custom Item View Class
------------------------------
The data used in an adapter can be any of the normal Python types, such as
strings, class instances, dictionaries, etc. It is up to the programmer to
assure that the args_converter has appropriate functionality.
Here we make a simple DataItem class that has the required text and
is_selected properties:
from kivy.uix.listview import ListItemButton
class DataItem(object):
def __init__(self, text='', is_selected=False):
self.text = text
self.is_selected = is_selected
data_items = []
data_items.append(DataItem(text='cat')
data_items.append(DataItem(text='dog')
data_items.append(DataItem(text='frog')
list_item_args_converter = lambda obj: {'text': obj.text,
'size_hint_y': None,
'height': 25}
# We will set this data in a ListAdapter along with the list item
# args_converter function above (lambda), and we set arguments about
# selection. We will allow single selection, selection in the listview
# will propagate to the data items -- the is_selected for each data item
# will be set. And, by having allow_empty_selection=False, when the
# listview first appears, the first item, 'cat', will already be
# selected. The list adapter will instantiate a ListItemButton class
# instance for each data item, using the assigned args_converter.
list_adapter = ListAdapter(data=data_items,
args_converter=list_item_args_converter,
selection_mode='single',
propagate_selection_to_data=True,
allow_empty_selection=False,
cls=ListItemButton)
list_view = ListView(adapter=list_adapter)
The list_vew would then be added to a view with add_widget().
You may also use the provided :class:`SelectableDataItem` mixin to make a
custom class. Instead of the "manually-constructed" DataItem class above,
we could do:
from kivy.adapters.models import SelectableDataItem
class DataItem(SelectableDataItem):
pass
:class:`SelectableDataItem` is a simple mixin class that has the text and
is_selected properties.
Using an Item View Template
---------------------------
:class:`SelectableView` is another simple mixin class that has required
properties for a list item: text, and is_selected. To make your own template,
mix it in as follows:
from kivy.uix.listview import ListItemButton
from kivy.uix.listview import SelectableView
Builder.load_string(<triplequotes>
[CustomListItem@SelectableView+BoxLayout]:
size_hint_y: ctx.size_hint_y
height: ctx.height
ListItemButton:
text: ctx.text
is_selected: ctx.is_selected
</triplequotes>)
A class called CustomListItem will be instantiated for each list item. Note that
it is a layout, BoxLayout, and is thus a kind of container. It contains a
:class:`ListItemButton` instance.
Using the power of the Kivy language (kv), you can easily build composite list
items -- in addition to ListItemButton, you could have a ListItemLabel, or a
custom class you have defined and registered with the system.
An args_converter needs to be constructed that goes along with such a kv
template. For example, to use the kv template above:
list_item_args_converter = \
lambda rec: {'text': rec['text'],
'is_selected': rec['is_selected'],
'size_hint_y': None,
'height': 25}
integers_dict = \
{ str(i): {'text': str(i), 'is_selected': False} for i in xrange(100)}
# Here we create a dict adapter with 1..100 integer strings as
# sorted_keys, and an integers_dict as data, passing our
# CompositeListItem kv template for the list item view.
dict_adapter = DictAdapter(sorted_keys=[str(i) for i in xrange(100)],
data=integers_dict,
args_converter=list_item_args_converter,
template='CustomListItem')
# Now we create a list view using this adapter. The args_converter above
# converts dict attributes to ctx attributes.
list_view = ListView(adapter=dict_adapter)
The list_vew would then be added to a view with add_widget().
Using CompositeItemView
-----------------------
The class :class:`CompositeItemView` is another option for building complex
composite list items. The kv language approach has its advantages, but here we
build a composite list view using a straight Kivy widget method:
# This is quite an involved args_converter, so we should go through the
# details. A CompositeListItem instance is made with the args
# returned by this converter. The first three, text, size_hint_y,
# height are arguments for CompositeListItem. The cls_dicts list contains
# argument sets for each of the member widgets for this composite:
# ListItemButton and ListItemLabel. This is a similar approach to using a
# kv template described above.
args_converter = \
lambda rec: \
{'text': rec['text'],
'size_hint_y': None,
'height': 25,
'cls_dicts': [{'cls': ListItemButton,
'kwargs': {'text': rec['text']}},
{'cls': ListItemLabel,
'kwargs': {'text': "Middle-{0}".format(rec['text']),
'is_representing_cls': True}},
{'cls': ListItemButton,
'kwargs': {'text': rec['text']}}]}
item_strings = ["{0}".format(index) for index in xrange(100)]
integers_dict = \
{ str(i): {'text': str(i), 'is_selected': False} for i in xrange(100)}
# And now the dict adapter, constructed with the item_strings as
# the sorted_keys, the integers_dict as data, and our args_converter()
# that produce list item view instances from the
# :class:`CompositeListItem` class.
dict_adapter = DictAdapter(sorted_keys=item_strings,
data=integers_dict,
args_converter=args_converter,
selection_mode='single',
allow_empty_selection=False,
cls=CompositeListItem)
list_view = ListView(adapter=dict_adapter)
For details on how :class:`CompositeListItem` works, view the code and look
for parsing of the cls_dicts list and kwargs processing.
Uses for Selection
------------------