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.
Custom View class loading a .pyui into itself (with instance method actions)?
-
I see some pretty old threads about this topic, but I was wondering what the current definitive "best practices" way is to create a custom View subclass which can load itself from a .pyui file, and hook up the actions of its subviews to instance methods?
So something along the lines of:
class MyView(ui.View): def __init__(self): ui.load_view(...) def buttonAction(self, sender): print "button pressed:",sender instance = MyView() instance.present()
Where buttonAction is set in the UI editor as the Action attribute of a Button that is a subview of the base view in the pyui file.
Now, obviously the above doesn't work, since if I try to load a .pyui which has a base view set to the custom view subclass "MyView" calling "ui.load_view()" is recursive, trying to insantiate a MyView which in turn loads the pyui which needs to instantiate another MyView, etc. And if I try to have the base view in the pyui file just be a plain ui.View class, calling ui.load_view() in the init() of my custom class doesn't appear to load anything, at just creates an empty view.There's an old thread from two years ago discussing a way of doing this by wrapping the instantiation:
specifically this bit:
class MyView(ui.View): def __init__(self, *args, **kwargs): class selfwrapper(ui.View): def __new__(cls): return self if kwargs.get('pyui_file'): ui.load_view(... ,bindings={'selfwrapper':selfwrapper, 'self':self})
Is something like that still the right way to do this?
-
Searching around in the forum I stumbled on this thread which has a reply about halfway through specifically about loading a custom View subclass from a pyui file:
https://forum.omz-software.com/topic/4697/pyui-button-image-options
This is only about 8 months old, so I will assume that there is no current "better way" to do it?
And thanks to @Phuket2 for posting such a clear piece of example code. -
That's the right approach -- though (what might not be obvious) is that you ought to use a descriptive name (not MyClass).
-
Thanks @JonB! Those are just examples, of course...the actual objects are named things that are meaningful in context.
So I tried this out, and the custom View class was able to load the .pyui into itself and present it. But I can't figure out how to get actions to hook up to instance methods. Actually, I can't get any actions to hook up to anything: bound methods, class functions, or functions in the global scope.
I tried setting the action to "self.action" and "MyView.action", neither worked. And even if there's a function in the global scope named "action" that doesn't get called either if I set the action function to just that.
Anyone have an example of something like this where a subview action function is able to call an instance method?
-
Looking around in the inspection panel with the script running and the UI loaded and displayed, I can see that the only thing which actually fills the button action attribute with something is if I use: "<class name>.<action method name>"
I assume because that's the only method which actually resolves to something in scope. Filling it out with "self.<action method name>" or "<global function name>" leaves it set to None at runtime.
Unfortunately the unbound method hooked up via "<class name>.<action method name>" can't be called without the class instance as the first argument, and it isn't being given that, so it produces a traceback when the button is pressed. I can manually put the correct method into the action attribute post-load:
def did_load(self): self['button1'].action = self.button1Pressed
so I guess this issue is easily circumvented if I just automate that process.
-
sorry, i thought Phuket's version included the self binding:
bindings['self'] = self
after the other binding is set, in the PYUILoader init.
That makes
self
available to use in actions, when calling via the MyClass() instantiation technique. However, when loading directly from pyui, such as when a pyui contains another custom method, it becomes much trickier. Somewhere I posted a technique to use inspect to go up the stackframe to find the parent to bind to (the action is simply eval'd, allowing you to do all sorts of such mischief)... it is also possible to use MyClass.action, except you have to detect it is called as a function with no sender argument, and find the real object by travsersing up the superview tree. I can post updated code later... -
Yep, that did it. Thanks!
So...how on earth did you all ever find that these "bindings" existed in the call to load_view?
-
The
ui
module is pure python (though it relies on the_ui
module for
so you can view the source. -
Clearly I need to explore the API modules more :)