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