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.
Using a custom pu/pyui view in another one using ui editor
-
@JonB k sorry don't worry about it. It appears the new method is past the params. Even if n9t in the init. I didn't expect that. But this is fantastic.
All I did is The below. Works perfectly
class LoadedPanel(ui.View): def __init__(self): pass def __new__(self, pyui_file): return ui.load_view(pyui_file) l = LoadedPanel(pyui_file = 'Countries') l.present('sheet')
-
Oh, there was one gotcha, but not a big deal. The class init is not called. But I think you can reach it with super or by calling self.init.
But is not a big deal. I just changed the class a little and do my init stuff inside the newclass LoadedPanel(ui.View): def __init__(self): pass def __new__(self, pyui_file): # hmmm initialse here as init is not called.. # maybe can do super, but this works just fine... cls = ui.load_view(pyui_file) cls['tb_countries'].data_source.items = _country_list return cls
-
Keep in mind, this approach gives you a ui.View, not a LoadedPanel class. So you cannot use touch, etc, unless you monkey patch it.
ui.View.__init__
does get called, notLoadedPanel.__init__
.Let me explain the point of this a little clearer, because rereading the thread got me confused again, others might be too.
A "recommended way" to have a custom view defined both in code and pyui, is to set the Custom View of the pyui to point to a class name. Then, to instantiate the custom view, you would use ui.load_view -- NOT MyView(). If you want to be able to personalize a pyui, wrap load_view inside another function that takes parameters.
But, if you use that approach, you can not use the editor to insert your custom view into another pyui edited view. Because the ui editor doesnt know you wanted to use load_view instead of MyView() to instantiate your class. The solution is to use the wrapper approach.
Here is a long winded example of what I was just saying. pyuistr represents a pyui which has a button, but is also a MyView CustomView. pyuistr2 adds a MyView as a subview to another view, which does not load the pyui properly. The last method uses the wrapper, and all is hunky dory.
Actually, it seems Custom View in the editor can be anything that can get eval'd to return an object which is a subclass of ui.View, so could just be a wrapper function.
# coding: utf-8 import ui pyuistr=''' [ { "class" : "View", "attributes" : { "custom_class" : "", "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "enabled" : true, "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "flex" : "" }, "frame" : "{{0, 0}, {240, 240}}", "selected" : false, "nodes" : [ { "class" : "Button", "attributes" : { "title" : "Button", "class" : "Button", "frame" : "{{80, 104}, {80, 32}}", "font_size" : 15, "uuid" : "32714A73-9E4C-4EB6-89C1-140746155745", "name" : "button1" }, "frame" : "{{80, 104}, {80, 32}}", "selected" : true, "nodes" : [ ] } ] }] ''' pyuistr2=''' [ { "class" : "View", "attributes" : {}, "frame" : "{{0, 0}, {240, 240}}", "selected" : false, "nodes" : [ { "class" : "View", "attributes" : { "class" : "View", "name" : "view1", "uuid" : "C9AF78B9-C0EB-4951-A282-D06468F22F3E", "frame" : "{{70, 70}, {100, 100}}", "custom_class" : "MyView" }, "frame" : "{{70, 70}, {100, 100}}", "selected" : true, "nodes" : [ ] } ] }] ''' class MyView(ui.View): def hi(self): print 'hi' class MyViewWrapper(ui.View): def __new__(self): v= ui.load_view_str(pyuistr) return v def check_load(v): if v['button1']: return True else: return False v = ui.load_view_str(pyuistr) print 'loadviewstr loaded pyui :', check_load(v) v2= MyView() print 'MyView() loaded pyui:', check_load(v2) v3=MyViewWrapper() #i.e use this in editor print 'MyViewWrapper() loaded pyui:', check_load(v3) #v.present('sheet') v4=ui.load_view_str(pyuistr2) print 'Custom View subview inside pyui using MyView() loaded pyui', check_load(v4['view1']) v5=ui.load_view_str(pyuistr2.replace('MyView','MyViewWrapper')) print 'Custom View subview inside pyui using MyViewWrapper() loaded pyui', check_load(v5['view1'])
-
Ack, I forgot the whole poont of this... if your custom class has both a new and an init, you don't need the wrapper class:
class MyView(ui.View): def __init__(self): print 'initted' def __new__(self): v= ui.load_view_str(pyuistr) return v
-
@JonB, shhhhhh! I see what you mean about being returned a ui.View class in my other example. Its so obvious now! I thought I was so close! I will look at the custom class way. Thanks again!
-
Eureka! Looking at load_view, which now has a bindings parameter, it is possible to pass "self" to the view getting loaded. This means you can load a view inside init, and have the pyui update appropriate subviews and attributes of the currently initting View.
The code is looking for a class, that is a subview of View, so you have to create such a class inside init, but the new method can return self. Hopefully this explains it better:
# coding: utf-8 import ui pyuistr=''' [ { "class" : "View", "attributes" : { "custom_class" : "self", }, "frame" : "{{0, 0}, {240, 240}}", "selected" : false, "nodes" : [ { "class" : "Button", "attributes" : { "title" : "Button", "class" : "Button", "frame" : "{{80, 104}, {80, 32}}", "font_size" : 15, "uuid" : "32714A73-9E4C-4EB6-89C1-140746155745", "name" : "button1" }, "frame" : "{{80, 104}, {80, 32}}", "selected" : true, "nodes" : [ ] } ] }] ''' class MyView(ui.View): def __init__(self): class selfy(ui.View): def __new__(cls): return self ui.load_view_str(pyuistr,bindings={'self':selfy}) v=MyView() v.present()
-
@JonB, wow. You cracked it... It's perfect. I had a problem getting your expample to run. There was just some problem with the pyui_str in could not spot. I was too excited to spend too long on it.
** but thank you **I know some of these side tracks I go on seem a bit crazy, but I think this is great news for the community for anyone doing ui work.
I can't say I exactly get what's happening with the bindings, but I get the gist of it. I have seen the bindings before, but no understanding of there function. So, I would never have figured this out.
I think this is too important to leave in this thread. What about in Pythonista-tools repo there was a folder for code snippets, a folder for each subject? An online knowledge base app would be better in my opinion, but repo's appear to be the way here.
Anyway, just seems to important to leave in here.
For others reading this. The only thing that is done special here you can't see is that in the pyui file, the custom class is set to self. I just did this in the ui Editor
I put a pic at the bottom
class MyView(ui.View): def __init__(self, *args, **kwargs): class selfy(ui.View): def __new__(cls): return self if kwargs.get('pyui_file'): ui.load_view( kwargs.get('pyui_file') ,bindings={'self':selfy}) else: self.make_view() def make_view(self): self.frame = (0,0,500,500) self.background_color = 'purple' def test(self): print 'in test' #v=MyView(pyui_file = 'Countries') v=MyView() # isinstance is not good in this case as ui.View class returns true # check the type instead ... assert isinstance(v, MyView) , 'The class is wrong type' assert type(v) == MyView, 'The class is wrong type' v.test() # just make sure the methods are callable v.present('sheet')
-
@Phuket2 In retrospect it might be better to pass in a dict which includes both the wrapper class, and the actual self instance. That way, you can have actions refer to instance methods.
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( kwargs.get('pyui_file') ,bindings={'selfwrapper':selfwrapper, 'self':self}) else: self.make_view()
-
@JonB , given I will use the class like in the code below, do you still think it's an issue? I will listen to you, I would like to get it right. But I wanted to show you how I thought I would use it. Essentially a loader. In this case the child just decides if it calls the super or not. I guess it wouldn't hurt to call super regardless.
But in my view subclassing the "loader" so to speak is nice. I could see @omz just providing this in future releases, or something close any way.
But it would be nice to here your thoughts, as I say, I would like to get it right.class ViewBase(ui.View): def __init__(self, pyui_file = None): self.loaded_from_pyui = False class selfy(ui.View): def __new__(cls): return self if pyui_file: ui.load_view( pyui_file ,bindings={'self':selfy}) self.loaded_from_pyui = True class CountriesPanel(ViewBase): def __init__(self, pyui_file = None): if pyui_file: super(CountriesPanel, self).__init__(pyui_file) self.background_color = 'purple'
-
@JonB, I don't really understand you changes in the binding. I think name conflicts. Not really sure. But to get the ui.load_view_str() working correctly I had to change it. Well at least I think I had to... Been getting a little complicated as working on different things.
But here is a gist that I am using this idea with. It's not finished, but it's working -
@JonB or anyone else that can comment, I appreciate your feedback if you have time.
What I have outlined below, seems to work. But I have long past my level of competence to really know if it's ok.
I put a working version of the code at this gist
Based on the discussion in this thread I have had to extend the model to use multiple inheritance for it really to be very useful.
Well I think I have to use multiple inheritance.The idea of the MyPanel class is its reusable to create any type of panel (basically just another uiView, that is displayed as a subview)
Without adding the PanelHelper there will be a lot of duplicate code as well as uncertainty to attributes the class will contain. Meaning anyone that wants to write a MyPanelClass will have to know to much about the callbacks etc required as well as have to implement std code and define a std set of attrs that will be required.I have read a few times about avoiding Multiple inheritance, but in this case I don't see how to do it nicely or even if I need to avoid it at all. As I say it seems to work. But maybe I have stepped in a land mine, next step might not be nice 😱
So I have :
class PyiuLoader(ui.View)
class PanelHelper(object)
class MyPanel( PyuiLoader , PanelHelper):
PyuiLoader.init(self, params)
PanelHelper.init(self , params)