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.
Use a .pyui file in another .pyui file
-
If I have a bunch of views defined in .pyui files is it possible for me to reuse these to build more complex views?
I see that in the interface designer I can add a custom view and set a custom subclass, but that doesn't pull in the associated .pyui file.
-
You have to create custom Classes to use the ui builder. The custom class would simply ui.load_view() in its init method (when load_view is called inside init, it knows to bind itself to the calling instance)
The class needs to be in scope before calling load_view, which can sometimes make this awkward.
I think there are some example floating around the forum...
-
This post is deleted! -
@JonB said:
when load_view is called inside init, it knows to bind itself to the calling instance
Oh that's useful! I'll give it a shot when I get a chance. Thanks!
EDIT:
Hang on. I see a problem with that. If I put
ui.load_view()
in the custom view class's__init__
and then attempt to use that view with anotherui.load_view("my_view")
I'll end up in a recursive loop.Does that mean that if I do this I can ONLY instantiate my .pyui-defined views by adding them to other views in interface designer? Or have I missed something again? 😄
-
Right, ok, memory jogged....
The key to allowing both MyView() and load_view('MyView.pyui') type syntax is to create a wrapper class inside init, who returns the current instance in new, and use the bindings in load_view to override MyView.
A cleaned up example:
https://gist.github.com/dea48790491892ad15eabdb29d780436 -
Thanks! Someone pointed me to that in the Slack channel too :)
The cleaned up example will be handy.
-
@Subject22 , below is how I use it.
# coding: utf-8 import ui import os class PYUILoaderStr(ui.View): ''' loads a pyui file into the class, acts as another ui.View class. ** Please note that the pyui class must have its Custom Class attr set to selfwrapper Thanks @JonB ''' def __init__(self, pyui_str, raw = True): # black magic here, for me at least... class selfwrapper(ui.View): def __new__(cls): return self if raw: pyui_str = json.dumps(pyui_str) ui.load_view_str(pyui_str, bindings={'selfwrapper':selfwrapper, 'self':self}) def xx(): print 'hi' return True class PYUILoader(ui.View): ''' loads a pyui file into the class, acts as another ui.View class. ** Please note that the pyui class must have its Custom Class attr set to selfwrapper Thanks @JonB ''' def __init__(self, f_name = None): print 'in PYUILoader init' # black magic here, for me at least... class selfwrapper(ui.View): def __new__(cls): return self if not f_name.endswith('.pyui'): f_name += '.pyui' # make sure the file exists if not os.path.isfile(f_name): raise OSError ui.load_view( f_name , bindings={'selfwrapper':selfwrapper, 'self':self}) class MyClass(PYUILoader): def __init__(self, f_name ): PYUILoader.__init__(self, f_name) self.width = 500 self.height = 500 print 'in Myclass' self['menu'].bg_color = 'red' def xx(self): print 'hello from my class' return True if __name__ == '__main__': mc = MyClass('StdView') mc.present('sheet', animated = False)
-
@Phuket2
Partly to understand the black magic you need to look at what ui._view_from_dict does.If custom_class is set, the loader checks for the custom class's name in the bindings (or current scope's locals and globals), and if that class is an subclass of View will instantiate it, without arguments. Then it proceeds to set various attributes of this new view, and finally returns the view.
v = ViewClass() v.frame = _str2rect(view_dict.get('frame')) v.flex = attrs.get('flex', '') v.alpha = attrs.get('alpha', 1.0) v.name = attrs.get('name') ... return v ...
So the issue is that this creates a whole new instance of our custom class, and returns it... and if the constructor for that class is trying to call load_view, you get an infinite loop. But since we can override the bindings dict, we create a wrapper class whose new constructor simply returns the current instance.
If you also want to be able to use load_view to instantiate the view, rather than use selfwrapper as the custom class name, you would use the actual custom class name, as in my example above. adding self to bindings as you have done is also a good idea, since it lets you reference instance methods as actions.
-
Actually, here is a slightly less verbose example which hides some of the details so you have less to remember... basically create a factory function to do the same in one line.
# wrapper.py def WrapInstance(obj): class Wrapper(obj.__class__): def __new__(cls): return obj return Wrapper #MyView.py from wrapper import WrapInstance class MyView(ui.View): def __init__(self): ui.load_view('MyView',bindings={'MyView':WrapInstance(self),'self':self})
-
@JonB , thanks so much . Sorry for the delay. Just needed to get around to it.
But I created a snippet for myself.import ui # wrapper.py, Pythonista Forum @JonB # https://forum.omz-software.com/topic/3176/use-a-pyui-file-in-another-pyui-file # remember to add the the name of the class to the 'Custom View Class' # in the .pyui _pyui_file_name = 'find.pyui' def WrapInstance(obj): class Wrapper(obj.__class__): def __new__(cls): return obj return Wrapper class MyClass(ui.View): def __init__(self, *args, **kwargs): ui.load_view(_pyui_file_name, bindings={'MyClass': WrapInstance(self), 'self': self}) super().__init__(*args, **kwargs) if __name__ == '__main__': w = 600 h = 325 f = (0, 0, w, h) mc = MyClass(bg_color='deeppink') mc.present('sheet')
-
@JonB , one small improvement below. Not a big deal, but all constants removed.
ui.load_view(_pyui_file_name, bindings={self.__class__.__name__: WrapInstance(self), 'self': self})
-
Ok, I am an idiot. We got to this before 😱😱😱
But the below I think is a small improvement with a small refactor.
I tested it and the globals are handled correctly , with the custom view assignments as well as custom attributes.
Just separation between the custom class and the binding creation. I realise not a huge leap. But it does make it more tidy to call. In my opinion anyway.def pyui_bindings(obj): def WrapInstance(obj): class Wrapper(obj.__class__): def __new__(cls): return obj return Wrapper bindings = globals().copy() bindings[obj.__class__.__name__]=WrapInstance(obj) return bindings class PYUIClass(ui.View): def __init__(self, pyui_fn, *args, **kwargs): ui.load_view(pyui_fn, pyui_bindings(self)) super().__init__(*args, **kwargs)