Re: [Pyobjc-dev] Key-Value Coding; Key-Value Observing;
Brought to you by:
ronaldoussoren
From: Brice T. <br...@4d...> - 2010-05-25 20:25:44
|
I believe so. Like for the other approaches. On 24 May 2010, at 18:56, Orestis Markou wrote: > Thank you for this. Hopefully google will pick it up for other people. > > One question - in the synthesize case, is there a convention that maps the _fido ivar to the fido property? > > > On 24 May 2010, at 15:43, Brice Thurin wrote: > >> Good Morning, >> >> I am new to Objective-C and PyObjc. I decided to learn Cocoa by reading the book of Aaron Hillegass, "Cocoa Programming for Mac OS X". I first tried the tried the example using only Objective-C. I am now trying to implement the same example using Python and I would like to share my finding as It took me some times to figure out how to do it, also I will appreciate some inputs as what might be a very silly way of using PyObjc. Here I would like to talk about the example of chapter 7 on key-value coding and key-value observing. At first I was a bit confused as if I was supposed to use objc.accessor or not, or if i needed to import PyObjCTools.KeyValueCoding or not, etc. >> >> I am coding on a machine running Snow Leopard and I am using XCode with the template provided on the svn. The project is called PyKVCFun. It is a python-cocoa application. Remove the appdelegate file. Create a PyAppController.py file with the template python NSObject subclass. Delete the delegate in the MainMenu.xib file and create add a new NSObject controller and set its class to PyAppController. Make sure you replace the delegate import in main.py by import PyAppController. >> >> >> STEP 1: Add an init function method to the NSObject class . >> >> It should look like this: >> >> ###### >> from Foundation import * >> import objc # Do not forget to add it as it is needed later >> >> class PyAppController(NSObject): >> >> def init(self): # objc init different than the usual __init__ of python >> >> self = super(PyAppController, self).init() # found it in one of the example of the website needed to initialise the class i believe equivalent to [super init]; of objective c. >> if self is None: return None >> >> self.setValue_forKey_(NSNumber.numberWithInt_(5), u"fido") # That is the key-value coding mechanism. >> n = self.valueForKey_(u"fido") >> NSLog(u"fido = %@", n) >> return self #required by objc. >> ###### >> >> If you run this code not too much should happen except "fido = 5" logged into the console. >> >> Step 2: Add accessor methods >> I do not think it is required but it is a good way to understand the mechanism. >> >> ###### >> def fido(self): >> NSLog(u"-fido is returning %d", self._fido) >> return self._fido >> >> def setFido_(self,x): >> NSLog(u"-setFido: is called with %d", x) >> self._fido = x >> ###### >> As you noticed, the fido variable is called using _fido (i believe other naming convention can be used but this one is quite short!), as fido is the getter method. >> So now when you run it, you should see in the console that your accessor method are called. >> >> STEP 3: Add vertical slider to the GUI >> Drop a vertical slider to your window in interface builder and make it continuous. Then bind it to the fido key of PyAppController (binding->value->Model Key Path). >> Now if you run your application you should see the setFido accessor being called as you moved the slider. >> >> STEP 4: Key-Value Observing >> Add a label to the GUI and bind its value to the fido key. >> Now run the application. And you should see the value of the label changing as you move the slider, also in the console you can see now that the two accessor are being called when you move the slider and not only the setter. However one annoying things is that the value shows as float an not integer like you might like. One way I found to solve the problem is to declare _fido at the beginning of the class like that: >> ####### >> _fido = objc.ivar.int() >> ####### >> Adding this line you can now see the label showing as int. >> >> STEP 5: Making keys observable >> The aim is now to change the variable directly. >> Add the following method to the PyAppController class: >> >> @objc.IBAction >> def incrementFido_(self,sender): >> self._fido += 1 >> NSLog(u"fido is now %d", self._fid >> Add a push button to your GUI and control-drag from the button to the PyAppController Object so the button trigger the incrementFido: action. >> I you run the code and push the button the console display the log from the button showing you that the incrementFido method is called but the label and the slider are not updated. For that to happen you need to modify your method adding two lines to notify that the key is going to change and that it has changed. >> >> @objc.IBAction >> def incrementFido_(self,sender): >> >> self.willChangeValueForKey_(u"fido") >> self._fido += 1 >> NSLog(u"fido is now %d", self._fido) >> self.didChangeValueForKey_(u"fido") >> >> Now when you run the code the increment button is working fine. >> Note: I am not sure why the getter accessor is called twice when you press the button as you might see from the console log. >> >> There are alternatives to implement the incrementFido_ method, either using key value coding: >> >> @objc.IBAction >> def incrementFido_(self,sender): >> >> n = self.valueForKey_(u"fido") >> npp = n + 1 >> self.setValue_forKey_(npp, u"fido") >> >> Note: the getter accessor is now called three time when the increment button is pushed. >> >> either using the accessor method: >> >> @objc.IBAction >> def incrementFido_(self,sender): >> >> self.setFido_(self.fido()+1) >> >> Note: the getter accessor is now called three time when the increment button is pushed. >> >> STEP 6: Using synthesize >> Also it seems there is another way of coding the same application in a much more concise way, by using objc.synthesize. >> The code for the class becomes where the setter and getter method have been replaced by synthesize, here you will not see the log in the console: >> >> from Foundation import * >> import objc >> >> class PyAppController(NSObject): >> >> objc.synthesize('fido') >> _fido = objc.ivar.int() >> >> def init(self): >> >> self = super(PyAppController, self).init() >> if self is None: return None >> >> self.setValue_forKey_(NSNumber.numberWithInt_(5) , u"fido") >> n = self.valueForKey_(u"fido") >> NSLog(u"fido = %@",n) >> return self >> >> >> @objc.IBAction >> def incrementFido_(self,sender): >> self.setFido_(self._fido + 1) >> >> Conclusion >> They must be other way to code it maybe in a more elegant way and i will be happy to hear about it. >> >> Brice >> >> >> >> >> >> >> ------------------------------------------------------------------------------ >> >> _______________________________________________ >> Pyobjc-dev mailing list >> Pyo...@li... >> https://lists.sourceforge.net/lists/listinfo/pyobjc-dev > |