Search code examples
cocoapyobjckey-value-coding

How to pass NSSize and NSRect to PyObjC KVC convenience accessor


I'm writing some AppKit code in PyObjC on Lion, and I want to use the special KVC accessor style, e.g. obj._.field = value instead of obj.setField_(value).

I have success setting NSString- and NSDictionary-valued properties with KVC, but I'm having trouble with wrapped Objective-C structs, specifically NSSize and NSRect.

When I try to set a KVC property of type NSSize or NSRect, the Objective-C layer throws an NSInvalidArgumentException. Cocoa is trying to invoke sizeValue or rectValue on the Python object.

Here is a log at the interactive prompt that shows the issue:

% python
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from AppKit import *
>>> NSApplication.sharedApplication()
<NSApplication: 0x7fcf6ae071f0>
>>> w = NSWindow.alloc().init()
>>> w._.minSize = NSSize(100, 200)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC/objc/_convenience.py", line 141, in __setattr__
    return self.__object.setValue_forKey_(value, key)
ValueError: NSInvalidArgumentException - Class OC_PythonObject: no such selector: sizeValue
>>> r = NSMakeRect(100, 200, 300, 400)
>>> w._.frame=r
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC/objc/_convenience.py", line 141, in __setattr__
    return self.__object.setValue_forKey_(value, key)
ValueError: NSInvalidArgumentException - Class OC_PythonObject: no such selector: rectValue

PyObjC seems to be wrapping the structs in a OC_PythonObject proxy, which doesn't implement the expected KVC converter. The KVC code assumes that the value is like an NSValue and implements the struct accessor methods.

Is there a way to set struct-valued KVC properties in PyObjC?


Solution

  • This is quite simple. Even in plain ObjC, the structs would need to be wrapped in an NSValue before being passed through KVC. You can do the same in Python:

    >>> w._.minSize = NSValue.valueWithSize_(NSSize(100,200))
    >>> w.minSize()
    <NSSize width=100.0 height=200.0>
    >>> w._.frame = NSValue.valueWithRect_(NSMakeRect(100, 200, 300, 400))
    >>> w.frame()
    <NSRect origin=<NSPoint x=100.0 y=200.0> size=<NSSize width=300.0 height=400.0>>
    

    See "Wrapping and Unwrapping Structs" in the KVC Programming Guide for more info. Also, don't forget when doing this that not all classes are KVC compliant for all attributes.