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 |
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 > > |
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 |
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 |
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 |
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 |
[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 |
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 |
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 |
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 |
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 |
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 |
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 |
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! ! =========== |
Free forum by Nabble | Edit this page |