0

I have my code set up to initially add a few button widgets.

The first button will spawn a rectangular box. This is written as addfunction().

the second button is bound to perform hookupfull(), which does two things - gethooks() and hookfunc():

First it grabs the pos, width, and height of the children of my MainWindowWidget, and puts it in a list for each of those, then deletes extra entries from the buttons (I have my buttons as children of the MainWindowWidget currently, but I only want the properties of the boxes). This is written as gethooks()

Secondly it calculates fancy coordinates from the list and draws a line. This is written as hookfunc()

So, if I press the first button twice and the second button once, it will create two boxes and then draw a line connecting them together. This works fine. Next on my agenda is to schedule stuff like canvas.clear() and redraw the line, etc. every 1/60th of a second. I then create a third button widget to set a flag to start update loop run.

However if I try to schedule hookupfull() with Clock.schedule_interval() it doesn't work as I think it should - not sure how to explain it or what is going on, but the scheduled code doesn't seem to "go" to the MainWindowWidget I want. It seems to be spawning a whole bunch of other MainWindowWidgets.

I figured it's the way I'm referring to the widgets or something with the arguments (which is what I assume to be the (self, *args) portion of the code) or the way I'm declaring the method/function (I'm not sure of the difference between methods and functions still, sorry)

So, I tried to debug it by adding stuff like print self in several places to see what self was.

My code:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import Color, Line
from kivy.clock import Clock

boxscale = 1
global startenable
global debug_1
startenable = False
debug_1 = True

class Scatterer(Scatter):
    pass

class Drawer(FloatLayout):
    pass

class MainWindowWidget(FloatLayout):
    def addfunction(self, *args):
        s = Scatterer()
        d = Drawer()
        s.size = 60 * boxscale , 111 * boxscale
        # default size * scale
        d.size = s.size
        self.add_widget(s)
        s.add_widget(d)
        print "button is pressed"

    def startfunc(obj):
        global startenable
        if startenable == False:
            startenable = True
        else:
            startenable = False
        print'startenable set to',startenable


    def gethooks(self, *args):
        # get hook locations
        self.p = [] 
        for child in self.children:
            self.p.append(child.pos)
        del self.p[(len(self.p)-3):(len(self.p))]

        self.w = [] 
        for child in self.children:
            self.w.append(child.width)
        del self.w[(len(self.w)-3):(len(self.w))]

        self.h = []
        for child in self.children:
            self.h.append(child.height)
        del self.h[(len(self.h)-3):(len(self.h))]

        if debug_1 == True:
            print 'getting hook location........'
            print 'self.p:',self.p # list of positions
            print 'length of self.p:',len(self.p)
            print 'widths:',self.w # list of widths
            print 'heights:',self.h# list of heights
        print self

    def hookfunc(self, *args): 
        # draw line based on hooks' position
        self.h_01_x = \
        self.p[0][0]

        self.h_01_y = \
        self.p[0][1] + (self.h[0]/2)

        self.h_02_x = \
        self.p[1][0] + self.w[1]

        self.h_02_y = \
        self.p[1][1] + (self.h[1]/2)
        with self.canvas:
            Line(bezier=(
                self.h_01_x, self.h_01_y,
                self.h_01_x - 20, self.h_01_y,
                self.h_02_x + 20, self.h_02_y,
                self.h_02_x, self.h_02_y,
                ), width=2)

        print self

    def hookupfull(self, *args):
        self.gethooks()
        self.hookfunc()

    def update(self, *args):
        global debug_1
        if startenable == True:
            mww= MainWindowWidget()
            print mww

            mww.hookupfull()

        else: # if startenable is false
            pass

class Test2App(App):
    def build(self):

        Clock.schedule_interval(MainWindowWidget.update, \
                5.0/60.0)
        return MainWindowWidget()

if __name__ == '__main__':
    Test2App().run()

kv file:

#:kivy 1.0.9

<MainWindowWidget>

    NewButton:
        text: 'add'
        pos: 100, 0
        on_release: root.addfunction()

    NewButton:
        text: 'start'
        pos: 200, 0
        on_release: root.startfunc()

    NewButton:
        text: 'hook up full'
        pos: 400, 0
        on_release: root.hookupfull()

