omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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

    Pythonista
    4
    20
    13516
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Phuket2
      Phuket2 last edited by

      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 new

      class 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
      
      	
      
      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        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, not LoadedPanel.__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'])
        
        
        
        1 Reply Last reply Reply Quote 0
        • JonB
          JonB last edited by

          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
          
          Phuket2 1 Reply Last reply Reply Quote 0
          • Phuket2
            Phuket2 @JonB last edited by

            @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!

            1 Reply Last reply Reply Quote 0
            • JonB
              JonB last edited by

              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()
              Phuket2 1 Reply Last reply Reply Quote 0
              • Phuket2
                Phuket2 @JonB last edited by Phuket2

                @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')
                

                JonB 1 Reply Last reply Reply Quote 0
                • JonB
                  JonB @Phuket2 last edited by

                  @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()
                  
                  Phuket2 1 Reply Last reply Reply Quote 0
                  • Phuket2
                    Phuket2 @JonB last edited by

                    @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'
                    
                    1 Reply Last reply Reply Quote 0
                    • Phuket2
                      Phuket2 last edited by

                      @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

                      1 Reply Last reply Reply Quote 0
                      • Phuket2
                        Phuket2 last edited by

                        @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)

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post
                        Powered by NodeBB Forums | Contributors