Sort in a ListModel

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Sort in a ListModel

Dmitry Zamotkin-3
Hello, Smalltalkers!


I have a ListModel on some OrderedCollection that is aspect of a MyObject.
And I use ListPresenter with Enhanched (Multicolumn) view. I need to sort
column contents when user click on column header. But, unfortunately,
ListModel changes model to SortedCollection on this event and ListModel does
not bind to original OrderedCollection. When I try to add an element to
original collection by method of MyObject, I have no changes in my ListModel
and I must to update a ListModel.

What should I do really, any ideas?

--
Dmitry Zamotkin


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel

Christopher J. Demers
I am certainly not an MVP expert, but I think what you would want to do here
is to wrap your OrderedCollection in the ListModel sooner, like in your
MyObject instance variable that currently just holds the OrderedCollection.
In most cases a ListModel is transparent, the exception being a copy do with
deletions (I posted about this with a workaround  a little while ago).

At first I felt icky about doing this, because it seems to be bringing GUI
issues into the model.  However if you think about it ListModel is of course
a model, it just adds additional event support needed by the GUI.

I hope this helps, and the experts can feel free to chime in with a better
approach. ;)

Chris

Dmitry Zamotkin <[hidden email]> wrote in message
news:9i4p3u$d62$[hidden email]...
> Hello, Smalltalkers!
>
>
> I have a ListModel on some OrderedCollection that is aspect of a MyObject.
> And I use ListPresenter with Enhanched (Multicolumn) view. I need to sort
> column contents when user click on column header. But, unfortunately,
> ListModel changes model to SortedCollection on this event and ListModel
does
> not bind to original OrderedCollection. When I try to add an element to
> original collection by method of MyObject, I have no changes in my
ListModel
> and I must to update a ListModel.
>
> What should I do really, any ideas?
>
> --
> Dmitry Zamotkin
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel

Ian Bartholomew-4
In reply to this post by Dmitry Zamotkin-3
Dmitry,

> What should I do really, any ideas?

Dolphin's MVP is slightly awkward in the way it handles ListPresenters, you
can easily end up with a ListModel and ListPresenter that have a bit too
much in common.

One solution is to split them completely -

To do this - your MyModel contains a list held in a simple
OrderedCollection. All additions, deletions or modification to items in the
list are then done through methods that trigger an event specific to your
model (#myListChanged for instance). MyModel also exposes the list by
including a method that answers, preferably, a copy of the list.

MyModel>>addItem: aNewItem
    myList add: aNewItem.
    self trigger: #myListChanged

MyModel>>myList
    ^myList copy

Your MyPresenter adds a ListPresenter as normal -

MyPresenter>>createComponents
    ....
    myListPresenter := self add: ListPresenter new name: 'my list view'

You also have to tell the associated model that you are interested in the
new triggered event.  Where and when to do this depends to some extent on
how you connect the MyModel instance to your MyPresenter instance. A good
place is in the #createSchematicWiring method but this requires you to use
the model created in MyPresenter class>>defaultModel. If not the
#onViewOpened can be useful.

MyPresenter>>createSchematicWiring
    .....
    self model when: #myListChanged send: #onMyListChanged to: self

You then add the method that updates the ListPresenter -

MyPresenter>>onMyListChanged
    myListPresenter model list: self model myList

One final job is to ensure that the list is populated when MyPresenter is
initially opened -

MyPresenter>>onViewOpened
    ........
    self onMyListChanged

Now, when your user clicks on the sort header the ListModel associated with
the ListPresenter is sorted, and converted to a SortedCollection but the
OrderedCollection contained in your presenter is unchanged. Adding items to
the list, in the model, triggers the event and tells MyPresenter to update
the list.

Some other points -
- If you want to maintain the current selection after a new item has been
added you have to add a couple of lines to #onMyListChanged to remember the
selection and reselect it, if possible, after the list is changed.

- If you have a large list then doing a blanket update on every change is
very inefficient. You can get round that by adding extra triggers that
specify the action that has occurred and the data that changed. For
example -

MyModel>>addItem aNewItem
    myList addItem: aNewItem.
    self trigger: #itemAddedToList with: aNewItem

for which you could than use the event target -

MyPresenter>>onItemAddedToList: anItem
    myListPresenter model add: anItem

If you want a simple demo package that shows the above then mail me

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel

Ian Bartholomew-4
I wrote -

> One solution is to split them completely -

A clarification on that line as, on rereading it, I noticed that it was
somewhat vague.

What I am suggesting is splitting up the collection held by your model
(MyModel) and the collection held in the ListModel associated with the
ListPresenter. Any changes the user makes to the ListModels's copy of the
collection, such as sorting it, are not reflected back into the copy held by
MyModel.  However, MyPresenter is notified when MyModel changes the list and
can arrange for the ListPresenter to be updated.

Also, on reading Chris' post I realised I may have misunderstood the
original question, I'm still not 100% sure what you are currently doing? I
read it as if you had a model, MyModel, with an instVar containing a
ListModel (wrapping an OrderedCollection) and that you were exporting this
ListModel to share with the ListPresenter in your MyPresenter.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel

Dmitry Zamotkin-3
In reply to this post by Ian Bartholomew-4
Ian,

After your reply I myself have understood the key point of a problem.
ListModel misses his original collection after sorting. I suppose ListModel
MUST remember primary collection allways. I.e. ListModel must have two
instance variables: originalList and visualList, for example:

ListModel>>list
    ^ visualList

ListModel>>originalList
    ^ originalList

ListModel>>refreshList
    visualList := self originalList

ListModel>>list: aSequenceableCollection
    originalList == aSequenceableCollection ifFalse: [
        originalList := aSequenceableCollection.
        self trigger: #listChanged
    ].
    self refreshList.

ListModel loses sorting after refreshing but keeps a connection with an
original.

Do you feel I get to the truth? :-)

