A pattern for GUI programming (Morphic, Cuis, etc)

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

A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
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
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Stéphane Ducasse
tx for sharing that with us.


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
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Stéphane Ducasse
In reply to this post by Juan Vuletich-4
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
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Stéphane Ducasse
In reply to this post by Juan Vuletich-4
Juan

I was looking a bit at triggerEvent:
and I seein pharo and probably in squeak code like the following one

mouseUp: anEvent
        "Change the cursor back to normal if necessary and change the color back to normal."
       
        self canResizeColumn ifFalse: [^ self].
        (self bounds containsPoint: anEvent cursorPoint)
                ifFalse: [anEvent hand showTemporaryCursor: nil].
        self class fastSplitterResize
                ifTrue: [self updateFromEvent: anEvent].
        traceMorph ifNotNil: [traceMorph delete. traceMorph := nil].
        self adoptPaneColor: self paneColor.
        self triggerEvent: #mouseUp


So how does it fit your model?

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
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Noury Bouraqadi-2
In reply to this post by Juan Vuletich-4
Hi Juan,

Is this document available somewhere on the web? It's important to ensure it is persistence.

Noury
On 18 sept. 2010, at 15:23, 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
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
In reply to this post by Stéphane Ducasse
Stéphane Ducasse wrote:

> Juan
>
> I was looking a bit at triggerEvent:
> and I seein pharo and probably in squeak code like the following one
>
> mouseUp: anEvent
> "Change the cursor back to normal if necessary and change the color back to normal."
>
> self canResizeColumn ifFalse: [^ self].
> (self bounds containsPoint: anEvent cursorPoint)
> ifFalse: [anEvent hand showTemporaryCursor: nil].
> self class fastSplitterResize
> ifTrue: [self updateFromEvent: anEvent].
> traceMorph ifNotNil: [traceMorph delete. traceMorph := nil].
> self adoptPaneColor: self paneColor.
> self triggerEvent: #mouseUp
>
>
> So how does it fit your model?
>
> Stef
>
>  

Morphic gives a lot of freedom on what you can do. For example, you can
ask a morph to be notified when the morph itself gets a mouse event. In
Cuis and Squeak you can also do that.

My "pattern for GUI programming" is implemented in the new LightWidget
hierarchy. LightWidgets do not generate events like Morphs do. That's
why it is a separate root class, as LightWidget is not subclass of
Morph. It is to be coded in a different style.

Morphic in Cuis does _not_ follow the "pattern for GUI programming". I
hope, someday, to be able to use these rules for Morphic 3. This means
that programming in Morphic 3 would be done like with LightWidgets and
not like with Morphs. But Morphic 3 is far from mature enough to do this.

This programming style could also be adopted by Pharo, either using
Morphic or "SimpleMorphic" from Cuis. But this is not done. It would be
a great project, though.

Cheers,
Juan Vuletich

_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
In reply to this post by Noury Bouraqadi-2
Noury Bouraqadi wrote:
> Hi Juan,
>
> Is this document available somewhere on the web? It's important to ensure it is persistence.
>
> Noury
>  

Hi Noury,

I'm glad you're interested on it. I'll put it in a web page maybe
tonight, or in my next "free evening".

Cheers,
Juan Vuletich

_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Stéphane Ducasse
In reply to this post by Juan Vuletich-4
thanks for the information.

On Sep 19, 2010, at 3:38 PM, Juan Vuletich wrote:

