Using and moving Widgets/Buttons in Kivy

2020-07-24 06:12发布

问题:

I'm just starting off with Kivy and it's different to what I'm used to, apologies if I'm making stupid mistakes!

Right now I'm trying to create an app that does the following:

  • Allows creation of Nodes (ellipses for now).
  • Allows the user to position the nodes by dragging.
  • Allows the user to connect the nodes with a line.

So far I've achieved the first, and the second somewhat.

Right now my dragging is not working too well. If I move the mouse too quickly it cancels the move method (as it is no longer in contact). Is there a better way to produce dragging or do I just increase the refresh rate (if so how?).

def on_touch_move(self, touch):
   if self.collide_point(touch.x, touch.y):
       self.pos=[touch.x-25, touch.y-25]

I've tried using Buttons instead, using the on_press method to track the moving better. However now I'm having difficulty updating the position of the button (mostly just syntax).

    class GraphNode(Button):
       background_disabled_down=1
       background_disabled_normal=1

       def moveNode(self):
           with touch:
               self.pos=[touch.x-25, touch.y-25]

I have no idea how to use the touch value, and keep getting an array of errors. (Obviously the current attempt doesn't work, I just thought it was funny).

As you could probably tell, I also don't know how to get rid of the button graphics, as I want to use the ellipse. As an added bonus if someone could show me how to change the colour of the ellipse on button press that would be cool!

The kv file:

    <GraphNode>:
        size: 50, 50
        canvas:
            Ellipse:
                pos: self.pos
               size: self.size
        on_press:
            root.moveNode()

I want to be able to use the touch information to update the position, but don't know how to implement it here.

Full core python code:

    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.uix.button import Button
    from kivy.properties import NumericProperty, ReferenceListProperty,\
        ObjectProperty
    from kivy.graphics import Color, Ellipse, Line


    class GraphInterface(Widget):
        node = ObjectProperty(None)



    class GraphApp(App):

        def build(self):
            node = GraphNode()
            game = GraphInterface()

            createNodeButton = Button(text = 'CreateNode', pos=(100,0))
            createEdgeButton = Button(text = 'CreateEdge')
            game.add_widget(createNodeButton)
            game.add_widget(createEdgeButton)

            def createNode(instance):
                game.add_widget(GraphNode())
                print "Node Created"

            def createEdge(instance):
                game.add_widget(GraphEdge())
                print "Edge Created"

            createNodeButton.bind(on_press=createNode)
            createEdgeButton.bind(on_press=createEdge)
            return game

    class GraphNode(Button):

        def moveNode(self):
            with touch:
                self.pos=[touch.x-25, touch.y-25]


       #def onTouchMove(self, touch):
       #   if self.collide_point(touch.x, touch.y):
       #       self.pos=[touch.x-25, touch.y-25]
        pass

    class GraphEdge(Widget):

        def __init__(self, **kwargs):
            super(GraphEdge, self).__init__(**kwargs)
            with self.canvas:
                Line(points=[100, 100, 200, 100, 100, 200], width=1)
        pass

    if __name__ == '__main__':

        GraphApp().run()

If you need any other info, or anything is unclear, please let me know!

Edit: Second question moved to: Creating a dynamically drawn line in Kivy.

回答1:

First, you should read up on touch events in Kivy. Of particular interest here is the section on grabbing touch events. Basically, you can "grab" a touch to ensure that the grabbing widget will always receive further events from that touch - in other words, the on_touch_move and on_touch_up following that touch event will be sent to your widget.

Basic example:

class MyWidget(Widget):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            touch.grab(self)
            # do whatever else here

    def on_touch_move(self, touch):
        if touch.grab_current is self:
            # now we only handle moves which we have grabbed

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            touch.ungrab(self)
            # and finish up here

But, even better! If you want to be able to drag widgets around, Kivy already has that: Scatter.

Just wrap your widget in a Scatter and you can drag it around. You can also use multitouch to rotate and scale a Scatter, but you can easily disable that (kv):

FloatLayout:
    Scatter:
        do_scale: False
        do_rotation: False

        MyWidget:

Side note - this is incorrect:

class GraphNode(Button):
   background_disabled_down=1
   background_disabled_normal=1

background_disabled_down and background_disabled_normal are Kivy properties - you should set those values in __init__.

Force these values:

class GraphNode(Button):
    def __init__(self, **kwargs):
        super(GraphNode, self).__init__(background_disabled_down='',
                                        background_disabled_normal='', **kwargs)

Suggest these values (better option):

class GraphNode(Button):
    def __init__(self, **kwargs):
        kwargs.setdefault('background_disabled_down', '')
        kwargs.setdefault('background_disabled_normal', '')
        super(GraphNode, self).__init__(**kwargs)

Finally, note that these properties are filenames pointing to the images used for the disabled Button. If you remove these values, and disable your button, it will draw no background whatsoever.