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 > [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
