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.
Attaching a class to a ui element such as uiButton (I am desperate)
-
@dgelessus, wow, sorry I also don't get it. I guess I am missing something here. as far as I know only a few ui objects have delegates. Unless it's undocumented, a ui.Button for example does not have a delegate class as far as I can see, or does it?
-
No, buttons don't have a delegate, this solution of course only works with objects that have a delegate. I've never had to store extra data on delegate-less elements, so that always worked for me.
Gcarver's solution uses a custom class, which probably looks something like this:
class CustomAction(object): def __init__(self, action=None): self.action = action def __call__(self, sender): return self.action(sender)
The
__call__
method makes instances ofCustomAction
callable like a function:def real_action(self, sender): print("Eureka!") fake_action = CustomAction(real_action) button = ui.Button() button.action = fake_action button.action.random_attr = 42 # No error!
Because
button.action
is an instance of a Python class, you can assign new attributes to it.Now let's look at what happens when
button.action
is called:- You tap the button
ui
callsbutton.action
button.action
is not a function, but it has a__call__
method, so that gets called insteadbutton.action.__call__
callsbutton.action.action
button.action.action
printsEureka!
Like the delegate variant, this only works on views that have an
action
. -
@dgelessus, just return home after a hard night out. I think i follow the CustomAction code. I will give it a try in in some hours from now. 1:05am here already. But really thank you, it looks promising
-
neat idea with the
__call__
... I learned something new!yet another variation on gcarvers approach, which could be combined above: generally if you have extra data, you are going to be modifying it outside of an elements
action
... so it is not necessary to access the custom object from within the ui element -- you probably already have a custom View containing class which can store the data object in a more accessible form(such as a list, or dict, or named attribute). if you use custom @property getters/setters, the underlying data is accessible, and can also update the ui element when the data changes.here is a simple counter which has a data object, which also acts as a controller, as you can update the count which then automatically updates the view. this is a trivial example, but you could easily imagine a complicated object that updates a complicated bit of view... for instance, a Day controller object might have multiple representations, such as might be shown on a whole month vs single day. the higher level view doesn't need to know about how to draw a Day, the Day object can take care of that, and only triggers updates when the data changes
import ui class counter(object): def __init__(self): self._count=0 self.button=None def getButton(self): '''return the ui button object associated with this object, or cate a new instance''' if self.button is None: self.button=ui.Button(bg_color=(0,0,1)) self.button.action=self.button_action self.button.title=str(self._count) self.button.width=100 self.button.height=100 return self.button @property def count(self): return self._count @count.setter def count(self,value): '''update count attribute, and update the button title to show current count''' self._count=value if self.button: self.button.title=str(self._count) def button_action(self,sender): '''increment count in the underlying model. the @property takes care of updating the ui''' self.count+=1 b=counter() v=ui.View(bg_color='white') v.add_subview(b.getButton()) v.present() #note, if you set b.count from outside the view, the button title gets updated.
-
Why not create a
ButtonView
class that is a ui.View that embeds a ui.Button inside and attach the Button frame size and action to the ButtonView frame size and action? This provides an object which can be customized to hold any data and have complex functionality yet it is a ui.View so it behaves nicely (resizing, hiding, colors, etc.) within a hierarchy of ui elements. By making all the dates on your calendar ButtonViews, you could have custom ui elements that held rich data and functionality yet in a ui view hierarchy, they behave the way that you expect all ui elements to behave. -
Guys, thanks for all the input. I have had some friends turn up to visit (I live in a tourist town), I haven't had time to try much, but I will try each solution. I can not just read each solution and understand all the ramifications. Just don't have the experience yet. Just wanted to let you all know I appreciate all your solutions and I will definitely try them all.
-
@Dgelessus, I could not get your example working exactly. But is still great. Thank you @Gcarver!!
Is a very nice option. I did this :import ui class CustomAction(object): def __init__(self): self.action = self.real_action self.myid = 666 def __call__(self, sender): return self.action(sender) def real_action(self, sender): print("Eureka!") btn = ui.Button(title = 'test') btn.action = CustomAction() btn.action.random_attr = 42 # No error! print btn.action.myid, btn.action.random_attr v = ui.View() v.add_subview(btn) v.present('sheet')
The ui elements that have action
Button has action method
ButtonItem has action method
SegmentedControl has action method
Slider has action method
Switch has action method
TextField has action method
DatePicker has action method -
After playing around, and reading, my previous post would seem like the best solution for me. Of course it would be better if every single ui element had an action, but it seems the elements I normally deal with today are covered. But ultimately the real solution will come from omz. Just takes one common user property across all ui classes. But it is still a fantastic discussion. Brings out a lot of innovation from you guys.
-
Have to say it's very nice, the CustomAction(object) approach....
import ui import uuid import datetime class CustomAction(object): def __init__(self): self.action = self.real_action self.uuid = uuid.uuid4() def __call__(self, sender): return self.action(sender) def real_action(self, sender): print("Eureka!") if __name__ == '__main__': btn = ui.Button(title = 'test') btn.action = CustomAction() btn.action.random_attr = 42 # No error! btn.action.today = datetime.date.today() print btn.action.uuid, btn.action.random_attr print btn.action.today td = datetime.timedelta(days = 1) btn.action.today += td print btn.action.today print '*' * 59 print 'dir btn' print dir(btn) print '*' * 59 print 'dir btn.action' print dir (btn.action) v = ui.View() v.add_subview(btn) v.present('sheet')
Seems to work very generically.
-
I did some more playing around with the CustomAction Class. I was able to use isinstance to verify if an action has a CustomAction, also if no action is provided from the calling class, a function in the CustomAction is called.
One thing I am not sure about is the getting a reference to the parent object that the CustomAction is attached to. At the moment, I pass the parent object in the init of the CA Class. I Would appreciate if anyone could tell me how I could get a reference without having to pass it as a param. Seems to me I am missing something easy here. This would just clean it up a little.
I think the rest of my code is ok. Just trying to get a basic template in place.import ui import uuid def make_btn(title, use_custom_action = False, action = None): btn = ui.Button(title = title) if use_custom_action: btn.action = CustomAction(btn, action) else: btn.action = action btn.border_width = 1 btn.width , btn.height = 100, 32 return btn class MyCustomClass(ui.View): def __init__(self): self.background_color = 'white' # make a btn with a CustomAction, with action btn = make_btn('Custom',True, self.btn_act) self.add_subview(btn) btn.x , btn.y = 100, 100 # make a btn with a CustomAction, # with no action, will call CustomAction action btn = make_btn('Custom 2',True, None) self.add_subview(btn) btn.x , btn.y = 100, 150 # referencing the CustomAction inline self.ext(btn).group_id = 666 # create a button without the CustomAction btn = make_btn('Normal',False , self.btn_act) btn.x , btn.y = 100, 200 self.add_subview(btn) # pass though reference to the CustomAction def ext(self, obj): return obj.action def btn_act(self, sender): # check to see if sender has a CustomAction if isinstance(sender.action, CustomAction): ca = self.ext(sender) print ca.index, ca.group_id, ca.alt_text, ca.uuid else: print sender.title class CustomAction(object): def __init__(self, parent, action = None, group_id = None ): # i think i need to pass in the parent obj in # the init to be able to recover it in my code self.obj = parent # if no action is passed on init, use a function in the CustomActionClass if not action: action = self.fallback_act self.action = action #some vars i think will be useful self.index = -1 self.group_id = group_id self.alt_text = None # may or may not be useful later self.uuid = uuid.uuid4() def __call__(self, sender): #the magic thanks to pythonista Forums return self.action(sender) # this func is called if no action is supplied # in the init def fallback_act(self, sender): print 'called in the CustomAction' print self, sender if __name__ == '__main__': x = MyCustomClass() x.present('sheet')
-
That is correct. Without storing the "parent" object as an attribute, there is no way to tell what an object's "parent object" is. The reason for that is simple - a single object can have more than one name. For example:
class Useless(object): def __init__(self, attr=None): self.attr = attr test_list = [] useless1 = Useless(test_list) useless2 = Useless(test_list) # Now both useless1 and useless2 have test_list as their parent. # Proof: useless1.attr.append('hi there') print(useless2.attr) # --> ['hi there']
There is no single "parent object" in this case. So yes, you need to store the parent as an attribute on
CustomAction
. In most cases thesender
parameter should be enough though, unless you need to use it outside of__call__
. -
@dgelessus, ok thanks. I thought I might have been missing some trick. But makes sense what you say. I think it's better just to pass the parent each time rather than speculating when you actually might need to use the parent outside call. So I will just do that.
Thanks again