Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
UI proxies for subclassing
-
Please, I really doubt that omz is actively trying to stop you from subclassing certain classes. Most likely these things are just a consqeucene of how Python's C API works. First of all, subclassing needs to be explicitly enabled on the C side. When defining a Python class in C, you basically create a big
PyTypeObject
(which is astruct
) containing all info about the class. One of thestruct
fields istp_flags
, which contains various flags (duh). For example the one fromPyFloat_Type
looks like this:Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
And for comparison, the one from
PySlice_Type
:Py_TPFLAGS_DEFAULT, /* tp_flags */
Note how the one for
float
hasPy_TPFLAGS_BASETYPE
while the one forslice
does not. And if you try it in Python, you'll see that you can subclassfloat
, but forslice
Python will tell you that it's "not an acceptable base type".And why not just enable subclassing by default? Well, the C implementation needs to make a special case for when an instance of a subclass is created, rather than an instance of the class itself. In
float_new
this looks like this:if (type != &PyFloat_Type) return float_subtype_new(type, args, kwds); /* Wimp out */
In this case the subclass handling is really simple, but that is because
float
on the C side is mostly a struct containing adouble
. (And if you checkslice_new
, there is no speical case for subclasses there.)As for the subclass checking, the equivalent of
isinstance
is implemented for each type by macros like these:#define PyFloat_Check(op) PyObject_TypeCheck(op, &PyFloat_Type) #define PyFloat_CheckExact(op) (Py_TYPE(op) == &PyFloat_Type)
And
PyObject_TypeCheck
is also a macro, defined as:#define PyObject_TypeCheck(ob, tp) \ (Py_TYPE(ob) == (tp) || PyType_IsSubtype(Py_TYPE(ob), (tp)))
And
PyType_IsSubtype
basically returnstp in type(ob).__mro__
, which obviously does not call any of__instancecheck__
,__subclasscheck__
and__subclasshook__
.And even if
ui
's functions accepted things that didn't actually subclassui.View
, it would probably crash the app. The reason is that on the C side, the pointer to the underlyingUIView
or controller or whatever is actually a struct field, and_objc_ptr
is likely a read-only descriptor returning that field's value. So no,_objc_ptr
is not sufficient, that's only a convenience attribute forobjc_util
. -
@dgelessus Thanks. Does this mean that creating a new class inheriting from both
ObjectProxy
andView
would have unintended side-effects? I guess I'll just have to try.The third thing I can try is having
ui2
redefine everyui.View
class, overridingadd_subview
to addview.__subject__
instead ofview
, but this is probably impractical and fragile. -
No, inheriting from
ObjectProxy
andui.View
shouldn't cause any issues, though it probably won't do what you want either. When you do this and then add an instance of that subclass to another view, it will add a blank view (the proxy object itself, which inherits fromui.View
) and not the wrapped view.The most reliable way to handle this might be to implement your wrappers as simple
ui.View
subclasses that contain the wrapped view as their only subview, with the appropriate flexing etc. set. That way the wrappers could be used anywhere a normalui.View
works. -
Hmm, you're right. It doesn't work :( I really don't like your approach, because it's easily inspectable and easy for users to break. I think I'm going to try overriding
add_subview
. @mikael has been usingadd_subview(view.__subject__)
, I'm going to try to overrideadd_subview
to try this automatically. -
Some progress: I've got a wrapper that automatically adds the wrapped as a subview.
The way Python works in this respect is really cool, imo.
>>> a = ViewProxy(ui.Slider()) >>> a <_ui.Slider object at 0x10e642410> >>> a.value 0.0 >>> a.subviews () >>> object.__getattribute__(a, "subviews") (<_ui.Slider object at 0x10e642410>,)
The reason I'm now willing to use a subview is that when doing it with a proxy it's almost completely transparent.
-
Does this let you subclass slider, as was your original goal?
-
Success, I've got a wrapper for subclassing. It displays correctly in view heirarchies.
I could be lazy and make the wrapper view have a frame like
(0, 0, 3000, 3000)
, since that's larger than any iOS device. I'll try anAutoresizingMask
though.Because of
__getattribute__
, the attributes of the container are effectively invisible. The only way isobject.__getattribute__
as displayed above. So nobody will mess with my container.It displays correctly :D
This working approach is similar to Approach 1 listed above, but my assumption that it wouldn't act as a view was incorrect. It acts as a view, but its attributes are inaccessible. I simply add an instance of the wrapped class as a subview.
-
@JonB Yes
-
It seems to me for a ViewProxy there are some attributes which should not be proxied: namely anything that is tied up and out to ObjC geometry, or a relating views to each other. None of the following should be proxied, i think, but handled by the container : name, frame, x, y, center, bounds, flex, hidden, superview, left_button_items, right_button_items, navigation_view, bring_to_front(), close(), present(), send_to_back(), wait_modal(), action or delegate. I am not sure about alpha (not sure if a None bg color lets subviews see through to the underlying ui). add_subview should also be on this list as well, since you would want a subview's superview to return the ViewProxy, not the proxied view....although most people are not adding a subview to a slider.
For positioning, I don't think a large frame is going to work well with the underlying objc code. The container frame should define the size, with the contained subview set with flex='WH' to keep the size fixed to the container.
A few properties like
border_width
or corner_radius may give undesirable behavior, unless you setclipsToBounds=False
on the subview objcinstance. Presumably you have the container bg_color set to None...Finally... likely you would want to override the
action
method of the embedded view to call the container's action with the container as sender. Otherwise ObjC will send the low level slider to the action, thus breaking the illusion of a single object. delegate methods are trickier, i suspect you would need a DelegateProxy class which intercepts all delegate calls, and calls the corresponding method in the container's delegate but replacing the first argument with the container, rather than the embedded view. -
Ugh. Thanks, though.