> Stéphane Ducasse wrote:
>> Juan
>>
>> I was looking a bit at triggerEvent:
>> and I seein pharo and probably in squeak code like the following one
>>
>> mouseUp: anEvent
>> "Change the cursor back to normal if necessary and change the color back to normal."
>>
>> self canResizeColumn ifFalse: [^ self].
>> (self bounds containsPoint: anEvent cursorPoint)
>> ifFalse: [anEvent hand showTemporaryCursor: nil].
>> self class fastSplitterResize
>> ifTrue: [self updateFromEvent: anEvent].
>> traceMorph ifNotNil: [traceMorph delete. traceMorph := nil].
>> self adoptPaneColor: self paneColor.
>> self triggerEvent: #mouseUp
>>
>>
>> So how does it fit your model?
>>
>> Stef
>>
>>  
>
> Morphic gives a lot of freedom on what you can do. For example, you can ask a morph to be notified when the morph itself gets a mouse event. In Cuis and Squeak you can also do that.
>
> My "pattern for GUI programming" is implemented in the new LightWidget hierarchy. LightWidgets do not generate events like Morphs do. That's why it is a separate root class, as LightWidget is not subclass of Morph. It is to be coded in a different style.
>
> Morphic in Cuis does _not_ follow the "pattern for GUI programming". I hope, someday, to be able to use these rules for Morphic 3. This means that programming in Morphic 3 would be done like with LightWidgets and not like with Morphs. But Morphic 3 is far from mature enough to do this.
>
> This programming style could also be adopted by Pharo, either using Morphic or "SimpleMorphic" from Cuis. But this is not done. It would be a great project, though.
>
> Cheers,
> Juan Vuletich
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Gary Chambers-4
In reply to this post by Stéphane Ducasse
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" <[hidden email]>
To: <[hidden email]>
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
>> [hidden email]
>> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project 


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Alexandre Bergel
In reply to this post by Juan Vuletich-4
Maybe you can put it on the google code website...

Alexandre


On 19 Sep 2010, at 09:39, Juan Vuletich wrote:

> Noury Bouraqadi wrote:
>> Hi Juan,
>>
>> Is this document available somewhere on the web? It's important to ensure it is persistence.
>> Noury
>>  
>
> Hi Noury,
>
> I'm glad you're interested on it. I'll put it in a web page maybe tonight, or in my next "free evening".
>
> Cheers,
> Juan Vuletich
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project

--
_,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:
Alexandre Bergel  http://www.bergel.eu
^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;.






_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Mariano Martinez Peck
In reply to this post by Gary Chambers-4
could we add that to the pharo online book ?

On Mon, Sep 20, 2010 at 12:15 PM, Gary Chambers <[hidden email]> 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" <[hidden email]>
To: <[hidden email]>
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
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
Mariano Martinez Peck wrote:
> could we add that to the pharo online book ?

Yes, sure.

But I believe it would make sense only if it is documenting actual
practice. Otherwise a big warning like "Warning: This is just the
documentation of a proposed style. This is not currently used in
Pharo.". Then, I'm not sure if it is really useful...

Cheers,
Juan Vuletich

_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

csrabak

 I think we should create something akin the RFC concept in Pharo page: then after a time given to collect the comments we can see if the idea was OK or not.



Em 21/09/2010 09:16, Juan Vuletich < [hidden email] > escreveu:
Mariano Martinez Peck wrote:
> could we add that to the pharo online book ?

Yes, sure.

But I believe it would make sense only if it is documenting actual
practice. Otherwise a big warning like "Warning: This is just the
documentation of a proposed style. This is not currently used in
Pharo.". Then, I'm not sure if it is really useful...

Cheers,
Juan Vuletich

_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
In reply to this post by Noury Bouraqadi-2
Noury Bouraqadi wrote:
> Hi Juan,
>
> Is this document available somewhere on the web? It's important to ensure it is persistence.
>
> Noury
>  

Hi Noury,

It is now at http://www.jvuletich.org/Cuis/APatternForGUIProgramming.html .

Cheers,
Juan Vuletich

_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Hannes Hirzel
On 9/23/10, Juan Vuletich <[hidden email]> wrote:

> Noury Bouraqadi wrote:
>> Hi Juan,
>>
>> Is this document available somewhere on the web? It's important to ensure
>> it is persistence.
>>
>> Noury
>>
>
> Hi Noury,
>
> It is now at http://www.jvuletich.org/Cuis/APatternForGUIProgramming.html .
>
> Cheers,
> Juan Vuletich
>


Juan,
I have seen that you have started implementing the
    LightWidget hierarchy

Any updates / news?

Regards
Hannes

Reply | Threaded
Open this post in threaded view
|

Re: A pattern for GUI programming (Morphic, Cuis, etc)

Juan Vuletich-4
In reply to this post by Juan Vuletich-4
Hi Hannes,

I am not currently working on LightWidgets. If you're interested, Cuis
includes a working implementation where you can use the UI pattern.

Cheers,
Juan Vuletich

Ps. Please send copy to me of any mail intended for me... I'm not
following the Pharo list closely, and I might miss it.