Hi,
I have a simple Person class with 2 instance variables (firstname and surname) and its 2 accessors. It also has a #name method (and that's where I got in trouble) that simply return the concatenation of both: name ^firstname, ' ', surname. I created a presenter (descending from Shell) with 3 sub presenters: firstnamePresenter, surnamePresenter and namePresenter. I want the view that displays the name to update whenever the user changes the text in the surname or firstname views. So, I added the following code PersonShell createSchematicWiring firstnamePresenter when: #valueChanged send: #onNameChanged to: self. surnamePresenter when: #valueChanged send: #onNameChanged to: self. onNameChanged "namePresenter should raise #valueChanged and update its view (hopefylly)..." namePresenter value: model name. If I try this, by showing it in a view with 3 textboxes linked to the 3 textpresenters, and change the surname in its textbox, the model is updated, but the textbox displaying name remains unchanged... What am I doing wrong??? This is the full pac: ------------------------------------------------------------------------------------ | package | package := Package name: 'test'. package paxVersion: 0; basicComment: ''. package classNames add: #Person; add: #PersonShell; yourself. package binaryGlobalNames: (Set new yourself). package globalAliases: (Set new yourself). package allResourceNames: (Set new add: #PersonShell -> 'Default view'; yourself). package setPrerequisites: (IdentitySet new add: '..\..\Documents and Settings\Fernando\Mis documentos\Dolphin Smalltalk 5.1\Object Arts\Dolphin\Base\Dolphin'; add: '..\..\Documents and Settings\Fernando\Mis documentos\Dolphin Smalltalk 5.1\Object Arts\Dolphin\MVP\Base\Dolphin MVP Base'; add: '..\..\Documents and Settings\Fernando\Mis documentos\Dolphin Smalltalk 5.1\Object Arts\Dolphin\MVP\Type Converters\Dolphin Type Converters'; yourself). package! "Class Definitions"! Model subclass: #Person instanceVariableNames: 'firstname surname' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! Shell subclass: #PersonShell instanceVariableNames: 'namePresenter firstnamePresenter surnamePresenter' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! "Global Aliases"! "Loose Methods"! "End of package definition"! "Source Globals"! "Classes"! Person guid: (GUID fromString: '{AF6E4143-DF83-4BF7-9A02-FAB8CAA0F9D3}')! Person comment: 'Clase de prueba'! !Person categoriesForClass!Kernel-Objects! ! !Person methodsFor! firstname ^firstname! firstname: anObject firstname := anObject! name ^firstname, ' ', surname.! surname ^surname! surname: anObject surname := anObject! ! !Person categoriesFor: #firstname!accessing!private! ! !Person categoriesFor: #firstname:!accessing!private! ! !Person categoriesFor: #name!public! ! !Person categoriesFor: #surname!accessing!private! ! !Person categoriesFor: #surname:!accessing!private! ! !Person class methodsFor! new ^super new initialize.! ! !Person class categoriesFor: #new!public! ! PersonShell guid: (GUID fromString: '{1C3E56A4-8CE7-43AD-8EA2-95E7701FEE5C}')! PersonShell comment: ''! !PersonShell categoriesForClass!MVP-Presenters! ! !PersonShell methodsFor! createComponents super createComponents. namePresenter := self add: TextPresenter new name: 'name'. firstnamePresenter := self add: TextPresenter new name: 'firstname'. surnamePresenter := self add: TextPresenter new name: 'surname'.! createSchematicWiring firstnamePresenter when: #valueChanged send: #onNameChanged to: self. surnamePresenter when: #valueChanged send: #onNameChanged to: self. ! model: aPerson super model: aPerson. namePresenter model: (self model aspectValue: #name). firstnamePresenter model: (self model aspectValue: #firstname). surnamePresenter model: (self model aspectValue: #surname).! onNameChanged namePresenter value: model name.! ! !PersonShell categoriesFor: #createComponents!public! ! !PersonShell categoriesFor: #createSchematicWiring!public! ! !PersonShell categoriesFor: #model:!public! ! !PersonShell categoriesFor: #onNameChanged!public! ! !PersonShell class methodsFor! defaultModel ^Person new.! ! !PersonShell class categoriesFor: #defaultModel!public! ! "Binary Globals"! "Resources"! (ResourceIdentifier class: PersonShell name: 'Default view') assign: (Object fromBinaryStoreBytes: (ByteArray fromHexString: '2153544220312046020C0001000000566965775265736F75726365000000000E0124005354425265736F757263655354424279746541727261794163636573736F7250726F7879000000007200000024070000215354422031204E080C000A0000005354425669657750726F7879000000009A000000000000005200000010000000446F6C7068696E204D5650204261736552000000090000005368656C6C56696577620000001B0000000000000000000000620000000200000001009E0101000200A00100000000000006010B0053797374656D436F6C6F72000000001F0000000000000007020000000000000000000000000000A001000000000000EA000000000000000001000062000000060000009A010000000000009A00000000000000C001000052000000080000005465787445646974620000001000000000000000A0010000620000000200000082000000040000008000014401040000400200000000000000000000000000000700000000000000000000000000000040020000000000008200000008000000C706FFFF0000000006020D004E756C6C436F6E7665727465720000000000000000000000000100000006010F004D65737361676553657175656E636500000000CA00000000000000D0000000620000000300000006030B004D65737361676553656E64000 00000BA00000000000000520000001000000063726561746541743A657874656E743A620000000200000006020500506F696E7400000000330000002900000062030000000000002D01000033000000400200001203000000000000BA00000000000000520000000F00000073656C656374696F6E52616E67653A620000000100000006030800496E74657276616C00000000030000000100000003000000400200001203000000000000BA00000000000000520000000F0000006973546578744D6F6469666965643A6200000001000000200000004002000006010F0057494E444F57504C4143454D454E5400000000720000002C0000002C0000000000000001000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1900000014000000AF0000002D000000CA00000000000000D000000062000000000000006203000000000000C1000000C10000000000000013000000520000000900000066697273746E616D659A0100000000000050020000620000001000000000000000A0010000620000000200000082000000040000008000014401040000A004000000000000000000000000000007000000000000000000000000000000A0040000000000008200000008000000C706FFFF00000000B202000000000000000000000000000001000000D202000000000000CA00000000000000D00 000006200000003000000120300000000000030030000620000000200000062030000000000007D0100002900000062030000000000000F01000033000000A00400001203000000000000A00300006200000001000000D203000000000000030000000100000003000000A0040000120300000000000000040000620000000100000020000000A00400003204000000000000720000002C0000002C0000000000000001000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE00000014000000450100002D000000CA00000000000000D00000007004000080040000000000001300000052000000070000007375726E616D659A0100000000000050020000620000001000000000000000A0010000620000000200000082000000040000008000014401040000000600000000000000000000000000000700000000000000000000000000000000060000000000008200000008000000C706FFFF00000000B202000000000000000000000000000001000000D202000000000000CA00000000000000D0000000620000000300000012030000000000003003000062000000020000006203000000000000330000008D00000062030000000000005902000033000000000600001203000000000000A00300006200000001000000D20300000000000003000000010000000300000000060000120 300000000000000040000620000000100000020000000000600003204000000000000720000002C0000002C0000000000000001000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1900000046000000450100005F000000CA00000000000000D00000007004000080040000000000001300000052000000040000006E616D6500000000000000000000000000000000000000000100000000000000000000000000000000000000010000000000000000000000D202000000000000CA00000000000000D00000006200000002000000120300000000000030030000620000000200000062030000000000000B0000000B0000006203000000000000A5040000C7010000A00100001203000000000000BA0000000000000052000000080000006D656E754261723A620000000100000000000000A00100003204000000000000720000002C0000002C0000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF050000000500000057020000E8000000CA00000000000000D0000000620000000300000040020000A004000000060000800400000000000015000000460504000300000049636F6E0000000000000000100000000E02110053544253696E676C65746F6E50726F7879000000009A000000000000005200000007000000446F6C7068696E5200000018000000496D61676 552656C617469766546696C654C6F6361746F72BA00000000000000520000000700000063757272656E74520000000D0000005368656C6C566965772E69636F0E021F0053544245787465726E616C5265736F757263654C69627261727950726F7879000000005200000010000000646F6C7068696E64723030352E646C6C00000000'))! |
Fernando,
> What am I doing wrong??? You're quite close. There are three problems. 1) The #createSchematicWiring event should do a supersend first - it's important. 2) Your model needs an #initialize to set up the instance variables to empty Strings. It will prevent walkbacks. 3) The one that's difficult to explain :-) The solution is easy - remove the following line from the shell's #model method. namePresenter model: (self model aspectValue: #name). The reason why this was causing the problem. It's easiest to explain by putting a breakpoint in the #onNameChanged method and then make a few single steps. The breakpoint/single step sequence (after entering a string in the firstname field and tabbing) is .... #onNameChanged step over "model name" step into "namePresenter value: model name" step over "self model" step into "self model value: anObject" now, look at the first test in the next method (and yes, we've been here before in connection with ListPresenters). It tests to see if the current value being displayed is the same as the new value and, if so, doesn't do anything more. OK, step into "self value" step into "self getValue" We are now at the point where we are getting the current value. Note that it gets the current value by sending #name to the TextEdits model (i.e. a Person instance). It does this because we changed the 'name' TextEdit's model to be a wrapper around your model's #name method (see the line above that I suggest removing). So go back to the comparison a couple of steps ago. This is therefore comparing the value returned by sending #name to the model with the value returned by sending #name to the model. Not surprisingly they are identical so no update of the 'name' view takes place. If you remove the line from the #model method that links the #name method to the namePresenter (it can't work anyway as there is no #name: accessor in the model) then all will work as the #getValue method above will get the value from the TextPresenters default model, and as that will contain a different (empty) string the view update will work. -- Ian Use the Reply-To address to contact me. Mail sent to the From address is ignored. |
On Mon, 21 Mar 2005 18:18:16 -0000, "Ian Bartholomew"
<[hidden email]> wrote: [snip] Thank you very much, it was driving me crazy. Looks like it's gonna take some time to get used to this MVP thing... >If you remove the line from the #model method that links the #name method to >the namePresenter (it can't work anyway as there is no #name: accessor in >the model) then all will work as the #getValue method above will get the I just ran into this issue too. :-) I did it by making the PersonShell a subclass of Dialog. As soon as I clicked on the 'ok' button I got a DNU complaining about the lack of a #name: method. Yuck... What's the best way to present read-only aspects such as #name? Wouldn't it make sense to have read-only presenters for this sort of thing? |
Fernando.
> Looks like it's gonna > take some time to get used to this MVP thing... If it's any comfort, the behaviour you found surprised me too, and I had to follow Ian's explanation pretty closely to understand it (the behaviour, not Ian's explanation ;-) Another way of "fixing" the problem would be to leave PersonShell>>model: unchanged, but to modify #onNameChanged so that it just notified the sub-component that the 'virtual' #name aspect should be re-computed. onNameChanged namePresenter model trigger: #valueChanged. which you might find more intuitive if you like using value aspect adaptors. Of, course that would still suffer from the Dialog problem since there is no #name:. On that subject, I rather think that there's a bug in the Dialog framework there. The class comment for AspectBuffer states (my emphasis): Only the aspects that have been explicitly requested AND THEN MODIFIED will be updated back to the subject when #apply is received. I never use Dialogs myself (I don't like user interfaces with modal dialogs -- except the obvious File-open and so on), but if I'm reading the code correctly, the AspectBuffer will attempt to write back the value of any aspect that has been requested regardless of whether it has also been modified. > What's the best way to present read-only aspects such as #name? > Wouldn't it make sense to have read-only presenters for this sort of > thing? As I say, I never use Dialogs, but it looks as if overriding Dialog>>apply might be the easiest thing to do. A generic (and untested) implementation: apply aspects do: [:each | each canSet ifTrue: [self subject perform: each putSelector with: each value]]. -- chris |
In reply to this post by Fernando Rodríguez
Fernando,
> I just ran into this issue too. :-) I did it by making the PersonShell > a subclass of Dialog. As soon as I clicked on the 'ok' button I got a > DNU complaining about the lack of a #name: method. Yuck... It's correct though. A Dialog will update it's model when the OK button is pressed and you told it, by linking the #namePresenter model with the #name accessor in the Person, to do this by sending #name: to the model. > What's the best way to present read-only aspects such as #name? > Wouldn't it make sense to have read-only presenters for this sort of > thing? There is. In the ViewComposer you can set the #isReadOnly aspect for a presenter to #true or use a StaticText rather than a TextEdit. In both cases user input is not allowed so the presenter (normally) never tries to update the model. The easiest way to prevent the linkage is to just not connect the TextPresenters model to your model and control any update yourself, via events triggered off of your model or the presenters (the way you were trying to do). -- Ian Use the Reply-To address to contact me. Mail sent to the From address is ignored. |
Free forum by Nabble | Edit this page |