could we add that to the pharo online book ?

On Mon, Sep 20, 2010 at 12:15 PM, Gary Chambers
<[email protected]>wrote:

> Well, for the most part they do, aside from the "low-level" #mouseUp etc.
> Though the "Views" would be idomatically cleaner, they'd also be rather
> less flexible.
> Always a trade-off somewhere ;-)
>
> As for changing the #changed mechanism, good luck! (massive job).
>
> Regards, Gary
>
> ----- Original Message ----- From: "Stéphane Ducasse" <
> [email protected]>
> To: <[email protected]>
> Sent: Sunday, September 19, 2010 10:59 AM
> Subject: Re: [Pharo-project] A pattern for GUI programming (Morphic,
> Cuis,etc)
>
>
>
>  Gary
>>
>> what do you think about the main point of juan that View do not raise
>> event to communicate to their models.
>> I like that.
>>
>> Stef
>>
>> On Sep 18, 2010, at 3:23 PM, Juan Vuletich wrote:
>>
>>  Hi Folks,
>>>
>>> I have developed a GUI programming style, after studying MVC, Morphic,
>>> MVP and a few others. The model does not know anything about views, there
>>> are no unneeded redraws, partial updates work correctly, etc. It is
>>> implemented in the LightWidget hierarchy in Cuis. The documentation I wrote
>>> is attached.
>>>
>>> Cheers,
>>> Juan Vuletich
>>> GUI programming with LightWidgets
>>> ==========================
>>>
>>> Warning: Perhaps it is good to read
>>> AnExampleOfLightWidgetsProgramming.txt prior to reading this...
>>>
>>> This document summarizes the way LightWidgets are intended to be used.
>>> The style for GUI programming is based on PluggableViews in MVC and
>>> PluggableMorphs in Morphic. The main idea is to have a reusable set of
>>> standard widgets that can be customized when used. There is a strict
>>> separation between views and models. Models don't know about views, they are
>>> never aware of them. Views know about their model and update it directly.
>>> Therefore views don't trigger events.
>>>
>>> This description is not only conceptual, or theoretic. The rules
>>> described here are to actually be followed.
>>>
>>> GUIs are built by composing widgets. The main view is a subclass of
>>> CompositeLW. There is a complete separation between Model and Views.
>>>
>>> Rule 1. Models should never include GUI code
>>> ----------------------------------------------------------
>>>
>>> They must be completely ignorant of possible Views that operate on them.
>>> There could be at any time any number of different views active on the same
>>> model. They could belong to different technologies or frameworks. They could
>>> even be remote and run on a different computer. There could be no view at
>>> all. For example, the model could be driven by scripts or reside on a server
>>> and receive external commands. However, this document will only describe
>>> local LightWidgets GUIs.
>>>
>>> Rule 2. Views should never include any model code
>>> ---------------------------------------------------------------
>>>
>>> The view could be replaced anytime with a different one. Besides, a model
>>> should be able to run without any GUI at all. So any logic that belongs in
>>> the model but is included in the GUI will eventually be missing. The Views
>>> should query and modify Models only through public protocols, called
>>> 'Inquiries' and 'User Commands'.
>>>
>>> Rule 3. Views know about the model they operate on
>>> -------------------------------------------------------------------
>>>
>>> Views have an instance variable to hold their model. They can query Model
>>> Inquiries when needed. They can also issue User Commands when appropriate.
>>> Models are usually subclasses of ActiveModel. Let's consider a small
>>> example. We are building a GUI to operate on some Person objects. We'll
>>> consider an EntryField for the birthday of aPerson. LightWidget includes the
>>> following instance variables:
>>>
>>> - target : Holds the Model. For our example, the model of the EntryField
>>> would be the Person. We call it target, because sometimes it might be a view
>>> - aspect : It is a symbol, the getter for the aspect we are showing. In
>>> this case it would be #birthday.
>>> - aspectAdaptor : It is a symbol, a message that is sent to the aspect to
>>> adapt it to the widget. As the widget is an EntryField and the aspect is a
>>> Date, the aspectAdaptor could be #asString. This relies the Model from the
>>> need to provide an appropriate getter for each kind of possible widget for
>>> each attribute.
>>> - action : The action is the setter used to update the aspect on the
>>> model. In this example, it is #birthday:.
>>> - actionAdaptor : It is used to adapt the value the user entered in the
>>> widget for use as an argument of action. In this example it could be
>>> #asDate.
>>> It is usually a good idea to initialize model aspects with reasonable
>>> defaults, and avoid nil values. This saves a lot of #ifNil: messages in the
>>> gui.
>>>
>>> Rule 4. View Structure
>>> ----------------------------
>>>
>>> A Model could have a tree-like structure. It could be composed of other
>>> Models. This is not mandatory.
>>> Views always have a tree-like structure. The leaves are simple widgets.
>>> The internal nodes are CompositeLWs. They can all share the same model, or
>>> they could could use different parts of the bigger model. Anyway, they are
>>> customized with the aspec and action.
>>>
>>> Rule 5. GUI construction
>>> ------------------------------
>>>
>>> The construction of the Views tree and the customization of each widget
>>> is done by a main view. The main view also specifies how the Views are
>>> notified of model changes for updating.
>>>
>>> Rule 6. Instance variables in views
>>> --------------------------------------------
>>>
>>> Additional instance variables in GUIs are of two kinds: They can be uses
>>> to hold sub-views, or to hold 'Model Extensions'. Possible uses of  Model
>>> Extensions include:
>>> - Holding information that can be obtained from the aspect, but that
>>> could be expensive and it makes sense to cache. For example, our EntryField
>>> could hold an array if indices of word starts and ends or some other
>>> internal detail.
>>> - Holding state that is meaningful for the widget, but that it doesn't
>>> make any sense to keep in the model. An example could be the cursor position
>>> in our EntryField. Others could be visual options, such as a graph type or
>>> graph style for an application generated graph.
>>> - Not-yet-commited information, entered by the user, but awaiting for OK
>>> / Cancel.
>>> In general, Model Extensions usually are re-fetched from the model, or
>>> re-set to default values when the model changes.
>>>
>>> Rule 7. View updating because of model changes
>>> ---------------------------------------------------------
>>>
>>> Any widget (in fact, any Morph) can redraw itself when needed, with the
>>> #changed method. But when there is a change in the Model, the views must be
>>> updated appropriately. All the Model Extensions must be updated, and all
>>> sub-views must be updated too.
>>>
>>> When there is a change in the Model, the Views must receive the
>>> #modelChanged message. A main view (i.e. a view that is not subview of
>>> another view with the same Model) must send itself #beMainViewOn: on
>>> construction. This does 'target when: #selfChanged send: #safeModelChanged
>>> to: self'. The Model must trigger event #selfChanged when appropriate.
>>> #safeModelChanged will eventually update all subviews recursively. So only a
>>> main view should receive the #selfChanged event. Models are usually
>>> subclasses of ActiveModel, to use the more advanced events implementation
>>> there.
>>>
>>> This is the implementation of #beMainViewOn: . This message should be
>>> used to set the model of a main view.
>>>
>>> beMainViewOn: aModel
>>> "We are a main view on aModel.
>>> This means:
>>> - aModel is a real model, i.e. not a widget.
>>> - no aspect or aspectAdaptor. We show the whole thing.
>>> - no action or actionAdaptor. There is no main action.
>>> - we must update ourselves on #selfChanged event"
>>>
>>> self target: aModel aspect: nil aspectAdaptor: nil modelChangeEvent:
>>> #selfChanged
>>>
>>> The main update method is #modelChanged. #safeModelChanged is only to
>>> guarantee that the update is done in the User Interface process, in the
>>> inter-cycle paus. The implementation of #modelChanged at LightWidget is:
>>>
>>> modelChanged
>>> "The model changed is some way.
>>> This is usually the pace to call #targetAspect to fetch the current value
>>> of the aspect from the
>>> model, and to store it in some Model Extension.
>>> We must update all Model Extension instance variables with values from
>>> the model (i.e. target)
>>> or with appropriate defaults.
>>> We must update ourselves and all subviews to reflect the model's new
>>> state"
>>>
>>> self updateView
>>>
>>> #modelChanged must be reimplemented in classes with model extensions.
>>> Check the implementors to see how they work.
>>>
>>> After updating the model and model extensions, #updateView is called.
>>> This is the implementation at LightWidget:
>>>
>>> updateView
>>> "The model or some Model Extension changed is some way.
>>> We must update ourselves to reflect the new state.
>>>
>>> This is the place to update secondary Model Extensions or any other state
>>> that must be updated
>>> after model or Model Extension change.
>>>
>>> This method is usually reimplemented in CompositeLWs, to update subviews.
>>>
>>> The subviews should be sent one of the following messages:
>>> target:
>>> target:aspect:
>>> target:aspect:aspectAdaptor:
>>> target:aspect:aspectAdaptor:aspectChangeEvent:
>>> to update their model and do a full update, as triggered by
>>> #modelChanged"
>>>
>>> self changed
>>>
>>> Warning: Never implement other methods like #updateViews. If for
>>> performance reasons the updating of subviews must be splitted in parts, then
>>> the views and subviews must be restructured accordingly. Then, each part can
>>> be updated as a whole with the #updateView method. Each part can be updated
>>> by more specific model change events, or alternatively, they might be set
>>> different submodels. Both options are described below.
>>>
>>> The update of widgets should never trigger the action of the widget.
>>>
>>> Rule 8. View updating because of Model Extension changes
>>> ------------------------------------------------------------------------
>>>
>>> If the target of a widget is another widget, the action is a User Command
>>> on the target widget. These methods should not update the model, because if
>>> this was the case, the target should be the model and not the widget.
>>> Therefore, User Command methods in widgets can only update Model Extensions
>>> or trigger view actions, such as opening new views, etc. If they update
>>> Model Extensions they should call #updateView, so the change is shown in the
>>> widget and its subviews.
>>>
>>> sampleUserCommand: data
>>> modelExtension1 := data.
>>> self updateView
>>> "Must call updateView because the model didn't change, and it will not
>>> trigger any change event"
>>>
>>> Rule 9. Subview updating because of submodel changes
>>> --------------------------------------------------------------------
>>>
>>> If the model has a tree-like structure, its view will send #beMainViewOn:
>>> aSubModel to some subviews with a part of the model as the argument. In this
>>> case, subviews will need to be notified of the events of their own models.
>>> This is because the submodel might trigger the #selfChanged event, and only
>>> the views on it should be updated. Views on the bigger model don't need to
>>> be updated. This is good for performance when having complex models and
>>> views.
>>>
>>> Rule 10. Subview updating because of model minor changes
>>> -----------------------------------------------------------------------
>>>
>>> There is another reason for subviews receiving event notifications. A
>>> model could trigger a more specific #someAspectChanged event and NOT the
>>> main #selfChanged event. This could be done to avoid superfluous and
>>> extensive views updating. In this case, some specific view on the view tree
>>> should receive the #updateView message, and only the widgets that are part
>>> of it will be updated.
>>>
>>> So, the owing view should send
>>> #target:aspect:aspectAdaptor:modelChangeEvent: to these subviews. The
>>> implementation is:
>>>
>>> target: aModel aspect: aSymbol aspectAdaptor: anotherSymbol
>>> modelChangeEvent: eventSymbol
>>> "Widgets are notified of model changes by being sent #modelChanged.
>>> This happens when:
>>> - The widget is given a new model (or target widget), aspect or aspect
>>> adaptor
>>> - An owner view is updated
>>>
>>> In addition, main views are updated from model events. See #beMainViewOn:
>>>
>>> But other widgets might update on more specific events from the model.
>>> This is useful to
>>> update only a small subview, and not the whole main view.
>>>
>>> This message is sent to such widgets, to set this specific event.
>>>
>>> Warning:
>>> When models change, they should trigger just one event.
>>> It might be #selfChanged (the most general one) or a more specific one.
>>> But it should not trigger more than one event for each change."
>>>
>>> self target: aModel aspect: aSymbol aspectAdaptor: anotherSymbol.
>>> target when: eventSymbol send: #safeModelChanged to: self
>>>
>>> Warning: When a model triggers more specific change events we must make
>>> sure some widget will be notified of them. Otherwise, those changes could
>>> not be shown to the user.
>>>
>>> Pensar un cacho en como actualizar estas cuando ocurra la actualizacion
>>> general. Creo que es justo cuando hay que decirle target:... SI!
>>>
>>> Rule 11. Accessing views
>>> ------------------------------
>>>
>>> Nobody should ever query a widget for value or status. A widget should
>>> not even query itself for current value or such. The last value or state
>>> entered by the user should be stored in the model and/or Model Extensions.
>>> When needed, it should be retrieved from there. The only legitimate accesses
>>> to subviews are in #initialize and in #updateView. Check implementors of
>>> #updateView.
>>>
>>> Rule 12. Model updating
>>> ---------------------------
>>>
>>> Views DO NOT trigger events. This is not "Event Oriented Programming".
>>> This is Object Oriented Programming. The model is updated using the action
>>> and the optative actionAdaptor. Methods that react to user activity should
>>> update the model by just using the action, a simple message. They are not
>>> allowed to ask the model for some other object to work on it. They are not
>>> allowed to send other messages to the model. They are allowed to modify
>>> Model Extensions. If they do, theyshould also call sned 'self modelChanged'
>>> because an action might not modify the model and therefore there could not
>>> be a model change event. See ButtonLW>>mouseUp: for an example of this.
>>>
>>> If you ever feel the need to update the object answered by the aspec,
>>> instead of sending a new value to the model (ivar target), it is because
>>> that aspect should be the real model.
>>>
>>> Rule 13. GUI building
>>> ------------------------
>>>
>>> Main views know about their subviews. Therefore it's them, in theire
>>> #initialize method, who build the subviews and customizes them. Views are
>>> created before assigning target or model to the main view. Afterwards, the
>>> model or target is set, and #modelChanged is called. As seen before, this
>>> will set the model or target of all subviews recursively.
>>>
>>>
>>> Misc. notes
>>> -------------------
>>>
>>> I believe nobody should do #modelChanged, but only #safeModelChanged.
>>> Think a bit about this. Maybe if we're certain we're in the UI process,
>>> #modelChanged is ok...
>>>
>>> If a visual detail like #fontColor: in a LabelLW  is updated, after
>>> updating the ivar, the widget should do 'self changed'. Check the code to
>>> see that it is actually done!An example of LightWidgets programming
>>> ===========================
>>>
>>> The ProgrammingWithLightWidgets.txt document might be a little boring to
>>> read with all those rules. This document, instead, shows the style of
>>> LightWidgets programming based on a concrete example. It focuses on building
>>> application guis, an not on building widgets themselves. GUIs done following
>>> the LightWidgets ideas are very simple. Remembering the rules might seem a
>>> bit rigid, but this avoids complexity in the GUI, making long term
>>> mainteinance easier.
>>>
>>> The example I chose is the Local Users screen in Squeak STB, class
>>> STBLocalUserEditorLW. Model are instances of STBLocalUser.
>>>
>>> Note that even though models are advised to inherit from ActiveModel,
>>> STBLocalUser does not. This shows a general rule: Views don't have the right
>>> to say how models should work. Models are independent of views. In this
>>> case, STBLocalUser inherits from STBModel, the class of persistent objects
>>> in the box.
>>>
>>> Local users are pretty simple objects. They have a userName, a password
>>> (only a passwordHash is stored), and a list of groups the user belongs to.
>>>
>>> The GUI has the following widgets:
>>> - An entry field for the name
>>> - An entry field for the password
>>> - A list of groups the user belongs to. Selecting one and doing <ok>
>>> removes the group from the user
>>> - A list of available groups (groups the user does not belong to).
>>> Selecting one and doing <ok> adds the group to the user.
>>>
>>> In addition, we have:
>>> - A 'Create new User' button
>>> - A list of existing users. Selecting one and doing <ok> edits that user
>>> - A 'Save' button
>>> - A 'Close' button that exist without saving
>>> - A 'Delete' button used to actually delete the currently edited user
>>>
>>> Class STBLocalUserEditorLW has several instance variables for holding its
>>> widgets, one model extension 'password'. and one visual property:
>>> 'backColor'. Instance variable 'backColor' is only there to avoid computing
>>> it each time thescreen is redrawn. Instance variable 'password' is needed
>>> because the STBLocalUser can not answer it.
>>>
>>> The model is an instance of STBLocalUser. However, the list of available
>>> users does not depend on it. This list has content even if no model is
>>> assigned yet. The model is set later, in messages #selectedUser: and
>>> #newUser.
>>>
>>> Initialization
>>> -----------------
>>>
>>> Method #initialize creates all the widgets. It is quite long but it does
>>> not do anything interesting. It just creates the widgets, lays them out,
>>> adds them as submorphs, and stores them in instance variables. It also does
>>> 'self newUser', so the user does not need to click the button before
>>> entering data.
>>>
>>> Note that the target of all buttons is 'self', meaning that user commands
>>> will be processed by the editor itself. In many cases, (as in the name
>>> fields in this editor) the target of the actions would be the model instead.
>>>
>>> drawing
>>> ------------
>>>
>>> Method #drawOn: is there only because the backColor is defined in this
>>> class.
>>>
>>> updating
>>> ------------
>>>
>>> #updateView - This method is called after a new model is set, or if model
>>> changes. It sets labels to appropriate values, updates the current and
>>> available group lists, and sets the STBUser as the target of the name field.
>>> In addition it updates the users list.
>>>
>>> #password - helper method to access the password entered by the user.
>>>
>>> user commands
>>> -----------------------
>>>
>>> #newUser - Creates a new user and sets it as the model of the view.
>>>
>>> #selectedUser: - Sets the selected user as the model of the view.
>>> (Persistent objects note: Persistence is paused, to be resumed in case of
>>> save. If the user cancels, nothing should be persisted!)
>>>
>>> #password: - This is processed here (and not just in the model) to store
>>> the password entered by the user.
>>>
>>> #addGroup:  - This is processed here (and not just in the model) to
>>> handle keyboard focus.
>>>
>>> #removeGroup: - This is processed here (and not just in the model) to
>>> handle keyboard focus.
>>>
>>> #saveUser - This makes changes persistent, and logs stuff.
>>>
>>> #deleteUser - This removes the user from the persistent pool and logs
>>> stuff.
>>>
>>> #cancel - This undoes any changes (by going back to the persisted state),
>>> resumes persistence, and closes the editor
>>>
>>> That's all. It wasn't hard at all, was
>>> it?_______________________________________________
>>> Pharo-project mailing list
>>> [email protected]
>>> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>>>
>>
>>
>> _______________________________________________
>> Pharo-project mailing list
>> [email protected]
>> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>>
>
>
> _______________________________________________
> Pharo-project mailing list
> [email protected]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>
_______________________________________________
Pharo-project mailing list
[email protected]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project

Reply via email to