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.


    Attaching a class to a ui element such as uiButton (I am desperate)

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

      My approach to this is to abuse delegates, because they are Python classes/objects that can have arbitrary attributes. It would be nice if all ui.View subclasses could have custom attributes, I assume they can't yet because they are C classes without a __dict__ or something?

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

        @JonB, thank you, I sort of thought I could do something like telegraphing, but in my opinion, way to messy and can break so easily. In my mind, I want a solution that won't break. As I would like to build Libs off this construct. If its fragile code, really no point. Could be ok for one small project, but then again one small project you don't need the flexibility. Could just hard code it and be done with it.
        Omz says he is working on something, not sure about the implementation he has in mind. In my view he just needs to add a variable to each ui element. (Yes I know I am talking about the tag again) but in this language it's 1 million times better than what we had.

        If we didn't care then would be no point discussing :)

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

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

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

            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 of CustomAction 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:

            1. You tap the button
            2. ui calls button.action
            3. button.action is not a function, but it has a __call__ method, so that gets called instead
            4. button.action.__call__ calls button.action.action
            5. button.action.action prints Eureka!

            Like the delegate variant, this only works on views that have an action.

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

              @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

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

                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.
                
                1 Reply Last reply Reply Quote 0
                • ccc
                  ccc last edited by

                  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.

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

                    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.

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

                      @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

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

                        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.

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

                          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.

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

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

                              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 the sender parameter should be enough though, unless you need to use it outside of __call__.

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

                                @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

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