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.


    Use a .pyui file in another .pyui file

    Pythonista
    3
    12
    8180
    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.
    • Subject22
      Subject22 last edited by

      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.

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

        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...

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

          This post is deleted!
          1 Reply Last reply Reply Quote 0
          • Subject22
            Subject22 last edited by Subject22

            @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 another ui.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? 😄

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

              Right, ok, memory jogged....

              See https://forum.omz-software.com/topic/2154/using-a-custom-pu-pyui-view-in-another-one-using-ui-editor/15

              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

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

                Thanks! Someone pointed me to that in the Slack channel too :)

                The cleaned up example will be handy.

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

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

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

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

                      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})
                      
                      Phuket2 2 Replies Last reply Reply Quote 2
                      • Phuket2
                        Phuket2 @JonB last edited by

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

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

                            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)
                            
                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post
                            Powered by NodeBB Forums | Contributors