<NewButton@Button>:
    font_size: 15 
    size_hint: None, None
    size: 100, 100 

<Scatterer>:
    do_rotation: False
    size_hint: None, None
    size: self.size

<Drawer>:
    size: self.size
    canvas:
        Color:
            rgba: 0, 1, 0, 0.3
        Rectangle:
            pos: self.pos
            size: self.size

What I found out was, when I clicked the button that does hookupfull(), which print self, they always return the same thing such as <__main__.MainWindowWidget object at 0x7f110fcef530> no matter how many times hookupfull() is called.

However when the same function is scheduled, self always returns different widgets.

Some other questions I'm wondering about:

  1. Is there a way to assign id's or references to dynamically created widgets? For example, in my code, pressing the first button will create a new Scatter object. Is there a way to point to that specific Scatter object?

  2. How do I refer to widget grandchildren? For example, in my code, I have a FloatLayout object set up as a child of a Scatter object, which is a child of the MainWindowWidget. The FloatLayout object changes in size due to the Scatter, which acts like a controller (and to my knowledge, doesn't actually change in size). I would like to access the properties such as pos and width of the FloatLayout.

  3. arguments for the method/function declarations such as (self, *args) - what do they do?

ivandaho
  • 43
  • 5
  • You have some confusion about how class instances and definitions work. `MyWindowWidget` is a class *definition*, and when you do `MyWindowWidget()` you get a specific *instance* with its own internal state and properties. That means that (amongst other things) stuff like scheduling `MyWindowWidget.update` doesn't make sense because you're scheduling it for the class definition rather than for the specific instance your program actually displays. – inclement Nov 07 '14 at 16:57

1 Answers1

0

Each function you define in your class should have self as the first parameter (except for static methods) by convention. It's the instance object of the class when the function is called. You were also creating new instances of the main widget in some functions: refer to inclement's comments above. It doesn't make much sense to do this. Please read the manual: https://docs.python.org/2/tutorial/classes.html#class-objects

Regarding your other questions:

  1. Is there a way to assign id's or references to dynamically created widgets?

You can assign and id directly by using the named id parameter when instantiating the object e.g. Scatterer(id='myid'). You can refer to it by its id but my personal preference would be to track the objects with a list (see below)

  1. How do I refer to widget grandchildren?

Simply put, don't! Track them directly in a list instead and use references to their properties instead of creating secondary lists of dimensions:

def __init__(self, *args, **kwargs):
    self.box_list = [] # <---- set up list to track boxes
    super(MainWindowWidget, self).__init__(*args, **kwargs)
....
....
    if len(self.box_list) > 1:    
        box1 = self.box_list[-2] # grab the last two boxes added to the list
        box2 = self.box_list[-1]
        h_01_x = box1.x
        h_01_y = box1.y + (box1.height/2)
        h_02_x = box2.x + box2.width
        h_02_y = box2.y + (box2.height/2)
        with self.canvas:
            Line(bezier=(
                h_01_x, h_01_y,
                h_01_x - 20, h_01_y,
                h_02_x + 20, h_02_y,
                h_02_x, h_02_y,
            ), width=2)

I would like to access the properties such as pos and width of the FloatLayout.

The scatter does have both size and pos. Both are updated when you change the object. It would also be better if you declared your FloatLayout directly in kivy as a child of Scatterer.. There's no need to create them separately

  1. arguments for the method/function declarations such as (self, *args) - what do they do?

self is a reference to the instance whose function has been called. *args is the expansion of the list of unnamed parameters. Take a look at this question: *args and **kwargs?

As a final comment, if you call canvas.clear as you mentioned in your description then you'll erase the buttons and boxes too because you're using the canvas of the main widget. Create another class to contain your line and link its position to the boxes. When you call canvas.clear on that object then it'll clear only the line. If you update the line object when the box position/size changes then you can also move the rendering of the line into the kivy file and get rid of the extraneous clock scheduling.

Community
  • 1
  • 1
tiktok
  • 456
  • 3
  • 13