When to bind to attributes that populated with kv-file? - python

test.kv
<RootWidget>:
test: test
Label:
id: test
text: "some"
test.py
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
# 1:
print(self.test) # None
# 2:
def after_tick(*args):
print(self.test) # Label object
Clock.schedule_once(after_tick, 0)
super().__init__()
If I'll try to bind something to self.test directly inside __init__ (1), I would get AttributeError since kv rules aren't applied yet and self.test is None. Possible workaround would be to bind to attribute after first event loop iteration (2).
This solution seems to be duct tape. Is there any better way?

I know 3 other ways.
First, make the after_tick a method and execute it in init:
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.delayed_init()
#mainthread
def delayed_init(self):
print(self.text)
Second, use a built-in method that is always executed after the first loop frame:
class RootWidget(Widget):
test = ObjectProperty(None)
def add_widget(self, widget, index=0):
super().add_widget(widget, index)
if widget.__class__.__name__ == 'Label':
widget.text = self.test.text
This is useful if you have a lot of same type widgets to bind.
Third, use a property's on_x callback:
class RootWidget(Widget):
test = ObjectProperty(None)
def on_test(self, *args):
print(self.test)

Related

Using decorator to change the return value of a method

I have a TextBox and a Button. When the user hits Enter, I want the specific button related to the TextBox to be clicked. TextBox and Button are classes. I am passing Button instance into the TextBox, so that we know one should be clicked. Button is a class with 2 methods click() and autoclick().
What I want is for autoclick() to check if Enter key is being pressed and make click() return True if it is. I tried using decorators and wrote this toy program to demonstrate exactly what I tried.
class Button:
def click(self):
return False
def autoclick(self, func, *args):
def wrapper():
func(*args)# This function needes events as it argumnet
return True
return wrapper
b = Button()
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick(self.button.click, events)()
tb = TextBox(button=b)
while True:
tb.box()
if b.click():
print("this needs to be printed when user hits enter")
I don't get any errors but it simply doesn't work.
I think I may know what you want to do now.
Try this out. This is how we usually use decorators.
class Button:
#autoclick # this is how decorators are used.
def click(self):
return False
def autoclick(self, func):
def wrapper(*args):
func(*args)# This function needes events as it argumnet
return True
if enterPressed:
return wrapper
else:
return func
I think the following part is needed to be modified
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick(self.button.click, events)()
to something like:
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.click = self.button.autoclick(self.button.click) # this line is changed
UPDATE
I guess maybe you are supposed to mean something like the following:
class Button:
def _click(self): # Maybe this method have events as parameters?
return False
def click(self):
return self._click()
def autoclick(self):
def wrapper(*args):
self._click(*args) # This function needs events as it arguments
self.click = self._click
return True
self.click = wrapper
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick()
Or maybe this one? :
class Button:
def _click(self): # Maybe this method have events as parameters?
return False
def click(self):
return self._click()
def autoclick(self):
def wrapper(*args):
result = self._click(*args) # This function needs events as it arguments
if enterPressed:
return True
return result
self.click = wrapper
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if self.button is not None:
self.button.autoclick()

Kivy binding to on_property doesn't seem to work

