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


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

Reply via email to