It took a lot longer then I expected to “generalize” the experiments I was doing with creating an owner-drawn List/Grid hybrid for our in-house work, but I’ve finally un-tied it from our core systems and uploaded the /very/ basic version to Launchpad. This isn’t directly usable yet (though its close: our in-house version is “usable”, I just have to finish some tweaking/refactoring and expose a few more things properly), but I thought I’d pre-announce it to get some feedback if anyone has any ideas or suggestions. Its very flexible in how it can display a rather wide range of data, I just need to work up some example renderers/field customizations.
I’m hosting it at http://launchpad.net/catalogctrl – and the code is in particular at http://bazaar.launchpad.net/~apt-shansen/catalogctrl/trunk/files
The basic documentation thus-far:
CatalogCtrl, v0.1
WHAT IS CATALOGCTRL?
···
====================
The CatalogCtrl is an owner-drawn control for displaying “row”-oriented data
in an extremely flexible and configurable way while still attempting to mimic
(to the extent possible) the behavior of similar native controls.
The key features are:
- Model oriented data storage: instead of adding rows, columns or list items
you set the models that the CatalogCtrl should use to gather data from.
Exactly how it chooses to display that data is determined separately by
Field configuration. - Record oriented interface: Although a ListCtrl can have columns, it is
generally a “row-oriented” control in that a user generally interacts with
it on a row by row basis. Similarly, a Grid might be configurable to allow
row or cell selection, but it is generally more of a “cell-oriented”
control. The CatalogCtrl is “record-oriented”, where a record is essentially
a horizontal row – except see the next item– - Multiple lines per record: The CatalogCtrl allows each record to be
configured to have multiple lines, one on top of the other. If a record has
two lines they will visually be shown to belong to the same horizontal zone
(such as have the same background color in cases where records are
configured to have alternate background colors), with different data shown
in each line. - Variable line height: Each line has its height calculated independantly,
for each record. So one record can be set to be 100 px high, and the other
20 if that is what is configured based upon its data. - Variable field width: A line is made up of a series of fields, each which
can have a different width that is calculated as needed. The CatalogCtrl
is /not/ (unless you set it that way) a column-oriented control: if there
are two lines per record, you may have one field directly on top of the
other and they can have completely different widths. As such there can even
be entirely different number of fields per line. - Pluggable renderers: Exactly how a field is rendered can be entirely
customized by adding new renderers to the CatalogCtrl. - Field objects are used to customize and store the specific details of how
the control displays its data: these are pickleable, as well.
WHAT ISN’T THE CATALOGCTRL?
- A drop-in replacement for ListCtrl: The API for the CatalogCtrl is different
then the ListCtrl so you can’t simply use it instead to provide owner-drawn
rendering capabilities to an existing application. Its conceivably possible
that I might one day add a “ListCtrl-Compatibility” wrapper around it for
that purpose, but… - A drop-in replacement for Grid: Same as above.
- An editor control: There is no way to add inline editing capabilities to
the CatalogCtrl at this time. - A native control: Although I intend on attempting to make it behave as
natively as possible.
GETTING STARTED
The first thing you should do when getting started with using the CatalogCtrl
is to configure the fields to be displayed. The easiest way to do this is to
create subclasses of CatalogFields._BaseField, one for each field in your
model.
For example, if you have the following data in your model:
- status: an integer
- title: a string
- notes: a (long) string
- updated: a datetime instance.
- placed: A boolean value
The following are example field instances you can configure to display this
data:
from CatalogFields import _BaseField, DefaultContext, SizeInfo
class StatusField(_BaseField):
Name = "status"
Type = "string"
SizeInfo = {
DefaultContext: SizeInfo(
Fixed=True,
Size=100
)
}
class TitleField(_BaseField):
Name = "title"
Type = "string"
SizeInfo = {
DefaultContext: SizeInfo(
Size=200,
Proportion=2,
)
}
class NotesField(_BaseField):
Name = "notes"
Type = "string"
SizeInfo = {
DefaultContext: SizeInfo(
Size=100,
Proportion=1,
Wrap=True,
)
}
class UpdatedField(_BaseField):
Name = "updated"
Type = "datetime"
SizeInfo = {
DefaultContext: SizeInfo(
Size=100,
)
}
class PlacedField(_BaseField):
Name = "placed"
Type = "checkbox"
IsChecked = lambda x: x == True
SizeInfo = {
DefaultContext: SizeInfo(
Fixed=True,
Size=20,
)
}
In the field classes, the Name is the attribute on the model object that will
obtain the actual data. Type is used to indicate which renderer should be used
to draw the data.
The SizeInfo attribute is a dictionary of contexts to SizeInfo instances.
Contexts are an advanced feature and for most usages you can simply use
DefaultContext.
By default, the CatalogField system automatically registers any classes created
that inherit from _BaseField. You can use that registration to fetch instances
of the field for constructing a view, such as:
view = [
[CatalogFields.getFieldById(f) for f in (
'status', 'title', 'notes', 'updated', 'placed)
]
]
You don’t have to use the registration system at all if you do not wish to. You
can simply pass the instances directly, such as:
view = [
[StatusField(), TitleField(), NotesField(),
UpdatedField(), PlacedField()]
]
Since the CatalogCtrl allows multiple lines per record, the view itself is
always a list of field instances – even if there’s only one line.
To set the view, you construct a CatalogCtrl and set its fields with the
SetFields method, as in:
ctrl = CatalogCtrl(parent)
ctrl.SetFields(view)
You then set the model objects, which are always a simple list of instances:
ctrl.SetModels(models)
WHAT ABOUT MODEL OBJECTS?
Model objects can be any instance which has named attributes that match the
names specified in the field configuration; the only requirement in those
objects is that there is an “id” attribute which uniquely identifies that
model.
If your system already has such objects available you likely can use them
directly with the CatalogCtrl, but if your data is available in other formats
you can use the CatalogModel helper to construct some CatalogCtrl-compatible
models.
CatalogModel can be constructed either from a dictionary, where the keys are
the names and the values the actual data; or two lists, the first which is the
actual data and the second which represents the names in sequential order.
For example,
model = CatalogModel(dict(
status=1,
title="My First Title",
notes="Some random bit of notes",
updated=datetime.datetime.now(),
placed=True
))
Or,
model = CatalogModel(
[1, "My First Title", "Some random bit of notes",
datetime.datetime.now(), True],
["status", "title", "notes", "updated", "placed"]
)
In both of these example cases, an “id” field was not provided and so the
CatalogModel will automatically use the memory address of the instance to
uniquely identify the model.