Not sure why on_touch_up is being fired when the button is released. The other two events, on_touch_down and on_touch_move are not fired.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
print "on_touch_down"
def on_touch_move(self, touch):
print "on_touch_move"
def on_touch_up(self, touch):
print "on_touch_up"
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
btn = Button(text='Click Me')
parent.add_widget(painter)
parent.add_widget(btn)
return parent
if __name__ == '__main__':
MyPaintApp().run()
You've overloaded the up, down and move methods of MyPainterWidget and they execute as defined when clicking on the widget.
A uix.Button doesn't have a on_touch_up method so the event propagates up the widget tree. You can investigate this a little further by changing the order of
parent.add_widget(painter)
parent.add_widget(btn)
to
parent.add_widget(btn)
parent.add_widget(painter)
We now see that the both "on_touch_up" and "on_touch_down" are printed to the console, even when clicking the uix.Button, rather than just "on_touch_up".
These are facets of how kivy handles events, the details of which can be found here
Related
The pictures example in /share/kivy-examples/demo/pictures places Image widget in Scatter. I'd like to extend the example and replace Image with <ImageButton#ButtonBehavior+Image>. However, the touch events are not implemented correctly. The ImageButtons are press-able but the drag functionality from the original example is lost.
At first I simply changed Image to <ImageButton#ButtonBehavior+Image> in the pictures.kv file. I see in the documentation that I may need to (re)implement on-press or on_touch_down. To that end, I've added these methods in the Picture class:
class Picture(Scatter):
source = StringProperty(None)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print('picture touch down')
else:
print('no collide in picture')
super(Picture, self).on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
print('touch move')
super(Picture, self).on_touch_move(touch)
I see the print statements in the terminal. But the ImageButtons are still consuming the on_press, and I know this from a print statement in that event handler. I tried to re-implement on_press and just not do anything with pass, and then I had an idea of calling self.parent.on_touch_down, but I don't have a touch object to pass into it.
One idea would be to
class ImageButton(ButtonBehavior, Image):
'''
def on_press(self):
print("button pressed")
print()
return super(ImageButton, self).on_press()
'''
def on_press(self):
pass
So, say I want the ImageButton to only register a double-clicks, and otherwise, for the widgets to behave just like in the example. How would I achieve that? Somewhat related
While there may be a way of distinguishing between a quick touch_down followed immediately by a touch_up, it is easier to show the desired functionality by letting an ImageButton press be activated with a double_tap:
class ImageButton(ButtonBehavior, Image):
def __init__(self, **kwargs):
super(ImageButton, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and touch.is_double_tap:
self.on_press(touch)
return True
return False
def on_press(self,touch):
#whatever else you want to happen
return True
I'm new to kivy, so apologies if I've missed something really basic here but I am struggling to understand both the behavior I'm getting as well as my inability to get the outcome I'm after.
The goal
The kivy app should be listening for TCP events from an external server, with certain triggers causing various stuffs in the kivy app. In this case, I want the external server to be able to open a popup in the app.
Illustrative Code
I have adopted the following (working) demo code from another SO answer that I can no longer seem to find, so apologies for the plagiarism but it is not intentional.
server.py
import socket
serversocket = socket.socket()
host = 'localhost'
port = 54545
serversocket.bind(('', port))
serversocket.listen(1)
clientsocket,addr = serversocket.accept()
print("got a connection from %s" % str(addr))
while True:
msg = input("> ")
clientsocket.send(msg.encode('utf-8'))
client.py
import socket
class MySocket:
def __init__(self,host="localhost",port=54545):
self.sock = socket.socket()
self.sock.connect((host, port))
def get_data(self):
return self.sock.recv(1024)
main.py
import kivy
from kivy.app import App
from kivy.event import EventDispatcher
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from threading import Thread
from client import *
kivy.require('1.9.1')
class MyEventDispatcher(EventDispatcher):
def __init__(self, **kwargs):
self.register_event_type('on_test')
super(MyEventDispatcher, self).__init__(**kwargs)
def do_something(self, value):
self.dispatch('on_test', value)
def on_test(self, *args):
print('I am dispatched', args)
CustomPopup().open()
class CustomPopup(Popup):
pass
class MainScreen(Label):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.sock = MySocket()
Thread(target=self.get_data).start()
def get_data(self):
while True:
self.text = self.sock.get_data().decode('utf-8').strip()
if self.text == 'click':
MyEventDispatcher().do_something(self.text)
class MyApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
MyApp().run()
my.kv
<MainScreen>:
Button:
text: 'I do nothing'
<CustomPopup>:
title: 'Popup window'
size_hint: .5, .5
auto_dismiss: False
GridLayout:
cols: 1
Label:
size_hint: .9, .9
halign: 'center'
valign: 'middle'
text: 'message text goes here'
text_size: self.width, None
Button:
text: 'A: Close'
on_release: root.dismiss()
Button:
text: 'B: Close'
on_release: root.dismiss()
This all works in that I can send the trigger word click from the server and the popup will open, it has multiple placeholder buttons that work. Hooray.
The problem, however, is that I seem to require the dummy button on the main app screen, which I do not want. If I simply eliminate the <MainScreen> bit from the .kv file, sending the click keyword leads to:
I am dispatched ('click',)
Segmentation fault (core dumped)
but I can send any other string and it will display on screen as expected.
So, the basic questions are:
How do I get this to work as desired and
Why does placing a non-functional button on the screen make the popup work?
Thanks!
I have this code, and I can’t remove the widget when I use the name of this widget: MainApp(). m.remove_widget (Butt()).
I understand that I am accessing this widget incorrectly, but I do not understand how to do it correctly.
Tell me how to remove a widget using its name?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.colorpicker import ColorPicker
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
class Butt(Button):
def add_wid1(self):
MainApp.m.add_widget(Box())
MainApp.m.add_widget(Butt1())
class Butt1(Button):
def on_press(self):
print('111')
MainApp().m.remove_widget(MainApp().m.children[1]) #this code works
MainApp().m.remove_widget(Butt()) #this code not working
class Box (ColorPicker):
size_hint=.50, .25
class Pict (Widget):
pass
class MainApp (App):
m = FloatLayout()
def build (self):
pic = Pict()
MainApp.m.add_widget(pic)
MainApp.m.add_widget(Butt())
return MainApp.m
if __name__ == '__main__':
MainApp().run()
You must use the instance of the Butt that you want to remove. An easy way to do it is to save a reference to that Butt in your build method:
self.butt = Butt()
MainApp.m.add_widget(self.butt)
and then remove it with:
app = App.get_running_app()
MainApp.m.remove_widget(app.butt)
I've spent the day working with the code from How to access id/widget of different class from a kivy file (.kv)? I've paired it down to the simplest code I could because what I want to do is use Kivy's Clock function to change the text for me instead of clicking the button.
As best as I can understand the code that changes the text should go on Line 38 of the program but all the code I tried stopped executing because it could not access the text to change it. The clock function is working in the code provided.
I've left the button press active but it's the Clock code that I want to change the text. I was wondering if someone knows the solution.
Thanks in advance.
....brad....
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty
Builder.load_string("""
<SingleScreen>:
id: single_screen
orientation: 'vertical'
Button:
text: "You Can Change Me By Button Press Here But I Really Want To Be Changed By Kivy's Clock Function"
on_release: root.rooted()
Change_Label:
id: gb
<Change_Label>:
_python_access: ChangeLabel
Label:
id: ChangeLabel
text: "I'm Gonna Change."
""")
class SingleScreen(BoxLayout):
def rooted(self):
self.ids.gb._python_access.text = "I Changed It By Clicking!. But That's Not What I Wanted To Program!"
class Change_Label(BoxLayout):
_python_access = ObjectProperty(None)
class OnlyScreen(App):
def build(self):
Clock.schedule_interval(self.Callback_Clock, 3)
return SingleScreen()
def Callback_Clock(self, dt):
print "Hello, world! I'm the clock working...."
# What code goes here that will change the text via Kivy's clock instead of using the button?
if __name__ == '__main__':
OnlyScreen().run()
Please do the following and refer to my example for details:
Snippet
class OnlyScreenApp(App):
def build(self):
Clock.schedule_interval(self.Callback_Clock, 3)
return SingleScreen()
def Callback_Clock(self, dt):
self.root.ids.gb._python_access.text = "I Changed It By Clock! dt=" + str(dt)
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty
class SingleScreen(BoxLayout):
pass
class Change_Label(BoxLayout):
_python_access = ObjectProperty(None)
class TestApp(App):
def build(self):
Clock.schedule_interval(self.Callback_Clock, 3)
return SingleScreen()
def Callback_Clock(self, dt):
self.root.ids.gb._python_access.text = "I Changed It By Clock! dt=" + str(dt)
if __name__ == '__main__':
TestApp().run()
test.kv
#:kivy 1.10.0
<SingleScreen>:
id: single_screen
orientation: 'vertical'
Change_Label:
id: gb
<Change_Label>:
_python_access: ChangeLabel
Label:
id: ChangeLabel
text: "I'm Gonna Change."
Output
Based on ikolim's answer above I wanted to post the solution as one complete python program that relates to the format from which the question was posted. Really appreciate the solution. The complete working (one file) code would be as follows;
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty
Builder.load_string("""
<SingleScreen>:
id: single_screen
orientation: 'vertical'
Change_Label:
id: gb
<Change_Label>:
_python_access: ChangeLabel
Label:
id: ChangeLabel
text: "I'm Gonna Change."
""")
class SingleScreen(BoxLayout):
pass
class Change_Label(BoxLayout):
_python_access = ObjectProperty(None)
class OneScreen(App):
def build(self):
Clock.schedule_interval(self.Callback_Clock, 3)
return SingleScreen()
def Callback_Clock(self, dt):
self.root.ids.gb._python_access.text = "I Changed It By Clock! dt=" + str(dt)
if __name__ == '__main__':
OneScreen().run()
I am attempting to draw a line with the mouse by dragging from one point to another point of the window. I also want to represent the line while I am dragging. Like drawing a line in an old MS PaintBrush.
My problem is that I have only been able to achieve this by constantly removing the old Line and adding a new Vertex Instruction to the canvas. However, I cannot update existing instructions. Not even adding and removing the same instruction. It has to be a new instance of Line. You can see the result that I want by running the following code. If you try to run it with the commented lines it doesn't work any more.
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line
class MyCanvas(RelativeLayout):
def on_touch_down(self, touch):
with self.canvas:
self.line = Line(points=[touch.x,touch.y,touch.x+1,touch.y+1])
self.bind(on_touch_move=self.update_line, on_touch_up=self.end_line)
return True
def update_line(self, instance, touch):
self.line.points[2] = touch.x
self.line.points[3] = touch.y
self.canvas.remove(self.line)
# self.canvas.add(self.line) # - this doesn't work
# self.canvas.ask_update() # - not even using this
with self.canvas:
self.line = Line(points=self.line.points) # this works
def end_line(self, instance, touch):
self.unbind(on_touch_move=self.update_line)
self.unbind(on_touch_up=self.end_line)
self.line.points[2] = touch.x
self.line.points[3] = touch.y
self.canvas.remove(self.line)
# self.canvas.add(self.line) # - this doesn't work
# self.canvas.ask_update() #- not even using this
self.canvas.add(Line(points=self.line.points)) # this way works
class ExampleApp(App):
def build(self):
return MyCanvas()
ExampleApp().run()
I also tried using Kivy properties as suggested in this other question with the Color instruction. It didn't work and there is another question related to it.
I am struggling with the same problem. I started from the 6_button.py example from the kivy/guide/firstwidget directory
I found something that works (using pop twice to remove the last x,y pair from points) But I think it is very awkward, see my code below. I hope someone can tel us how to 'update' properly.
based on 6_button.py
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
color = (random(), 1, 1)
with self.canvas:
Color(*color, mode='hsv')
d = 10.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y, touch.x+30, touch.y))
#print(dir(touch.ud['line']))
def on_touch_move(self, touch):
#touch.ud['line'].points += [touch.x, touch.y]
touch.ud['line'].points.pop() #
touch.ud['line'].points.pop() # works but is awkward
touch.ud['line'].points += [touch.x, touch.y] #
#touch.ud['line'].points[2:4] = [touch.x, touch.y]
#self.canvas.ask_update() # no error but didnt work
#touch.ud['line'].ask_update() # didnt work
#print(touch.ud['line'].points)
#touch.ud['line'].needs_redraw() # error 'bool not callable'
#touch.ud['line'].needs_redraw = True # error 'not writable'
#touch.ud['line'].needs_redraw #no error but doesnt work
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
clearbtn = Button(text='Clear')
parent.add_widget(painter)
parent.add_widget(clearbtn)
def clear_canvas(obj):
painter.canvas.clear()
clearbtn.bind(on_release=clear_canvas)
return parent
if __name__ == '__main__':
MyPaintApp().run()