Essentially, I have a grid with squares, and I keep track of which squares are occupied with a BooleanProperty on each square. Here's a simplified version of all the places in my code I declare the "occupied" property:
class Board(GridLayout):
def __init__(self):
super().__init__()
self.cols = 4
self.grid = []
self.create_slots()
def create_slots(self):
for i in range(10):
self.grid.append([])
for j in range(4):
temp = Square(i,j, "sideboard")
self.grid[i].append(temp)
self.add_widget(temp)
temp.bind(on_occupied = self.do_a_thing)
def do_a_thing(self):
for square in self.children:
#do a thing
class Square(Button):
def __init__(self, row, col, type):
self.row = row
self.col = col
self.id = str(self.row) + "," + str(self.col)
self.type = type
self.occupied = BooleanProperty(False)
super().__init__()
My goal is to bind the "do_a_thing" method to be called each time the value of the square's "occupied" property changes. Because the Square class is used elsewhere in my app, I don't want to set the callback for on_occupied in the kivy language, and I was hoping to avoid creating a Square sub-class just to change the one binding.
When I run my code, it throws no errors, and I've verified that the "occupied" property does actually change. But the "do_a_thing" method never gets fired. Can anyone tell me what I'm doing wrong?
Note that for a property my_property, the change event is called my_property as well. The callback receives two arguments: instance that fired the event, and new value of the property, as shown in the docs. Also, if the class has a method called on_propertyname, this will be called as well.
Here is a self-contained example that works for me:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import BooleanProperty
class Board(GridLayout):
def __init__(self, **kwargs):
super(Board, self).__init__(**kwargs)
for i in range(10):
self.add_widget(Square())
for square in self.children:
print square
square.bind(occupied=self.do_a_thing)
def do_a_thing(self, *args):
print "hello from {}, new state: {}".format(*args)
for square in self.children:
pass
#do a thing
class Square(Button):
occupied = BooleanProperty(False)
def on_occupied(self, *args):
print "Callback defined in class: from {} state {}".format(*args)
class mApp(App):
def build(self):
return Builder.load_string("""
Board:
cols: 4
rows: 3
<Square>:
on_press: self.occupied = ~self.occupied
""")
mApp().run()

Inheritance in Variable Value - Python

Well, I'm having trouble in a specific part of my code. It uses kivy, however I'm pretty sure there is a python solution. Here is the thing: I'll have a button, when pressed will take me to another screen, when it's pressed, calls method vai, that changes, or should change the string variable value that was created in init method. Afterwards, when another screen shows up, its button receives that CHANGED variable at text parameter. But the real issue is that by the time second screen appears, the button text does not change, remaining the value I set up on init once, not the changed value on vai method.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
class Principal(App):
def build(self):
return SM()
class SM(ScreenManager):
def __init__(self):
super(SM, self).__init__()
self.add_widget(Um())
self.current = 'TelaUm'
class Um(Screen):
def __init__(self):
super(Um, self).__init__()
self.name = 'TelaUm'
self.add_widget(UmC())
class UmC(FloatLayout):
def __init__(self):
super(UmC, self).__init__()
self.btnSelecionado = 'qualquer_merda'
self.btn = Button(id = 'Bosta!', text = 'Bosta!', pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None))
self.btn.bind(on_press = self.vai)
self.add_widget(self.btn)
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
class Dois(Screen):
def __init__(self):
super(Dois, self).__init__()
self.name = 'TelaDois'
self.add_widget(DoisC())
class DoisC(UmC, FloatLayout):
def __init__(self):
super(DoisC, self).__init__()
self.btn2 = Button(text = self.btnSelecionado, pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None) )
self.add_widget(self.btn2)
Principal().run()
I don't completely understand what you are trying to do. But it looks to me like the second screen is a new instance of UmC and therefore has its own value of btnSelecionado. So inevitably this new instance has the value from init since it's only the old instance that has been changed.
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
Line 2 creates a new instance and line 4 sets the value in the old instance.

Kivy: Self-updating label text

Let's say I have 3 classes: a "woking class" where stuff takes place, a label class and a class to contain them.
For example the label class could be a status bar showing the status of something going on the working class. I wish I could find a way to make the label self-update the value to show, since this value is a value of the working class being changed inside the latter.
Here I have an example code
Builder.load_string('''
<CustomLabel>
text: 'Value is {}'.format(root.value)
<WorkingClass>:
orientation: 'vertical'
Button:
text: 'Update'
on_release: root.update()
<MainLayout>
orientation: 'vertical'
''')
class CustomLabel(Label):
value = NumericProperty()
class WorkingClass(BoxLayout):
def __init__(self, *args, **kwargs):
super(WorkingClass, self).__init__(*args, **kwargs)
self.a = 5
def update(self):
self.a += 1
print(self.a)
class MainLayout(BoxLayout):
def __init__(self, *args, **kwargs):
super(MainLayout, self).__init__(*args, **kwargs)
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.add_widget(self.customLabel)
self.add_widget(self.workingClass)
class MyApp(App):
def build(self):
return MainLayout()
if __name__ == "__main__":
MyApp().run()
Is there a way of doing it with properties or whatever? Becouse I don't want to need to manually update (sommehow) the label each time I change the value. Anyway to achieve this?
You're updating a property on WorkingClass, but that doesn't update the value on CustomLabel since you did a direct assignment instead of binding it. But yes, you can use Propertys to make everything work automatically.
In WorkingClass:
class WorkingClass(BoxLayout):
a = NumericProperty()
def __init__(self, **kwargs): ...
This makes a into a Property which you can bind to.
Then in MainLayout's constructor:
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.workingClass.bind(a=self.customLabel.setter('value'))
The last line says: "when the value of property a on self.workingClass changes, set the value property of self.customLabel to the same value"
Alternatively, you could just add the Property to WorkingClass above, then get rid of MainLayout's constructor and use kv instead:
<MainLayout>:
orientation: 'vertical'
WorkingClass:
id: working_class
CustomLabel:
value: working_class.a # assigning one property to another in kv automatically binds