--
Dmitry Zamotkin


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel

Ian Bartholomew-4
Dmitry,

> ListModel loses sorting after refreshing but keeps a connection with an
> original.
>
> Do you feel I get to the truth? :-)

I can see what you are saying but I don't really think it is a good way to
go.

ListModel is a Collection subclass that implements most (but not all) of the
behaviour of other Collection subclasses. However, it is not really intended
to be a general purpose Collection class but is instead specialised into
being the Model for a ListPresenter MVP triad. What you are trying to do is
create a new type of ListModel which can be used as a general Collection
class, to maintain your Models data, and also function as a Model for a
ListPresenter.

I can't see that such a modified class would be of any extra use though. The
ListModel's main task in life must be to as the Model for a ListPresenter.
If it's behaviour in this role is not acceptable for your model than you
should really maintain the models data in it's own Collections and interface
with the ListModel's list using events.  I really can't see that enhancing
the ListModel in this way will gain anything.

Personally, I very rarely use ListModels in my models now but instead use
normal collections that trigger events in the way I mentioned before. It
makes life a lot easier, especially if you are going to share your model's
data amongst different MVP triads.

Having said all that I must say that I have found it very difficult to think
of any concrete reasons (other than a gut feeling!) why it is a bad idea -
so maybe it isn't <g>. Anyone else?

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Chris Uppal-3
[Warning, the following is pretty long]

Ian, Dmitry,

> Personally, I very rarely use ListModels in my models now but instead use
> normal collections that trigger events in the way I mentioned before. It
> makes life a lot easier, especially if you are going to share your model's
> data amongst different MVP triads.

I agree.  I have only two ListModels left in any capacity (except internally
by ListPresenters), and they are both used as the model of the top Shell of
the application (and they cannot be shared).  I'm not really happy with
either of those uses, but there doesn't seem to be a strong enough reason to
change, at least not yet.

I think that part of the problem with ListModel is that it is attempting to
be two (or more) things at once:

1)  It is a Decorator (in the sense of the GoF pattern's book) for
SequenceableCollection instances which adds event generation, so that
clients may be Observers of modifications to the list.

2) It is a Decorator which add the ability to tell a SequenceableCollection
to sort itself "in place".

3) It is some sort of adapter which provides the insulation between the
collection managed by ListPresenter and some Collection which is part of the
application model.

I think we could gain by separating out these concerns.  The following is a
design exercise (or, really, only a sketch), generalising what I think
Dmitry was getting at; I'd be interested to hear what people think, and also
whether anyone thinks it worthwhile petitioning OA to add something like it
to the standard image.

I'm going to use new class and event names for everything, but for real use,
we should probably try harder to retain backward computability.

First we need an event-generating Decorator for Collections.  This is
intended for general use, not just to support ListPresenter, so I'd be
inclined to generalise ListModel's role and allow it to decorate any
collection.  I'll call it EventGeneratingCollection (I don't claim it's a
good name); it wraps any Collection and generates the following events:

    #itemAdded:
    #itemAdded:key:
    #itemRemoved:
    #itemRemoved:key:
    #itemChanged:
    #itemChanged:key:
    #collectionChanged

#itemAdded: is generated whenever an item is added (gosh!).

#itemAdded:key: is generated whenever an item is added to a keyed or indexed
collection, the "key" is either the real key (for a Dictionary or similar),
or the <Integer> index for an indexed collection (like Array).

If #itemAdded:key: is generated then #itemAdded> is *also* generated (but
not necessarily the reverse, e.g. if the underlying collection is a Set).

#itemRemoved: and #itemRemoved:key: follow the same pattern as
#itemAdded:[key:].

#itemChanged:key: is generated when #at:put: (or similar) is sent to the
underlying collection, and is only meaningful if the collection is keyed or
indexed.  #itemChanged: is never generated by the wrapper itself (it can't
know when it has happened), but it can be triggered off the wrapper object
by client code which "knows" that it has mutated an element of the
collection.  (Not all client code will need to do this, of course).

#collectionChanged is generated when the wrapper is given a new list to
wrap.  I'm a bit unhappy with this idea, and I suspect that there may be
better approaches to changing the whole collection -- see below.

Note that the above, besides generalising ListModel, also fixes a bug in its
design.  When an item is added to a ListModel, it generates
#item:addedAtIndex:, although the index is logically unnecessary (since one
can always scan the collection to find the new item).  OTOH, when an item is
removed, it only generates #itemRemovedAtIndex:, so you don't know what item
has been removed!   (This has prevented me using ListModel in this role as a
wrapper before.)

In the above, I've assumed that there was just one EventGeneratingCollection
class; in fact that might be difficult to implement (E.g. the existing
ListModel depends on the underlying collection understanding
#addAnsweringIndex:).  So I suspect that we'd need two, or maybe three,
wrapper classes which know how to wrap the major categories of Collection.
In that case I'd add a factory method to Collection which (appropriately
specialised by subclasses) answered a new event-generating wrapper (of the
appropriate concrete class) around the receiver. Call it
#asEventGeneratingCollection; e.g:
SequenceableCollection>>asEventGeneratingCollection would:
    ^ EventGeneratingIndexedCollectionWrapper on: self.

