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