Copying UI editor object programmatically
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.
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,f,f,f 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')
If you want to have multiple copies of the entire UI, just
load_viewit multiple times:
import ui v1 = ui.load_view("test_ui") v2 = ui.load_view("test_ui")
v2are 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
Haha your response looks like a textbook. Got it!
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:
I often use the copy module.
newobj = copy.copy(oldobj)
copy_button.pyfor a working example that copies attributes like action, border width, rounded corners, etc. If you have a better way, please submit a pull request.
Well this is all interesting stuff.... The copy module is extremely useful.
@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?
I can see that new objects are being created, but I don't see my object on screen.
Are you changing
my_ui_item.yso it is not hidden underneath the original?
@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
Are you doing:
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)
@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
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.
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!
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
resizefunction in your custom view, which relays out the features when rotation occurs for example, which might address the issue.
@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()
x.flex = 'WH'
@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 :)
@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.