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.


    Copying UI editor object programmatically

    Pythonista
    6
    20
    11367
    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.
    • donnieh
      donnieh last edited by

      Is there a clean way to make a ui.Button for example in the UI editor and then make a object (or copy) of it programmatically?

      If so this would be cool because I could make the object quickly the first time in the editor, then use it many times through out the code.

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

        A few weeks ago, I was asking questions like this. But, it is really not the right approach. As I say, a few weeks ago I thought it was.
        I am sure you can do what you want. I tried to code it but failed :( But I was very close (I am still a beginner). But basically opening a view file, get a reference to the object you want ie(ui.Button) then create a new ui.Button() then pass both objects to a function that assigns the attributes from you button you loaded from the pyui file to the ui.button you created dynamically. But still a lot of code for not much. In my code example below, if you look at the function my_std_button(), does what you are asking for as far as I can see. There is more code than needed, I wanted to show reuse clearly rather than put one button on the screen. I hope it helps.

        
        #please dont copy my code, i am still a beginner
        # a lot smarter people around.
        # i am still on the learning journey! 
        
        import ui
        _padding = 10
        _placement = ['tl', 'tc', 'tr', 'c', 'bl', 'bc', 'br']
        
        # the obj_pos code not about the answer. just used to position the objects on the screen without having to reinvent the wheel. 
        def obj_pos(obj, pos, pad=_padding):
        	pos = pos.lower()
        	
        	f = obj.superview.frame
        	l,t,w,h = f[0],f[1],f[2],f[3]
        	
        	if 'c' in pos:
        		#deal with centering first, otherwise will get side effects. eg tc (top,center) would fail
        		obj.x = (w/2) - (obj.width/2)
        		obj.y = (h/2) - (obj.height/2)
        		
        	for c in pos:
        		if c == 't':
        			obj.y = t + pad
        		elif c == 'l':
        			obj.x = l + pad
        		elif c == 'b':
        			obj.y = h - obj.height - pad 
        		elif c == 'r':
        			obj.x = w - obj.width - pad
        		
        def my_std_button(the_title):
        	#this btn could be what you want to try and copy from the ui. only one way to do it. other ways using a dict and setattr.
        	btn = ui.Button(title = the_title)
        	btn.background_color = 'red'
        	btn.tint_color = 'white'
        	btn.border_width = 1
        	btn.corner_radius = 5
        	btn.width = 64
        	btn.height = 32
        	btn.action = my_button_click
        	return btn
        
        def my_button_click(sender):
        	# of course you could send the callback function to the my_std_button function if you wanted each button to have a different click function.
        	print sender.title
        	# could dispatch here with if/elif on title or name....
        
        if __name__ == '__main__':
        	v = ui.View()
        	v.frame = (0,0,540,576)
        	v.background_color = 'white'
        	#create as many btns in the list _placement
        	#just to demonstrate reuse
        	for i in _placement:
        		btn = my_std_button(i) # getting a new copy of ui.button here, acting like a template
        		v.add_subview(btn)
        		obj_pos(btn, i)
        	
        	
        	v.present('sheet')
        
        
        1 Reply Last reply Reply Quote 0
        • dgelessus
          dgelessus last edited by

          If you want to have multiple copies of the entire UI, just load_view it multiple times:

          import ui
          
          v1 = ui.load_view("test_ui")
          v2 = ui.load_view("test_ui")
          

          Now v1 and v2 are two identical, but separate views. You could add them as subviews of another view (at different coordinates) or use them separately in some other way.

          If you only want to copy a single UI element, create a new one of the same type and copy all necessary attributes. For example:

          import ui
          
          root = ui.load_view("test_ui")
          button1 = root["button1"]
          button2 = ui.Button()
          root.add_subview(button2)
          button2.width = button1.width
          button2.height = button1.height
          button2.title = button1.title
          # ...
          # Place button2 to the right of button1
          button2.x = button1.x + button1.width
          button2.y = button1.y
          
          1 Reply Last reply Reply Quote 0
          • donnieh
            donnieh last edited by

            @dgelessus

            Haha your response looks like a textbook. Got it!

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

              Well this is interesting. When adding a UI editor label to a scroll view using your first method, it is not added as a sub view but put on top of the scroll view - see pic. (I've created a label in separate UI view and tried adding it inside a scroll view).

              import ui
              
              sv = ui.load_view('main')
              lbl = ui.load_view('lbl')
              sv.add_subview(lbl)
              sv.x = 0
              sv.y = 0
              lbl.x = 0
              lbl.y = 100
              sv.present(hide_title_bar=True)
              

              Here are some pics to show what I got:

              https://www.dropbox.com/s/ukisx2rvx0367ls/photo jun 21%2C 10 17 40 am.png?dl=0

              https://www.dropbox.com/s/m7pfjkp2ueqlmch/photo jun 21%2C 10 21 56 am.png?dl=0

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

                I often use the copy module.
                import copy

                newobj = copy.copy(oldobj)

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

                  See copy_button.py for a working example that copies attributes like action, border width, rounded corners, etc. If you have a better way, please submit a pull request.

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

                    Well this is all interesting stuff.... The copy module is extremely useful.

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

                      @Gcarver, you brought up a great point for me with the copy. I have a custom object inheriting from ui.View. If that object, I create a lot of other views and buttons. Just positioning the views and buttons and assigning attributes.
                      If another custom class I create 31 of these objects in a for statement. It's a bit sluggish. I was going to go back later and see what I could to to optimise it. But when I seen your post about copy, I dawned on me to try creating one object and just do copies instead. Massive speed increase, except it doesn't really work. I am pretty sure my code is working, was just a little rework.
                      I can see that new objects are being created, but I don't see my object on screen. I read a little about it and can see that maybe I need to use deepcopy instead of copy. I tried using deepcopy, the function just never return. Was hoping you or someone else have any ideas?

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

                        I can see that new objects are being created, but I don't see my object on screen.

                        Are you changing my_ui_item.x and/or my_ui_item.y so it is not hidden underneath the original?

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

                          @ccc, yes I am changing all the x/y cords. I will do some more checking. Mabe I have made a silly mistake. I have a placement method to do this. I only changed the creation point in the code. I will look further, also change it back. But if it's really working, timed code around the for loop went from 0.358 to 0.02, a very exciting increase in speed

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

                            Are you doing: main_view.add_subview(my_ui_item) ?

                            Do print(my_ui_item.frame) to ensure there are no zero values.

                            Is your copy routine throwing exceptions? (Surround it with a try/except block)

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

                              @ccc, I have listed the creation code of the objects below. Also links to pics of the results on screen. But after the creation code nothing changes. Something must be gapping in the copy code unless I have made a stupid blunder, which is highly possible, I hope I have...

                              '''
                              			Create 31 day objects only once, dont display yet.
                              			put the day objects in their own view. 
                              			TODO:
                              		'''
                              		v = ui.View(frame = self.frame)
                              		v.height -= cp.wb_height
                              		v.y = cp.wb_height
                              		v.background_color = cp.dv_bg_color
                              		w = self.width / 7
                              		h = w * cp.di_height_ratio
                              		print w,h
                              		self.day_height = h
                              		self.day_width = w
                              		
                              		_USE_COPY = True  
                              		if _USE_COPY:
                              			start = time.time()
                              			d_item = day_item(i, w,h)
                              			d_item.day_action(self.day_button_click)
                              			
                              			for i in range(1,32):
                              				try:
                              					new_item = copy.copy(d_item)
                              					new_item.day_title = i
                              					v.add_subview(new_item)
                              					self.day_obj.append(new_item)
                              				except:
                              					print 'a problem'
                              			
                              			self.add_subview(v)
                              			self.days_view = v
                              			
                              			finish = time.time()
                              			print finish - start
                              		else: 
                              			start = time.time()
                              			for i in range(1,32):
                              				d_item = day_item(i, w,h)
                              				d_item.day_action(self.day_button_click)
                              				v.add_subview(d_item)
                              				self.day_obj.append(d_item)
                              			self.add_subview(v)
                              			self.days_view = v
                              			finish = time.time()
                              			print finish - start
                              

                              Result when using copy
                              https://www.dropbox.com/s/mi16axib4l5ccyk/file 22-06-2015 18 14 31.png?dl=0
                              Result when not using copy
                              https://www.dropbox.com/s/kqnq526o3q54d30/file 22-06-2015 18 15 06.png?dl=0

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

                                I should mention that the above code is executed in the init function of my class, in case that has some weird affect on things.

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

                                  Also, @ccc, your copy_button.py shed some light on some things for me. Mainly how to step over non attributes in the ui classes. But really nice to know how to do it.
                                  One could say should read all the sample code around, but my brain is not as sharp as it used to be. Also, as I learn more python syntax , the code makes more and more sense to me. So will look more, especially at yours and @JonB code on github. Thanks again guys, is great!

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

                                    don't put this in init, because frame might not yet be created, unless you pass it in explicitly. at a minimum, frame will get changed as soon as you present it, so unless you have flex set up properly, things will not scale as you expect.

                                    actually, id recommend you implement a resize function in your custom view, which relays out the features when rotation occurs for example, which might address the issue.

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

                                      @JonB, thanks. Not sure this will really fix my problem. However, it's about time I got serious about orientation etc... I sort of thought I had it figured out, and to my surprise I don't :) I am not really surprised. I started off with a small simple class for drawing the days of the week across the top of the screen. Just using buttons in a view. If I create the view without it being a subview of another view, I am getting called on the layout method as expected. However as soon as I add the custom view as a subview to another view the layout method is called twice on start up and no more calls to layout arrive as I change the orientation of my iPad.
                                      The only code difference I have in the custom class is that if it has a superview I set the customs class frame to the bounds of the superview. This seems to be the correct thing to do. But I tried many variations without success. Sorry to bother you, if it's a stupid thing I have done here, I have really tried to figure it out myself without any success.

                                      import ui
                                      
                                      week_days = ['MON','TUE','WED','THU','FRI','SAT','SUN']
                                      
                                      
                                      class day_title_bar(ui.View):
                                      	def __init__(self):
                                      		self.day_titles=[]
                                      		
                                      		#create the 7 buttons that will be day headings
                                      		for i in range(0,7):
                                      			btn = ui.Button(title = week_days[i])
                                      			self.day_titles.append(btn)
                                      			self.add_subview(btn)
                                      		
                                      		#set all the style attributes for the view
                                      		self.style()
                                      		
                                      	def layout(self):
                                      		# check to see if the view has a superview
                                      		#if its added as a subview, it will have
                                      		# a superview, otherwise it wont have
                                      		
                                      		if self.superview:
                                      			self.frame = self.superview.bounds
                                      			
                                      		w = self.width / 7
                                      		for i in range(0,7):
                                      			btn = self.day_titles[i]
                                      			btn.width = w
                                      			btn.height = 60
                                      			btn.x = i * w
                                      			
                                      	def style(self):
                                      		self.background_color = 'white'
                                      		
                                      		for i in range(0,7):
                                      			btn = self.day_titles[i]
                                      			btn.background_color = 'red'
                                      			btn.tint_color = 'white'
                                      			btn.font = ('<system>',18)
                                      				
                                      if __name__ == '__main__':
                                      	
                                      	__ADD_AS_SUB_VIEW = True     
                                      	
                                      	if __ADD_AS_SUB_VIEW:
                                      		# this way, the custom classes layout method
                                      		# is only called twice, both at start up
                                      		# as the ipad orientation changes, 
                                      		# i dont recieve any more layout method calls
                                      		v = ui.View()
                                      		x = day_title_bar()
                                      		v.add_subview(x)
                                      		v.present()
                                      	else:
                                      		# this works as expected. layout is called 
                                      		# when the ipad orientation changes
                                      		v = day_title_bar()
                                      		v.present()
                                      
                                      
                                      1 Reply Last reply Reply Quote 0
                                      • ccc
                                        ccc last edited by

                                        x.flex = 'WH'

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

                                          @ccc, thanks. I want to chew off my own arm at the moment. God, the amount of time I sent on trying to get it correct myself. I was not thinking out of the box. I would have thought that these events would have bubbled up all the parent views. But I guess this sort of explicit way saves redundant callbacks. But still would be nice to register the specific callbacks you would like to receive :)
                                          I added the self.flex = 'WH' to the init code of the custom class. I check the various ways of creating the class and all seems ok.
                                          Thank you :)

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

                                            @ccc, @JonB, the old saying the more you know the less you know is coming in to play :) but going back refactoring my crappy code to be orientation friendly. Is quite a journey. But a good one, better I do it now and understand it than further down the track. You start to see some cool things happen. Like a form of double buffering. I am not really sure what's going on, but I guess from the start of calls to layout to the completion, all screen writes are being double buffered. Well, that's my old name for it anyway. If that's in the documentation, I didn't see it. But I think an addendum on the virtues of writing custom classes would be great. It's becoming very clear to me now that even the simplest ui tasks are simplified by custom classes.

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