Search code examples
user-interfaceobserver-patternsmalltalksqueak

How to update single key/value from dictionary in morph?


In a previous question I asked how I could show the contents of a Dictionary in a GUI. I started from this idea to build a GUI with a slightly better look and feel. It mainly consists of RectangleMorphs glued together in columns and rows (cfr. the accepted answer in my previous question).

The problem now is that I would like my table to be updated when elements are added/removed/edited in my dictionary. I managed to write some Morph that consists of columns of CellMorphs, which inherit from RectangleMorph and have model and message as instance variables with the following update message:

update
    " update the contents of this cell "

    | value |
    self removeAllMorphs.
    (model = nil or: message = nil) 
        ifTrue: [ value := '' ]
        ifFalse: [ value := model perform: message ].
    self addMorph: value asMorph.

As can be seen, the CellMorph is a container for a Morph containing the actual content of the cell. This works great for displaying the size of the dictionary for instance:

d := Dictionary new.
d at: 'foo' put: 100.
d at: 'bar' put: 200.

cell := CellMorph new
    model: d;
    message: #size;
    color: Color white.
cell openInWorld.

d at: 'boo' put: 300.  " cell will be updated "

but I don't seem to get something similar working for the contents of the dictionary, because I can't find a way to access single keys or values with a message. The only solution I can think of is to create new columns with new cells every time, but this is so expensive and I can't imagine that this is a good idea...

Therefore my question:
Is there a way to update my Morph displaying the dictionary without creating billions of my CellMorphs or should I forget about my idea and rather work with rows of CellMorphs for instance in order to group the entries in the dictionary?


for completeness: the model: message in CellMorph looks like:

model: newModel
    "change the model behind this cell"

    model ifNotNil: [ model removeDependent: self ].
    newModel ifNotNil: [newModel addDependent: self].
    model := newModel.
    self update.

update: aParameter does nothing more than call update. and I also added self changed. in all messages of Dictionary that I want the interface to be notified of (at: put:, removeKey:, etc.).


Solution

  • In the instance variable named 'message' you could have a Message object, instead of having only the selector. An instance of Message has the receiver, selector and arguments. So, you could configure it with the dictionary keys sorted asArray in the receiver, the selector #at: and an index, to get a specific key. Accessing the value would be getting the value at: that key in the dictionary.

    I think that a Message is not executed with object perform: message, you should check. message perform should work because it already has the receiver.

    In any case, this complexity may show that having only (one) model and (one) message is not enough to get the model in th granularity you want, and you can possibly specialize a bit more, using the knowledge that the model is a dictionary. For instance, having an instance variable for key or for keyIndex.

    Some side notes about the code:

    (model = nil or: message = nil) 
    

    has comparisons with nil, that can be replaced by #isNil message or, if you want to stick with equality, use the faster == to compare identity, since nil is unique.

    #or: is used to get the benefits of partial evaluation (the argument is evaluated only if the receiver is false). But that only works if you have a block as argument, otherwise the expression is evaluated before, to get the argument for the message in the stack.