detecting click/touch in kivy textinput

I have a textinput that I want to focus on when the user clicks/touches on it. (Fairly standard!) It inherits from DragableObject (a user example in the kivy wiki) and GridLayout.
class DragableObject( Widget ):
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return True
def on_touch_up( self, touch ):
if touch.grab_current is self:
touch.ungrab( self )
return True
def on_touch_move( self, touch ):
if touch.grab_current is self:
self.pos = touch.x-self.width/2, touch.y-self.height/2
class MyWidget(DragableObject, GridLayout):
def __init__(self, **kwargs):
kwargs['orientation'] = 'lr-tb'
kwargs['spacing'] = 10
kwargs['size_hint'] = (None, None)
kwargs['size'] = (200, 80)
self.cols = 2
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=self.size)
with self.canvas.before:
Color(0, .5, 1, mode='rgb')
self.bind(pos=self.update_rect)
self.bind(size=self.update_rect)
self.add_widget(Label(text='t1'))
self.text1 = TextInput(multiline=False)
self.add_widget(self.text1)
self.add_widget(Label(text='t2'))
self.text2 = TextInput(multiline=False)
self.add_widget(self.text2)
# these bind's don't seem to work
self.text1.bind(on_touch_down=self.set_focus)
self.text1.bind(on_touch_up=self.set_focus)
self.text1.bind(on_touch_move=self.set_focus)
def set_focus(self):
print("pressed")
self.text1.focus = True
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
I have two problems.
a. The text input is unfocusable.
b. I can't get an event callback (such as on_touch_down) to work on the textinput widget.
Any ideas?
Short Answer
You can simple use an Scatter. It includes dragging, rotation and scaling functionality and you can deactivate any or all of them:
my_scatter = Scatter(do_rotation=False, do_scale=False)
None of the problems you described should happen inside a Scatter
Long Answer
Your problem is that you are overriding the on_touch_down, on_touch_move and on_touch_up of the parent.
Normally these methods will call the corresponding ones of the children. For example, when the on_touch_down method of a Widget instance is called, then the Widget instance is going to traverse its children, calling the on_touch_down method of each of then (If you are familiarized with recursion and tree structures, we are talking about a recursive traverse method - I think pre-order - Tree traversal).
This functionality is overridden in the DraggableObject class. You got to be sure to call the method of be base class with:
super(DraggableWidget, self).on_touch_down(touch)
Depending on what is the behaviour you are looking for the methods could look like:
(1) If you always want to call the children:
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return super(DraggableWidget, self).on_touch_down(touch)
(2) If you just want to call the children when there is no collide:
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return True # Don't call the on_touch_down of the Base Class
return super(DraggableWidget, self).on_touch_down(touch)
And there is more options!. The return value indicates if the event was or not handled by any children. You can, for example, do something like this:
def on_touch_down( self, touch ):
handled = super(DraggableWidget, self).on_touch_down(touch)
if not handled and self.collide_point( *touch.pos ):
touch.grab( self )
return True
return handled
In this case, you will avoid the Widget to be dragged when one of the children handle the event. It all depends on what you do.

Categories