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
    13517
    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.
    • JonB
      JonB last edited by

      It has been a while, but IIRC there are two ways to create custom views:

      • you give the pyui a Custom View name -- and when you load_view('a') it will return an A object.
      • You instantiate A directly, and inside A you can call load_view() or load_view('a') (no arguments loads the pyui associated with the current py). One neat thing about this method is that load_view operates in the current scope, so you can reference self.some_method when you create the pyui to assign actions. The first approach IIRC does not let you do that, since self has not yet been created.

      With the second method, in b.pyui you can add a custom View A -- which will instantiate an A(). For this to work A must have already been imported.

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

        My sense is that the second creates two separate object. You can try creating and printing a self.create_time variable in the __init__() method.

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

          Hi,
          I don't get the way to load the pyui file in the __ init__() method.
          there is no load_view() method in View class

          So I really don't get it... do you have sample code that load view content in __ init__() ?

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

            Do ui.load_view(), not my_view.load_view().

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

              in the past, i have always done this by encapsulating (i.e a custom class which loads a class, then adds it as a subview). but i learned something new about Python today... __new__ is called before __init__ and returns an instance. so just do your loading inside __new__, and return the result of the load.

              class someview(ui.View):
                 def __new__(self):
                    return ui.load_view('someview')
              

              i will say the one delicate part of this is that if you change paths, the pyui might not be findable. If you always import, you can use __file__. but if you run the script, you might want to add some logic that stores away the current path into the class as a class var.

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

                @JonB said:

                in the past, i have always done this by encapsulating (i.e a custom class which loads a class, then adds it as a subview). but i learned something new about Python today... __new__ is called before __init__ and returns an instance. so just do your loading inside __new__, and return the result of the load.

                class someview(ui.View):
                   def __new__(self):
                      return ui.load_view('someview')
                

                i will say the one delicate part of this is that if you change paths, the pyui might not be findable. If you always import, you can use __file__. but if you run the script, you might want to add some logic that stores away the current path into the class as a class var.

                @JonB , this is almost exactly what I am looking for. I want a class inherited from ui.View but with the pyui file loaded. This is almost perfect but for the fact that I need to somehow provide the pyui file at runtime. Did you ever figure out a way to do it?

                I have been going down the black magic path , without success 😭 Changing the mro attr example. I know it's stupid. I was looking for inspiration 😁

                I have a feeling I tried to do this a long time ago. I couldn't find anything on the forum. But I found this post. It's encouraging but also not 😱

                But any ideas appreciated. Oh, my goal is to have just to have a class that works as its a ui.View but it loads the view from a pyui file. Maybe multiple inheritance could help?

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

                  @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')	
                  
                  1 Reply Last reply Reply Quote 0
                  • 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