And, of course, an EventGeneratingCollection instance would just:
     ^ self.

If we go this route (using several classes hidden behind a factory method),
then we have a problem with emulating ListModel>>list: (which changes the
ListModel's underlying list).  I can think of a few approaches:

a) Allow the method, but make it fail if the new collection is not of the
right general type.  (I don't like this, although it's essentially what
ListModel does).

b) Remove the method entirely.  In this case, for example,
ListPresenter>>list: would have be (re-)implemented something like (ignoring
the issue of sorting for the moment):

    self model: list asEventGeneratingCollection.

(Which I *think* would be enough to cause the ListView to reconnect its
events to the new model)  On the whole I like this approach, it is simple
and clean.  It may not be backward-compatible *enough*, though.  Note that
the #collectionChanged event would not be needed.

c) Provide some direct way to copy the events collection of the old wrapper
to the new one.  In the extreme case we could just use #become:.  This feels
like a kludge to me.

That's all I have to say about the event-generating wrapper.  Next thing is
my second of ListModel's uses:

    It is a Decorator which add the ability to tell a SequenceableCollection
    to sort itself "in place".

I think we need a separate wrapper for this.  I'll call it
SortingAndFilteringCollectionView; it provides a view (in the database
sense) of an existing collection with a sort criterion and/or filtering
criterion applied to it.  Filtering isn't part of what ListModel does, but
it would be very useful (I've wanted it several times), and it would be easy
and efficient to implement in the same class.

A SortingAndFilteringCollectionView would be created on an existing
collection, it would need to wrap that collection in the event-generating
wrapper (since it needs to know about changes to the collection) so the
first thing it'd do is send #asEventGeneratingCollection to the collection
it has been asked to wrap.  It would maintain one or both of a sortblock and
a filter block.  The sort block is a <diadicValuable> which, if present, is
used to sort the elements of the underlying collection.  The filter block is
a <monadicValuable> which, if present, is applied to the elements of the
underlying collection, and only those items for which it answered true would
be visible members of the wrapper collection.

I don't see any way to implement this without giving it a separate internal
OrderedCollection/SortedCollection which is built from the underlying list
by #select-ing the filtered items and sorting them using the sort block.  It
would then use the change notifications from its underlying collection
(which, remember, is wrapped in an event-generating decorator) to maintain
this derived copy.  It sounds expensive, but I think that it would just be
rationalising all the places where we do this kind of duplication in an
ad-hoc way at present.  (It's the absence of this internal shadowed
collection in ListModel that makes it particularly "broken" for general use,
and is -- as I understand it -- what Dmitry is trying to fix.)

I haven't really thought through what events a
SortingAndFilteringCollectionView should generate, but probably the best
thing would be for it to generate the same set of events as the
EventGeneratingCollection (including #collectionChanged).  Note that adding
an item might not generate an event unless the item was also passed by the
filter block.  Also the "key" of the item{Added/Changed/Removed}:key: events
would be the numeric index in the sorted "virtual" collection.  It might be
best for this wrapper not to provide #at:put: (and similar), just #add:, and
(possible) #addFirst: and #addLast::.

Note that this class would also, implicitly, be acting as an Adapter, since
it would give non-indexed classes like Sets an interface which allowed them
to be used in contexts which expect a SequenceableCollection (and
ListPresenter is one such context).

A case could be made that we should really have two wrapper classes for
these purposes -- one for sorting and another for filtering.  My gut feeling
is that the advantages would be small compared with the disadvantage of
possibly requiring two shadow collections.

Now, all the above is fine as far as it goes, and I think it would be
generally useful.  In fact the only reason why I don't already have this
stuff in my image is the difficulty of extending the ST-80 collection
classes.  (The ST80 hierarchy just wasn't factored to facilitate
extension -- take a look at the Java2 Collections to see one example how a
Collection hierarchy can be designed to make this easy.  I have, in fact,
tried to add extensions like these before on several occasions; but each
time I've given up and used a less general approach instead.)

The remaining question is what to do about ListPresenter (and the various
list View classes)?

At one extreme we could just leave them as they are, with an internal
ListModel.  ListModel itself would unchanged, but be deprecated as only
intended to be used internally by ListPresenter (and the views).  (And that
is essentially what Ian and I already do, since we don't use ListModels
outside of ListPresenter triads).

At the other extreme, we could remove ListModel, and change the
implementation of ListPresenter (and views) to use the new stuff.   In this
case, it would be "normal" for a Model with an embedded collection to use an
EventGeneratingCollection, and expose an interface to get that wrapper.
ListPresenter would use another layer of wrapping around it (a
SortingAndFilteringCollectionView which would allow it to sort the
collection it displays without affecting the underlying model)  This would
have several advantages, but would not be backward compatible.

An intermediate position would be to retain ListModel, it would be
deprecated, but retained in it's old form for backward compatibility.  The
ListPresenter (and views) interfaces would be extended so that new methods
were used to tell them to use the new wrapper classes, but if the old
interfaces were used then they would know that the supplied ListModel should
be used and adapted in some way which I haven't thought about.

A second intermediate position would be again to retain ListModel for
backward compatibility (deprecated), but change it to use the new wrapper
classes internally and expose both the old and new event sets.  In this case
ListPresenter, etc, could probably use the new wrappers and ListModels
interchangeably.  This sounds like the best approach, but would be quite a
lot of work to set up, and I'm not even sure that it's possible.

Oh, well.  I warned you that this post would be long.  (You make take
comfort in the reflection that if it hadn't been so long, then I'd have had
time to write a different, but still pretty long, post in the DateAndTime
thread; so you'd have had that to read instead. ;-)

I hope it's been of some interest to someone.  I really would be interested
in what other people think about this and whether it could be worth taking
forward.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Ian Bartholomew-4
Chris,

A very interesting post. Thanks.

A few thoughts about your suggestions. I won't quote from your post as the
following is pretty general and doesn't address specific points.

My first thought was to question whether it might be possible to go one step
further with EventGeneratingCollection and actually implement the behaviour
in the Collection classes themselves. The action of triggering an event has
very little overhead if no actions have been registered (and could be
further optimised if needed) so modifying Collections to trigger events
would have little or no adverse effect to existing code. The extra
functionality might also provide useful leverage for extending/modifying the
behaviour of existing applications.

But ....

Whilst I completely agree with the concept of a ListPresenter being able to
handle any sort of Collection, or rather any EventGeneratingWrapper, I would
have thought it would cause a problem for non-sequencable collections - Set
for example. Say you were displaying a Set using a ListPresenter and a new
item was added to the Set. After refreshing the list the order of items will
almost certainly have changed, making the displayed list churn about
somewhat disconcertingly. In the same vein, I think that non-sequencable
collections might also prove a problem for ListViews as (IIRC) the
underlying control only needs to access the items that are currently
visible, and asks for them by index.

So ....

The answer would seem to be buffering non-sequencable collection in a
sequencable collection maintained by the EventGeneratingCollection. However,
this would get messy if you then had a SortingAndFilteringCollectionView
wrapping that as well (two buffering collections) and would exacerbate the
conversion problem you mentioned.

So (rough musings) ....

How about a combined EventGeneratingWrapper /
SortingAndFilteringCollectionView that had the following behaviour -

- A single class. Not as elegant as the Factory scheme you suggested but
    more flexible if the underlying Collection is changed.
- Conversion methods (#asSet, asSortedCollection etc) that change the
    wrapped collection but leave the events registered for the wrapper
    alone.
- 3 modes of operation (selected automatically when the wrappers state or
    wrapped class is changed)
    o Transparent. Used for OrderedCollections, Arrays etc that can use
        the wrapped collection
    o Ordered. Used for Set, Bag, Dictionary?. Maintains a buffered
        OrderedCollection of the wrapee (where new items are added at
        the end).
    o Sorted. As Ordered but maintains a SortedCollection. Can also be
        used for Transparent if sorting required
- (deprecated) ListModel interface. Can be connected to a ListPresenter in
    the same way (events triggered, methods supported) as an existing
    ListModel. but..
- Switchable (via subclassing?) so that an "enhanced" interface could be
    used if connected to enhanced ListPresenter.

For the "Enhanced interface" version I'm not sure I like the two-event-type
format you suggested, especially if both events can be triggered by some
operations. I would have thought a single event, with the #key: argument
being set to nil where applicable, would be easier to handle.  I suppose the
above gets around this by only presenting a sequence collection to the
presenter.

What about filtering ....

I'm not so sure about this either. Filtering seems more like a Model
function to me with the List MVP always displaying the complete list it is
given.

And finally .....

If we are changing ListModel (the royal we <g>) can we also think about
altering the existing behaviour when a ListModel doesn't replace it's
collection if it thinks it hasn't changed (see ListModel>>list:). That can
be most confusing!.

The more observant of you will have noticed that this is now very close to
Dmitry's original suggestion of adding another buffered collection to
ListModel, which I said I didn't think was a good idea!. We can all be wrong
occasionally, some of us more often than others<g>. I think the difference
is that this in now more of decorator/adaptor providing a generalised
event-enabled collection class rather than just a ListModel used as part of
a ListPresenter MVP.

I would really prefer Chris' approach with two classes though, the outline
above seems too much for just one class. I can't quite see how it would be
implemented without too much redundancy though.

Food for thought anyway.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Dmitry Zamotkin-3
In reply to this post by Chris Uppal-3
"Chris Uppal" <[hidden email]> wrote in message
news:[hidden email]...
> [Warning, the following is pretty long]

Chris,

Your message is a good feed for a brain :). Next time when I dig in your
thoughts, I'll reply more widely, but now there is FilteredListModel
implementation using by mine pretty long.

--
Dmitry Zamotkin
























begin 666 FilteredListModel.cls
M(D9I;&5D(&]U="!F<F]M($1O;'!H:6X@4VUA;&QT86QK(#(P,# @<F5L96%S
M92 T+C Q(B$-"@T*3&ES=$UO9&5L('-U8F-L87-S.B C1FEL=&5R961,:7-T
M36]D96P-"@EI;G-T86YC959A<FEA8FQE3F%M97,Z("=O<FEG3&ES="!F:6QT
M97)";&]C:R<-"@EC;&%S<U9A<FEA8FQE3F%M97,Z("<G#0H)<&]O;$1I8W1I
M;VYA<FEE<SH@)R<-"@EC;&%S<TEN<W1A;F-E5F%R:6%B;&5.86UE<SH@)R<A
M#0I&:6QT97)E9$QI<W1-;V1E;"!C;VUM96YT.B G)R$-"@T*1FEL=&5R961,
M:7-T36]D96P@9W5I9#H@*$=5240@9G)O;5-T<FEN9SH@)WM!-#-!-40Q-"TW
M1C,Q+3$Q1#0M0D$W0BTP,# X0S<X.4$T,C1])RDA#0H-"B%&:6QT97)E9$QI
M<W1-;V1E;"!C871E9V]R:65S1F]R0VQA<W,A56YC;&%S<VEF:65D(2 A#0HA
M1FEL=&5R961,:7-T36]D96P@;65T:&]D<T9O<B$-"@T*861D.B!A;D]B:F5C
M="!A9G1E<DEN9&5X.B!A;DEN=&5G97(-"@T*"45R<F]R(&YO=%EE=$EM<&QE
M;65N=&5D+B$-"@T*8F53;W)T960-"@DB4V]R="!T:&4@8V]L;&5C=&EO;B!H
M96QD(&)Y('1H92!R96-E:79E<B!U<VEN9R!A(&1E9F%U;'0@<V]R="!B;&]C
M:R(-"@T*"6QI<W0@.CT@<V5L9B!L:7-T(&%S4V]R=&5D0V]L;&5C=&EO;BX-
M"@ES96QF('1R:6=G97(Z("-L:7-T0VAA;F=E9"X-"@T*(0T*#0IB95-O<G1E
M9#H@85-O<G1";&]C:PT*"2)3;W)T('1H92!C;VQL96-T:6]N(&AE;&0@8GD@
M=&AE(')E8V5I=F5R('5S:6YG(&%3;W)T0FQO8VLB#0H-"@EL:7-T(#H]('-E
M;&8@;&ES="!A<U-O<G1E9$-O;&QE8W1I;VXZ(&%3;W)T0FQO8VLN#0H)<V5L
M9B!T<FEG9V5R.B C;&ES=$-H86YG960N#0HA#0H-"F9I;'1E<CH@84UO;F%D
M:6-686QU86)L90T*#0H)9FEL=&5R0FQO8VL@.CT@84UO;F%D:6-686QU86)L
M92X-"@ES96QF('!E<F9O;49I;'1E<FEN9RXA#0H-"FQI<W0Z(&%,:7-T#0H)
M;W)I9TQI<W0@.CT@84QI<W0N#0H)<V5L9B!P97)F;VU&:6QT97)I;F<N#0H-
M"@T*(0T*#0IO<FEG:6Y,:7-T#0H);W)I9TQI<W0@:7-.:6P@:694<G5E.B!;
M;W)I9TQI<W0@.CT@<W5P97(@;&ES="!=+@T*"5X@;W)I9TQI<W0A#0H-"G!E
M<F9O;49I;'1E<FEN9PT*#0H)9FEL=&5R0FQO8VL@:7-.:6P-"@D):694<G5E
M.B!;<W5P97(@;&ES=#H@<V5L9B!O<FEG:6Y,:7-T70T*"0EI9D9A;'-E.B!;
M<W5P97(@;&ES=#H@*'-E;&8@;W)I9VEN3&ES="!S96QE8W0Z(&9I;'1E<D)L
M;V-K*5TN#0HA#0H-"G)E;6]V94%F=&5R26YD97@Z(&%N26YT96=E<@T*#0H)
M17)R;W(@3F]T665T26UP;&5M96YT960N(0T*#0IR97-E=$9I;'1E<@T*#0H)
M9FEL=&5R0FQO8VL@.CT@;FEL+@T*"7-E;&8@<&5R9F]M1FEL=&5R:6YG+B$@
M(0T*(49I;'1E<F5D3&ES=$UO9&5L(&-A=&5G;W)I97-&;W(Z("-A9&0Z869T
M97));F1E>#HA*BUN;W0@>65T(&EM<&QE;65N=&5D(6%D9&EN9R%P=6)L:6,A
M("$-"B%&:6QT97)E9$QI<W1-;V1E;"!C871E9V]R:65S1F]R.B C8F53;W)T
M960A<'5B;&EC(7-O<G1I;F<A("$-"B%&:6QT97)E9$QI<W1-;V1E;"!C871E
M9V]R:65S1F]R.B C8F53;W)T960Z(7!U8FQI8R%S;W)T:6YG(2 A#0HA1FEL
M=&5R961,:7-T36]D96P@8V%T96=O<FEE<T9O<CH@(V9I;'1E<CHA;W!E<F%T
M:6]N<R%P=6)L:6,A("$-"B%&:6QT97)E9$QI<W1-;V1E;"!C871E9V]R:65S
M1F]R.B C;&ES=#HA86-C97-S:6YG(7!U8FQI8R$@(0T*(49I;'1E<F5D3&ES
M=$UO9&5L(&-A=&5G;W)I97-&;W(Z("-O<FEG:6Y,:7-T(2HM=6YC;&%S<VEF
M:65D(7!U8FQI8R$@(0T*(49I;'1E<F5D3&ES=$UO9&5L(&-A=&5G;W)I97-&
M;W(Z("-P97)F;VU&:6QT97)I;F<A:&5L<&5R<R%P<FEV871E(2 A#0HA1FEL
M=&5R961,:7-T36]D96P@8V%T96=O<FEE<T9O<CH@(W)E;6]V94%F=&5R26YD
M97@Z(7!U8FQI8R%R96UO=FEN9R$@(0T*(49I;'1E<F5D3&ES=$UO9&5L(&-A
M=&5G;W)I97-&;W(Z("-R97-E=$9I;'1E<B%O<&5R871I;VYS(7!U8FQI8R$@
%(0T*#0H`
`
end


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Chris Uppal-3
In reply to this post by Ian Bartholomew-4
Ian,

Thanks for your responses.

> My first thought was to question whether it might be possible to go one
step
> further with EventGeneratingCollection and actually implement the
behaviour
> in the Collection classes themselves. The action of triggering an event
has
> very little overhead if no actions have been registered (and could be
> further optimised if needed) so modifying Collections to trigger events
> would have little or no adverse effect to existing code. The extra
> functionality might also provide useful leverage for extending/modifying
the
> behaviour of existing applications.

I would be tempted too.  The potential problem I see is that it wouldn't
have "very little overhead" for Collections which used the standard event
regisry mechanism, so Collections would have to be like Models (etc) in
having an events instvar.  That would cause some overhead too, though.  Here
are instance-counts for the top 10 Collections classes from my image:

String:                         112,035
Array:                           26,292
ByteArray:                    18,234
Symbol:                        15,689
MethodDictionary:           2,642
IdentityDictionary:           2,566
Semaphore:                    2,230
WeakIdentitySet:            2,088
WeakArray:                    1,761
IdentitySet:                       563

I don't really think it would be OK to add instvars to all of these.  OTOH,
the big 4 are, in one way or another, not ones where we would want to add
the automatic event generation.  Strings are (nearly) immutable.  Symbols
*are* immutable.  Arrays and ByteArrays are probably too primitive for this
kind of enhancement (and, I suspect the VM wouldn't be happy if they gained
instavrs either).  But all the more "sophisticated" Collections look like
candidates for the enhancement.  Awkward just to add it to some of the
heirarchy, though.

Certainly, if OA decided they wanted to go in that direction, then I'd be
pleased, and would see it as another step in keeping Dolphin an advanced and
sophisticated Smalltalk environment.

> Whilst I completely agree with the concept of a ListPresenter being able
to
> handle any sort of Collection, or rather any EventGeneratingWrapper, I
would
> have thought it would cause a problem for non-sequencable collections -
Set
> for example.

Aha!  I think you missed a sublety ;-)  In the scheme I was exploring,
ListPresenter (and views) always wrap the supplied collection in a
SortingAndFilteringCollectionView, which -- amongst other things, acts as an
adaptor to give a non-SequencableCollection an indexed interface.  (Not that
that was one of my specific design aims for the class, but I don't think
there's any way to implement it which doesn't make it simple/inevitable that
it works in that way; and it turns out to be essential for ListPresenter,
which is all to the good).

> Say you were displaying a Set using a ListPresenter and a new
> item was added to the Set. After refreshing the list the order of items
will
> almost certainly have changed, making the displayed list churn about
> somewhat disconcertingly

Well, I've partially answered that.  Also I don't think the order in which
items are displayed in Lists (including the non-ordered, iconic, ones), is,
or should be, strongly dependent on the order of the underlying collection.

You have made me realise, though, that there may be times when you *do* want
this dependency to be maintained.  I *think* that the current version of my
scheme will usually handle that (since the initial/default order in the
ListPresenter -- unless it already applied some sorting -- would obviously
be the native #do: order of the underlying collection).  There may be
problems, though, where the Model maintains *and changes* the order of items
in its list, and expects Views to propgate that changing order to the user.
It may be that #collectionChanged would be enough to deal with this case,
but I'm not yet convinced.

> In the same vein, I think that non-sequencable
> collections might also prove a problem for ListViews as (IIRC) the
> underlying control only needs to access the items that are currently
> visible, and asks for them by index.

I'd call that an implementation detail.  Fortunately, since I'd like the
scheme to be implementable, it is handled anyway -- as I've described.

> The answer would seem to be buffering non-sequencable collection in a
> sequencable collection maintained by the EventGeneratingCollection.
> [elaboration snipped]

If I haven't missed your point(s), then I think I've shown that this isn't
necessary.

> For the "Enhanced interface" version I'm not sure I like the
two-event-type
> format you suggested, especially if both events can be triggered by some
> operations. I would have thought a single event, with the #key: argument
> being set to nil where applicable, would be easier to handle.  I suppose
the
> above gets around this by only presenting a sequence collection to the
> presenter.

Probably a matter of taste.  I see the triggered events as part of the
"interface" of a class, and further see the wrapper for a keyed collection
as a "subclass" (i.e. extending the interface of) the wrapper for a
non-keyed collection.  Hence it needs to generate the events appropriate to
both kinds of wrapper.

BTW, I've realised that the events #item{Added/Changed/Removed}:key: would
be much more reasonably called #item{Added/Changed/Removed}:at:.

> What about filtering ....
>
> I'm not so sure about this either. Filtering seems more like a Model
> function to me with the List MVP always displaying the complete list it is
> given.

I think it depends on the filtering.  In some cases (but I can't think of
any offhand) it will clearly be a part of the structure of the Model (or
other non-presentation logic).  There are lots of other cases where filters
are applied by the user in much the same way as s/he would change the order
of items, just so as to see the data more clearly.  I've got just two
examples of this in my own code to date (though stuff in the standard
environment come very close to the same idea), but this is mainly because
I'd have had to implement it from scratch each time, and have only thought
it worthwhile twice.

Actually, I think that user-controlled filtering is a very powerful UI
concept which should be more widely applied.  E.g. you may (I don't know)
think of the list of methods shown in the CHB as being *selected* by the
selection in the Categories pane.  I'd say that a more fruitful (in that it
suggests useful generalisations) way of seeing it is that the Categories
pane has a bunch of filters which can be applied to the list of methods.

You may remember the "Dropping Filter" suggestion I posted a year (or more)
ago.

> And finally .....
>
> If we are changing ListModel (the royal we <g>) can we also think about
> altering the existing behaviour when a ListModel doesn't replace it's
> collection if it thinks it hasn't changed (see ListModel>>list:). That can
> be most confusing!.

I'll go along with that.  I don't think we need all the superstructure we've
been talking about to achieve it though...

> Ian

    --chris


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Ian Bartholomew-4
Chris,

> I would be tempted too.  The potential problem I see is that it wouldn't
> have "very little overhead" for Collections which used the standard event
> regisry mechanism, so Collections would have to be like Models (etc) in
> having an events instvar.  That would cause some overhead too, though.

Yesterday I did some testing to check out the effect of an extra trigger in
the #add method of OrderedCollection and found that it made a minimal
impact, hence my original comments.

Today, while preparing this reply, I discovered that I made a rather silly
(or very stupid if I'm honest) mistake in my testing code and that in fact
the extra trigger does cause a large (~20x) overhead on the execution time
of a simple #add. Sigh!. I was wondering why OA added #events to Model as
the base method had so little overhead - I've answered that question for
myself as well now.

Sorry for the incorrect assumptions. Your reasoning about how the overhead
could be reduced by implementing in a selected subset of classes makes sense
but EventGeneratingCollection with, I assume, it's own #events instVar seems
like a safer way to go.

> Aha!  I think you missed a sublety ;-)  In the scheme I was exploring,
> ListPresenter (and views) always wrap the supplied collection in a
> SortingAndFilteringCollectionView, which -- amongst other things, acts as
> an adaptor to give a non-SequencableCollection an indexed interface.

Right - I had missed that (sublety is not my strong subject <g>). That does
remove most of the questions I thought of and makes the design a bit
clearer. The only question I would ask now is whether you were considering
ways to avoid the buffering of the collection when it is not needed. When
the wrapped collection is itself an OrderedCollection for example?.

> If I haven't missed your point(s), then I think I've shown that this isn't
> necessary.

Yes, completely.

> Probably a matter of taste.  I see the triggered events as part of the
> "interface" of a class, and further see the wrapper for a keyed collection
> as a "subclass" (i.e. extending the interface of) the wrapper for a
> non-keyed collection.  Hence it needs to generate the events appropriate
> to both kinds of wrapper.

I was just concerned that if you changed the class of the underlying
collection, which you mentioned in your original post, then you might have
to check for invalid triggers as well. If I haven't missed some other
subtlety <g> an EventGeneratingCollection wrapping a Set would trigger
#itemAdded whereas an EGC on an OrderedCollection would trigger #itemAdded:
and #itemAdded:at:. If you registered for the latter event symbol in a
Presenter then changing the underlying collection from an OrderedCollection
to a Set would also necessitate changing the code in the Presenter.

It's a minor point though and easily worked around, I think?

> I think it depends on the filtering.  In some cases (but I can't think of
> any offhand) it will clearly be a part of the structure of the Model (or
> other non-presentation logic).

How about the "local hierarchy" button on the CHB toolbar?. I think that is
a non UI filter as, IIRC, it is the Model that is changed to browse a subset
of the hierarchy rather than the TreeView just filtering out the unwanted
classes

I suppose the MethodBrowser, where you pass a filter block than is reapplied
if the list is changed, is another example of a UI filter and closer to the
scheme you suggest.

> Actually, I think that user-controlled filtering is a very powerful UI
> concept which should be more widely applied.  E.g. you may (I don't know)
> think of the list of methods shown in the CHB as being *selected* by the
> selection in the Categories pane.  I'd say that a more fruitful (in that
> it suggests useful generalisations) way of seeing it is that the
> Categories pane has a bunch of filters which can be applied to the list
> of methods.

I think the concept of filters, used in the way you describe, is something I
would have to try before understanding it's potential.  I can recall running
into problems on the few occasions that I've tried writing filters for the
MethodBrowser but having the filter and collection closely coupled in a
SortingAndFilteringCollectionView might well make it easier.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Ted Bracht-2
Hi Ian, Chris,

Please don't come to the conclusion by the lack of replies from other
people that this 1-2 between you two means that no-one else is
interested. Speaking for myself, I recognise the issues that you bring
up and have been struggling with them myself as well. Therefore I
follow your discussion closely, I just don't have anything sensible to
add at this level of detail <g>.

Ted


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Chris Uppal-3
In reply to this post by Ian Bartholomew-4
Ian,

> > [...] it needs to generate the events appropriate
> > to both kinds of wrapper.
>
> I was just concerned that if you changed the class of the underlying
> collection, which you mentioned in your original post, then you might have
> to check for invalid triggers as well.

Hmm, yes.  Well part of the answer is that the more I think about it the
more I feel that there's something inherently arse-backwards about changing
the underlying Collection of an existing wrapper.  The other half of my
answer follows:

> [...] an EventGeneratingCollection wrapping a Set would trigger
> #itemAdded whereas an EGC on an OrderedCollection would trigger
#itemAdded:
> and #itemAdded:at:. If you registered for the latter event symbol in a
> Presenter then changing the underlying collection from an
OrderedCollection
> to a Set would also necessitate changing the code in the Presenter.

The way I'd expect it to work is that if you were interested in *where*
items were in the collection, then you would necessarily be wrapping an
indexed or keyed collection, and the #item{A/C/R}:at: events would be what
you *needed* to listen for.  If, OTOH, you were only interested in the
contents of the collection then you'd listen for the #item{A/C/R}: events,
and wouldn't care (and possibly wouldn't know) whether the underlying
collection was a Set or an OrderedCollection or what.

There are, BTW, many, many places in my code where I simply don't care
whether the collections I'm creating and passing around are
OrderedCollections or Sets. (It'd be nice if there was slightly more overlap
between these interfaces, e.g. if Set>>first existed, or --
alternatively -- if all Collections had an #any method).

> The only question I would ask now is whether you were considering
> ways to avoid the buffering of the collection when it is not needed. When
> the wrapped collection is itself an OrderedCollection for example?.

I'm unsure, to be honest.  My immediate impulse would be to go for the
simpler implementation and build the shadow list unconditionally; but
there's not that much (code) overhead for postponing it until needed (when
the list is actually sorted or filtered), and seems to be possible to use
Very Large collections in a List triad these days, so the gain might well be
worthwhile.

> How about the "local hierarchy" button on the CHB toolbar?. I think that
is
> a non UI filter as, IIRC, it is the Model that is changed to browse a
subset
> of the hierarchy rather than the TreeView just filtering out the unwanted
> classes

Yes, that's an example of the kind of thing I'm thinking about.  I think you
are right and it is implemented somewhere in the TreePresenter's TreeModel
(I haven't checked either), but I don't think that's the right place for the
functionality -- it seems like a presentation issue to me.

> I suppose the MethodBrowser, where you pass a filter block than is
reapplied
> if the list is changed, is another example of a UI filter and closer to
the
> scheme you suggest.

Yes, again.

Or, indeed, the famous ChunkBrowser by a certain gentleman not a million
miles from here...

> I think the concept of filters, used in the way you describe, is something
I
> would have to try before understanding it's potential.

Without having tried it myself (yet) it looks as if Dmitry's implementation
would give you something to play with.

> Ian

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Sort in a ListModel [LONG!]

Chris Uppal-3
In reply to this post by Dmitry Zamotkin-3
Dmitry,

Thanks!  That looks like just the kind of thing I had in mind.  It's simpler
than I expected it to be too.

BTW, are #{add/remove}AfterIndex: needed by ListPresenter/ListView ?  I
noticed that you made them #notYetImplemented, rather than
#shouldNotImplement, and was wondering why ?

Also, I only saw your post on the news server I use from home; the free one
I use from work didn't carry it (presumably because of the attachement), so
I've taken the liberty of reproducing your code here.  I hope you don't
mind.

> Dmitry Zamotkin

    -- chris

==========
The following code is by Dmitry Zamotkin, posted 2001-07-09.  Reproduced
here (without permission ;-) because not all NNTP hosts have carried it.
==========

"Filed out from Dolphin Smalltalk 2000 release 4.01"!

ListModel subclass: #FilteredListModel
 instanceVariableNames: 'origList filterBlock'
 classVariableNames: ''
 poolDictionaries: ''
 classInstanceVariableNames: ''!
FilteredListModel comment: ''!

FilteredListModel guid: (GUID fromString:
'{A43A5D14-7F31-11D4-BA7B-0008C789A424}')!

!FilteredListModel categoriesForClass!Unclassified! !
!FilteredListModel methodsFor!

add: anObject afterIndex: anInteger

 Error notYetImplemented.!

beSorted
 "Sort the collection held by the receiver using a default sort block"

 list := self list asSortedCollection.
 self trigger: #listChanged.

!

beSorted: aSortBlock
 "Sort the collection held by the receiver using aSortBlock"

 list := self list asSortedCollection: aSortBlock.
 self trigger: #listChanged.
!

filter: aMonadicValuable

 filterBlock := aMonadicValuable.
 self perfomFiltering.!

list: aList
 origList := aList.
 self perfomFiltering.


!

originList
 origList isNil ifTrue: [origList := super list ].
 ^ origList!

perfomFiltering

 filterBlock isNil
  ifTrue: [super list: self originList]
  ifFalse: [super list: (self originList select: filterBlock)].
!

removeAfterIndex: anInteger

 Error NotYetImplemented.!

resetFilter

 filterBlock := nil.
 self perfomFiltering.! !
!FilteredListModel categoriesFor: #add:afterIndex:!*-not yet
implemented!adding!public! !
!FilteredListModel categoriesFor: #beSorted!public!sorting! !
!FilteredListModel categoriesFor: #beSorted:!public!sorting! !
!FilteredListModel categoriesFor: #filter:!operations!public! !
!FilteredListModel categoriesFor: #list:!accessing!public! !
!FilteredListModel categoriesFor: #originList!*-unclassified!public! !
!FilteredListModel categoriesFor: #perfomFiltering!helpers!private! !
!FilteredListModel categoriesFor: #removeAfterIndex:!public!removing! !
!FilteredListModel categoriesFor: #resetFilter!operations!public! !

===========