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.
I just don't get it
-
Perhaps this is a 1.6 issue, but I think I'm just missing something. I want to create a spinner widget that allows me to enter a number and increment/decrement it via two buttons (up and down). I have commented out much to isolate what I am doing wrong. On pythonista 1.6, this give me a ivory filled square with a bottom and right black shadow. No upper or left "border". And no uparrow. There is no pyui file, as I want to build this on the fly.
# spinner class import ui class Spinner(ui.View): ''' creates a view with a data entry field and up/down arrows which allow for increment/decrement valid types are int, list, float. A list will be fixed possibilities and limits are ignored ''' def __init__(self, parent, initialValue= 10, increment=1, frame=(20,20, 200,200), limits=(0,100), dataType='int', action=None ): self.parent = parent self.initialValue = initialValue self._value = initialValue self.increment = increment self.action = action self.dataType = dataType self.limits = limits self.frame = frame self.list = [] def buildView(self): # build the view here self.v = ui.View(frame=self.frame) self.v.background_color = "white" self.border_color = 'black' self.border_width = 3 self.parent.add_subview(self.v) # self.vInput = ui.Label( ) # self.vInput.text = "{}".format(self.initialValue) # self.vInput.bounds = (0,0,40,40) # self.vInput.text_color = 'black' # self.vInput.border_color = 'black' # self.vInput.alignment = ui.ALIGN_CENTER # self.vInput.border_width = 1 # self.v.add_subview(self.vInput) # self.vInput.bring_to_front() # self.vInput.hidden = False self.upArrow= ui.Button() self.upArrow.bounds = (0,60,50,50) self.upArrow.bg_color = 'ivory' self.upArrow.border_color = 'black' self.upArrow.border_width = 1 self.upArrow.name = 'upBtn' self.upArrow.action = self.onArrow self.upArrow.background_image = ui.Image.named('ionicons-arrow-up-b-24') self.upArrow.enabled = True self.v.add_subview(self.upArrow) self.parent.add_subview(self.v) # # self.dnArrow = ui.Button() # self.v.add_subview(self.dnArrow) # self.dnArrow.name = 'dnBtn' # self.dnArrow.action = self.onArrow @property def value(self): return self._value @value.setter def value(self,input): self._value = input def onArrow(self,sender): pass def reset(self): self.value = initialValue if __name__ == '__main__': view = ui.View(background_color = 'white') spinner = Spinner(view,frame=(200,200,100,100),initialValue = 500, limits=(0,1000),increment=10) spinner.buildView() view.present('full_screen')
-
The basic problem is that a Spinner is a subclass of ui.View but you were also trying to add a ui.View (self.v) to it which confused the logic.
# spinner class import ui def make_button(name, bg_image_name, loc_xy=(10, 10)): button = ui.Button(name=name) button.frame = (loc_xy[0], loc_xy[1], 50, 50) button.bg_color = 'ivory' button.border_color = 'black' button.border_width = 1 button.background_image = ui.Image.named(bg_image_name) button.enabled = True return button def make_label(text, loc_xy = (10, 10)): label = ui.Label( ) label.text = str(text) label.bounds = (loc_xy[0], loc_xy[1], 40, 40) label.text_color = 'black' label.border_color = 'black' label.alignment = ui.ALIGN_CENTER label.border_width = 1 label.bring_to_front() return label class Spinner(ui.View): ''' creates a view with a data entry field and up/down arrows which allow for increment/decrement valid types are int, list, float. A list will be fixed possibilities and limits are ignored ''' def __init__(self, initialValue= 10, increment=1, limits=(0,100), dataType='int', action=None): self._value = self.initialValue = initialValue self.increment = increment self.limits = limits self.dataType = dataType self.action = action self.list = [] self.add_ui() def add_ui(self): # add user interface elements self.background_color = "white" self.border_color = 'blue' self.border_width = 3 self.label = make_label(self._value, (10, 10)) self.add_subview(self.label) self.upArrow = make_button('upBtn', 'ionicons-arrow-up-b-24', (80, 25)) self.upArrow.action = self.onArrow self.add_subview(self.upArrow) self.downArrow = make_button('downBtn', 'ionicons-arrow-down-b-24', (140, 25)) self.downArrow.action = self.onArrow self.add_subview(self.downArrow) @property def value(self): return self._value @value.setter def value(self,input): self._value = input def onArrow(self,sender): # you might want to consider tap-and-hold functionality increment = self.increment * (-1 if 'down' in sender.name.lower() else 1) if self.limits[0] <= self._value + increment <= self.limits[1]: self._value += increment self.label.text = str(self._value) def reset(self): self._value = self.initialValue if __name__ == '__main__': view = ui.View(background_color = 'white') spinner = Spinner(initialValue = 500, limits = (0,1000), increment = 10) view.present('full_screen') spinner.frame = view.bounds view.add_subview(spinner)
-
I'm pretty sure a view can only be added as a subview to one view, one time.
The code above, you added v to the parent twice! In that process, the button is getting its frame confused.My suggestion: since Spinner is a ui.View, the behavior should act like a View. That means, to get it to snow, you would use
view.add_subview(spinner)
in main. The spinner should not add anything into the parent view.
v
should be a subview of self, not of the parent view.See Checkout ui.CheckBox in the uicomponents for an example... In your case you'll add two buttons and a textfield to self. Or, for another example, see dropdown, which does have a textfield and a single button, so the setup will look similar to what you want.
In the dropdown case, I specifically DID need to add something to the root view, though you can find that dynamically --- but the only time you need to do something like that is if you need to display something outside your custom view's frame... In this case I wanted to prevent interaction with any other ui components, so I create a view that covers up the root view until a choice is made.
-
Here is my quick solution: includes a textfield for direct data entry . I didn't include limits, but I see ccc's approach does.
import ui class Spinner(ui.View): def __init__(self,frame=(0,0,300,32),name='spinner', value=0): '''Simple spinner, supports direct entry. ''' self.frame=frame self.textfield=ui.TextField(frame=frame,name='textfield') self.textfield.autocapitalization_type=ui.AUTOCAPITALIZE_NONE self.textfield.autocorrection_type=False self.up=ui.Button(name='button',bg_color=None) self.down=ui.Button(name='button',bg_color=None) self.add_subview(self.textfield) self.add_subview(self.up) self.add_subview(self.down) h=frame[3] self.up.frame= (self.width-64, h-32, 32,32) self.down.frame=(self.width-32, h-32, 32,32) self.up.image=ui.Image.named('ionicons-arrow-up-b-32') self.down.image=ui.Image.named('ionicons-arrow-down-b-32') self.up.action=self.inc self.down.action=self.dec self.up.flex='l' self.down.flex='l' self.textfield.flex='w' self.textfield.text=str(value) def inc(self,sender): self.textfield.text = str(self.value+1) def dec(self,sender): self.textfield.text = str(self.value-1) @property def value(self): return int(self.textfield.text) @value.setter def text(self,value): self.textfield.text=value #lets you set the spinner.action @property def action(self): return self.textfield.action @action.setter def action(self,value): if callable(value): self.textfield.action=value else: self.textfield.action = lambda : None if __name__=='__main__': v=ui.View() v.present() s=Spinner() v.add_subview(s)