Pre-ANN: CatalogCtrl, an owner-drawn List/Grid hybrid

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.

Hi Stephen,

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 CatalogCtrl in Launchpad -- and the code is in
particular at
~apt-shansen/catalogctrl/trunk : files for revision 4

It looks very interesting and I will give it a try for sure.
Meanwhile, as I told you few days ago, I continued the development of
my owner-drawn list control, for which I attach a small screenshot (in
report mode, with wx.LC_REPORT style set) in case anyone is interested
in looking at it. In any case, as we are not in competition ( :smiley: ), I
think that emulating a list control with a wxGrid to implement more
features is a more viable solution than a completely owner drawn list
control written from scratch.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

On Tue, Jan 27, 2009 at 5:29 AM, Stephen Hansen wrote:

Andrea and Stephen,

You are probably aware of Phillip's ObjectListView:
http://objectlistview.sourceforge.net/python/index.html

Any chance that all your efforts could be coordinated/combined?

Werner

Hi Werner,

Andrea and Stephen,

You are probably aware of Phillip's ObjectListView:
ObjectListView — ObjectListView v1.2 documentation

Any chance that all your efforts could be coordinated/combined?

Probably. I don't know enough about Stephen's implementation, so I can
not comment on that. But the implementation I worked on is (almost)
completely compatible with the original wxWidgets wx.ListCtrl, so I
think it could be tied in ObjectListView without much effort.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

On Fri, Jan 30, 2009 at 11:42 AM, Werner F. Bruhin wrote:

Andrea, if you are onto that, could you implement a mode
where the list has x columns and the control has n * x
columns and allows the n list columns to "flow over" to the
right ? Like so (list has 2 columns)

ctrlcol 1 ctrlcol 2 ctrlcol 3 ctrlcol 4
listcol 1 listcol 2 listcol 1 listcol 2

···

On Fri, Jan 30, 2009 at 11:32:08AM +0000, Andrea Gavana wrote:

in looking at it. In any case, as we are not in competition ( :smiley: ), I
think that emulating a list control with a wxGrid to implement more
features is a more viable solution

------------------------------------------------------------------
item 1 item 1 item 6 item 6
item 2 item 2 item 7 item 7
item 3 item 3
item 4 item 4
item 5 item 5

We could really use such a thing in GNUmed.

Karsten
--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

+1, perhaps similar to the tree controls supercoordination
done by Frank ?

Karsten

···

On Fri, Jan 30, 2009 at 12:42:58PM +0100, Werner F. Bruhin wrote:

Andrea and Stephen,

You are probably aware of Phillip's ObjectListView:
ObjectListView — ObjectListView v1.2 documentation

Any chance that all your efforts could be coordinated/combined?

--
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD 4537 78B9 A9F9 E407 1346

It looks very interesting and I will give it a try for sure.

Meanwhile, as I told you few days ago, I continued the development of

my owner-drawn list control, for which I attach a small screenshot (in

report mode, with wx.LC_REPORT style set) in case anyone is interested

in looking at it. In any case, as we are not in competition ( :smiley: ), I

think that emulating a list control with a wxGrid to implement more

features is a more viable solution than a completely owner drawn list

control written from scratch.

Very nice! Your screen shot gave me some ideas on sample renderers to write, lol :wink: The current implementation I have can render basically anything in an individual cell and do variable widths and heights, but I was kind of at a loss on what sample renderers to put in to show people :slight_smile:

I can already seem some distinct differences between the two: you have the ability to have different renderers in the same “cell” from one row to another, and editors – two things I didn’t ever really consider and would probably not use, but which are quite cool!

The key feature that we needed and I have implemented in CatalogCtrl is that its not … /column/ oriented. We have two very distinct contexts where we show data from our application: in one the horizontal space is pertty free, in one it is very precious. I’ve wanted to use the same control in both contexts forever but its always had serious compromises, so I decided to approach the API from scratch instead of trying to mimic ListCtrl. Thus, the CatalogCtrl has “records”, comprised of “lines”, comprised of “fields”, with the width of one line not necessecarily being the same as the next, and not having anywhere near the same number of fields.

The attached image demonstrates a bit of an early sample that I was showing the bosses at my company – the currently released code won’t quite do that but only because the attached uses some renderers that are very specific to our data model and environment. You can see 2.1 rows in ListCtrl speak (2.1 records for CatalogCtrl). Each record presently has two lines, the first made up of 7 fields (5 visible: status, icon, slug, flags, keywords) and the second made up of 4 fields (thumbnail, has_subelements, has_headline, has_caption). (One bug in that phase of the implementation is the record height for each individual line /should/ be variable and distinct: so only the first record (which has a thumbnail) should be that high, the rest shorter as they don’t)

···

On Fri, Jan 30, 2009 at 3:32 AM, Andrea Gavana andrea.gavana@gmail.com wrote:

On Fri, Jan 30, 2009 at 3:42 AM, Werner F. Bruhin werner.bruhin@free.fr wrote:

Andrea and Stephen,

You are probably aware of Phillip’s ObjectListView:

http://objectlistview.sourceforge.net/python/index.html

Any chance that all your efforts could be coordinated/combined?

I’m aware of ObjectListView and I absolutely love it – and use it in a few places! Its a question of goals though. OLV is an object-oriented API on top of a ListCtrl interface with some very commonly needed and helpful features added in, something IMHO very needed… working with a ListCtrl is a seriously painful exercise. There’s some very critical features, though, that I think are too painful to implement on top of ListCtrl’s provided interface that I needed desperately: specifically “stacking” columns on top of each-other (‘multiple lines per record’, in CatalogCtrl speak) for a particular row (‘record’).

From an API point of view, CC is more like OLV then ListCtrl presently as it uses model objects and CatalogField objects to specify fields (where OLV has ColumnInfo objects). But they aren’t really compatible at present.

Sine Andrea is imiplementing a completely compatible owner-drawn ListCtrl, it’d probably not be that hard at all to plug his code “under” OLV – and I actually intend on having a ListCtrl compatible API wrapper around CC eventually. Its just not a priority yet.

–Stephen

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.

I’m not sure about this, but could it be useful if each “record” could be expandable/collapsible? I can imagine situations in which one would want the basic information to be in a simple list and thereby fit many rows on screen and visually scan them quickly, but then one could click something to expand one row into a full record, and thereby get lots of additional information/interactive widgets, etc. In this sense, if this were, e.g., a printed scientific catalog, pure list mode would sort of be in “table of contents” or “index” mode, and fully expanded would be like you were looking at that item and all its details inside the catalog–but with this you would sort of have a nice hybrid of both.

Completely thinking out loud here, and maybe it already does this, but I just thought I’d throw it out. Both this, ObjectListView, and Andrea’s UltimateListView seem like nice additions to the wx suite.

Che

···

On Tue, Jan 27, 2009 at 12:29 AM, Stephen Hansen apt.shansen@gmail.com wrote:

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.


wxpython-users mailing list

wxpython-users@lists.wxwidgets.org

http://lists.wxwidgets.org/mailman/listinfo/wxpython-users

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.

I’m not sure about this, but could it be useful if each “record” could be expandable/collapsible? I can imagine situations in which one would want the basic information to be in a simple list and thereby fit many rows on screen and visually scan them quickly, but then one could click something to expand one row into a full record, and thereby get lots of additional information/interactive widgets, etc. In this sense, if this were, e.g., a printed scientific catalog, pure list mode would sort of be in “table of contents” or “index” mode, and fully expanded would be like you were looking at that item and all its details inside the catalog–but with this you would sort of have a nice hybrid of both.

Completely thinking out loud here, and maybe it already does this, but I just thought I’d throw it out. Both this, ObjectListView, and Andrea’s UltimateListView seem like nice additions to the wx suite.

I… think I absolutely love that idea. Since each record’s actual size is calculated independantly, and there’s already support for on-the-fly changing of sizes (a string field can have the “Wrap” flag set which will make the StringRenderer wrap as much as needed vertically to display the data in the field-- in our internal system users can right click and select ‘Wrap’ on a column, since they may not want all that wasted space all the time), I’m pretty sure I can add that. And since I’d so use it, I will :slight_smile:

I’ll add a style to make it support Expandable Records, and then an option where you can set which lines are shown when a record is expanded.

Thanks for the idea!

–Stephen

Fine with me!

I think ObjectListView as it currently stands would be easier to adapt to
Andrea's UltimateList. But anything is possible, given enough effort and
evenings with nothing to do :slight_smile:

Regards,
Phillip

···

------------------------------------------------------------------------
Phillip Piper www.bigfoot.com/~phillip_piper phillip_piper@bigfoot.com
A man's life does not consist in the abundance of his possessions

-----Original Message-----
From: wxpython-users-bounces@lists.wxwidgets.org
[mailto:wxpython-users-bounces@lists.wxwidgets.org] On Behalf Of Werner F.
Bruhin
Sent: Friday, 30 January 2009 1:43 PM
To: wxpython-users@lists.wxwidgets.org
Subject: Re: [wxpython-users] Pre-ANN: CatalogCtrl, an owner-drawn List/Grid
hybrid

Andrea and Stephen,

You are probably aware of Phillip's ObjectListView:
http://objectlistview.sourceforge.net/python/index.html

Any chance that all your efforts could be coordinated/combined?

Werner
_______________________________________________
wxpython-users mailing list
wxpython-users@lists.wxwidgets.org
http://lists.wxwidgets.org/mailman/listinfo/wxpython-users