Kivy sending text between widgets - text

I am trying to reset a text input box with new text when I click the mouse in another widget and I am not quite sure how to make this happen. I have tried altering the code in many ways but clearly do not not what I am doing. Here is the code I am using: Thanks to inclement and qua-non for getting me this far
Builder.load_string('''
<MouseWidget>:
image: image
label: label
orientation: 'vertical'
Image:
id: image
source: root.source
Label:
id: label
size_hint_y: None
height: 50
text: 'Hello World'
''')
class Test(TextInput):
def on_double_tap(self):
# make sure it performs it's original function
super(Test, self).on_double_tap()
def on_word_selection(*l):
selected_word = self.selection_text
print selected_word
# let the word be selected wait for
# next frame and get the selected word
Clock.schedule_once(on_word_selection)
class MouseWidget(BoxLayout):
image = ObjectProperty()
label = ObjectProperty()
source = StringProperty()
It is here in this def that I want to update the textinput box that is created in AccordianAPP and sent through TEST with new text every time the mouse is clicked on the image
def on_touch_down(self, touch):
if self.image.collide_point(*touch.pos):
self.label.text = str(touch.pos)
def on_touch_up(self, touch):
self.label.text = 'Hello World'
class AccordianApp(App):
def build(self):
root = Accordion(orientation='horizontal')
item= AccordionItem(title='Page One')
src = "image.png"
image = MouseWidget(source=src, size_hint = (1.0, 1.0))
This is the textinput that I want to reset the text="" to something else when I click on the image
textinput = Test(text='Testing', size_hint = (0.5, 1.0))
# add image to AccordionItem
item.add_widget(image)
item.add_widget(textinput)
root.add_widget(item)
return root
if __name__ == '__main__':
AccordianApp().run()
Thanks for the help

What you really need here is a reference to the textinput that the mousewidget can access. As long as the mousewidget can access some variable that's assigned to the textinput, it can do anything it likes to that textinput.
Here's a simple modification that simply adds an attribute 'self.textinput' to the mousewidget, that is then set to point at the textinput. Then in the on_touch_up method it's easy to add text to the textinput...in this case appending the new coordinates each time.
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.accordion import *
from kivy.properties import *
from kivy.app import App
Builder.load_string('''
<MouseWidget>:
image: image
label: label
orientation: 'vertical'
Image:
id: image
source: root.source
Label:
id: label
size_hint_y: None
height: 50
text: 'Hello World'
''')
class Test(TextInput):
def on_double_tap(self):
# make sure it performs it's original function
super(Test, self).on_double_tap()
def on_word_selection(*l):
selected_word = self.selection_text
print selected_word
# let the word be selected wait for
# next frame and get the selected word
Clock.schedule_once(on_word_selection)
class MouseWidget(BoxLayout):
image = ObjectProperty()
label = ObjectProperty()
source = StringProperty()
textinput = ObjectProperty()
def on_touch_down(self, touch):
if self.image.collide_point(*touch.pos):
self.label.text = str(touch.pos)
def on_touch_up(self, touch):
self.label.text = 'Hello World'
if self.textinput is not None:
self.textinput.text += ' ... and {}'.format(touch.pos)
class AccordianApp(App):
def build(self):
root = Accordion(orientation='horizontal')
item= AccordionItem(title='Page One')
src = "image.png"
image = MouseWidget(source=src, size_hint = (1.0, 1.0))
textinput = Test(text='Testing', size_hint = (0.5, 1.0))
image.textinput = textinput
# add image to AccordionItem
item.add_widget(image)
item.add_widget(textinput)
root.add_widget(item)
return root
if __name__ == '__main__':
AccordianApp().run()
This isn't the only way to do it - there are lots of ways you could make that reference available, and different ones might be useful in different situations. For instance, you could make another widget containing both your mousewidget and the textinput, and write all the bindings in kv language.
Another trick that can be useful for unrelated widgets (where it isn't easy to pass a reference between them) is to store the reference in your App class. You can do something like
class AccordianApp(App):
some_thing = ObjectProperty()
...
This is useful because you can always access the app with App.get_running_app(), so even if you have disconnected widgets and can't see how they might communicate, you could store the textinput at app level so anything can easily get at it.
I hope that's clear...I only mean to say, there are several possibilities that might suit different situations. For your particular problem, you can just store a reference to the textinput in the mousewidget, or some similar simple solution.

Related

Kivy: How to switch buttons by dragging over them

I have created an array of buttons in Kivy, and I need to switch their states (or change color/background) by touch-dragging over them. I can't figure out how to do it. The task is to create words by dragging over letter buttons. Should I use an invisible Scatter widget, or is there something dedicated for this purpose. Thank you.
Scatter would be a way, but it’s not necessary, you can implement on_touch_down, on_touch_move and on_touch_up methods of your widget class to handle being dragged and dropped other things, you’ll need to test collision of the dragged widget with possible landing zones (widgets) during drag and drop, to decide how to react to them, i have this example https://gist.github.com/tshirtman/7282822
from kivy.app import App
from kivy.garden.magnet import Magnet
from kivy.uix.image import Image
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.clock import Clock
from os import listdir
IMAGEDIR = '/usr/share/icons/hicolor/32x32/apps/'
IMAGES = filter(
lambda x: x.endswith('.png'),
listdir(IMAGEDIR))
kv = '''
FloatLayout:
BoxLayout:
GridLayout:
id: grid_layout
cols: int(self.width / 32)
FloatLayout:
id: float_layout
'''
class DraggableImage(Magnet):
img = ObjectProperty(None, allownone=True)
app = ObjectProperty(None)
def on_img(self, *args):
self.clear_widgets()
if self.img:
Clock.schedule_once(lambda *x: self.add_widget(self.img), 0)
def on_touch_down(self, touch, *args):
if self.collide_point(*touch.pos):
touch.grab(self)
self.remove_widget(self.img)
self.app.root.add_widget(self.img)
self.center = touch.pos
self.img.center = touch.pos
return True
return super(DraggableImage, self).on_touch_down(touch, *args)
def on_touch_move(self, touch, *args):
grid_layout = self.app.root.ids.grid_layout
float_layout = self.app.root.ids.float_layout
if touch.grab_current == self:
self.img.center = touch.pos
if grid_layout.collide_point(*touch.pos):
grid_layout.remove_widget(self)
float_layout.remove_widget(self)
for i, c in enumerate(grid_layout.children):
if c.collide_point(*touch.pos):
grid_layout.add_widget(self, i - 1)
break
else:
grid_layout.add_widget(self)
else:
if self.parent == grid_layout:
grid_layout.remove_widget(self)
float_layout.add_widget(self)
self.center = touch.pos
return super(DraggableImage, self).on_touch_move(touch, *args)
def on_touch_up(self, touch, *args):
if touch.grab_current == self:
self.app.root.remove_widget(self.img)
self.add_widget(self.img)
touch.ungrab(self)
return True
return super(DraggableImage, self).on_touch_up(touch, *args)
class DnDMagnet(App):
def build(self):
self.root = Builder.load_string(kv)
for i in IMAGES:
image = Image(source=IMAGEDIR + i, size=(32, 32),
size_hint=(None, None))
draggable = DraggableImage(img=image, app=self,
size_hint=(None, None),
size=(32, 32))
self.root.ids.grid_layout.add_widget(draggable)
return self.root
if __name__ == '__main__':
DnDMagnet().run()
(see comments on the gist for possible improvements though, i didn’t try them but they seem to make sense)
Which depends on the magnet widget (https://github.com/kivy-garden/garden.magnet) for nice effects, but this is not strictly necessary for you either, the important part is understanding the role of the on_touch_* methods and grabbing (grabbing makes sure a widget that started caring about a touch, gets all the updates for this touch, whatever other widgets think about it).
there is also a DragBehavior https://kivy.org/doc/stable/api-kivy.uix.behaviors.drag.html but i don’t see events in the documentation allowing to check collision on drop, that i think you need, but possibly subclassing this widget and implementing your changes in your subclass would be easier, as my example predates it, i didn’t try.

Failed to add widgets dynamically in output screen in .kv file using python

I was trying to add the result to a new screen whose content should be dynamic. I tried a few of the trivial ways given in StackOverflow itself but failed to show any content. my code goes as such.
I am entering multiple data, separated by "," without spaces, using text input, and then splitting it which will be stored as a list. The data will be parsed and an equivalent number of labels should be shown on the output screen which I tried but was not successful in execution (line 51-56 in anbs.py).
For example, the input is "I,am,a,good,boy".
The result should be as it will be in the console, i.e. text of each Label should contain an individual item, but nothing goes to the output screen... just a big button that is used to traverse between the screens.
This is my anbs.py file.
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.factory import Factory
Window.size = (600, 600)
class HelpWindow(Screen):
"""
This is a help window. It contains the functionality of all the given boxes
on the main window.
"""
def main_window(self):
sm.current = "main"
class MainWindow(Screen):
"""
This is the main window that contains the main form.
This connects the frontend of the app to the backend
"""
target = ObjectProperty(None)
def v_popup(self):
version_popup()
def help(self):
sm.current = "help"
def output_window(self):
sm.current = "output"
def get_results(self):
#OutputWindow.main(options = options)
out_window = OutputWindow()
out_window.main(self.target.text)
sm.current = "output"
class OutputWindow(Screen):
"""
This is the output window. All the generated results will be seen here.
"""
res = ObjectProperty(None)
res_out = ObjectProperty(None)
def main(self, options):
options = list(options.split(","))
for item in options:
print(item)
self.res_out.add_widget(Label(text=item))
def main_window(self):
sm.current = "main"
class WindowManager(ScreenManager):
pass
def version_popup():
"""
Version Popup Window.
"""
version = "v1.0"
version_text = "this is "+version+" for this app"
vpop = Popup(title="Version",
content=Label(text=version_text),
size_hint=(None, None), size=(400, 400))
vpop.open()
### main builder and WindowManager object
kv = Builder.load_file("start.kv")
sm = WindowManager()
### Adding screens to widget
screens = [MainWindow(name="main"), HelpWindow(name="help"), OutputWindow(name="output")]
for screen in screens:
sm.add_widget(screen)
sm.current = "main"
### main working
class AnbsApp(App):
def build(self):
return sm
if __name__ == '__main__':
AnbsApp().run()
and my start.kv file looks like this
<HelpWindow>
name:"help"
Button:
id:ms
text:"Main Screen"
on_release:
root.manager.transition.direction = "left"
root.main_window()
<MainWindow>
name:"main"
target : target
GridLayout:
cols:1
GridLayout:
cols:3
row_force_default: True
row_default_height: 50
Button:
id:hp
text:"Help"
on_release:
root.manager.transition.direction = "right"
root.help()
Button:
id:version
text: "Version"
on_release:
root.v_popup()
GridLayout:
cols:2
row_force_default: True
row_default_height: 30
Label:
text:"Target *"
TextInput:
id:target
multiline:False
GridLayout:
cols:3
row_force_default: True
row_default_height: 50
Label:
text:""
Button:
text:"Submit"
on_release:
root.get_results()
Label:
text:""
<OutputWindow>
name:"output"
res_out:res_out
GridLayout:
id:res_out
cols : 1
Button:
id:ms
text:"Main Screen"
on_release:
root.manager.transition.direction = "right"
root.main_window()
I can't really say whats the major point I am missing in it.
Your code:
def get_results(self):
#OutputWindow.main(options = options)
out_window = OutputWindow()
out_window.main(self.target.text)
sm.current = "output"
is creating a new instance of OutputWindow in the line:
out_window = OutputWindow()
and then calling the main() method of that new instance. However, that new instance is not in your GUI, so no effect is observed. To correct that, change the code to use the instance of OutputWindow that is in your GUI:
def get_results(self):
out_window = self.manager.get_screen('output') # get OutputWindow instance
out_window.main(self.target.text)
sm.current = "output"
You will also need to adjust the size/position of the Button in the OutputWindowso that it does not completely cover the GridLayout.

Kivy - TextInput text show in Label text when Button is clicked (Not KV Lang)

How to make text that I enter in the TextInput field (textA) will show in the Label (labelA) by using a button?
Every time there is new inputs, when the button is clicked, the labels will show the latest inputs.
Problem here when I use:
self.textA = TextInput(text='ss')
When I type new text and I click the button, the label always show 'ss'. Its not updating the new input.
Hope someone can show the method - just in python, not the Kivy Languange
Thanks
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
class One(BoxLayout):
def __init__(self, **kwargs):
super(One, self).__init__(**kwargs)
self.orientation = 'vertical'
# text input
self.textA = TextInput(font_size='60sp')
self.add_widget(self.textA)
self.add_widget(Two())
class Two(BoxLayout):
def __init__(self, **kwargs):
super(Two, self).__init__(**kwargs)
self.orientation = 'vertical'
# Button
self.btnA = Button(text='Show')
self.btnA.bind(on_press=show)
self.add_widget(self.btnA)
# Output
self.labelA = Label(text='Result Here From Input')
self.add_widget(self.labelA)
def show(value):
Two().labelA.text = str(One().textA.text)
x = Two().labelA.text
y = One().textA.text
print(x)
print(y)
class TestApp(App):
def build(self):
return One()
if __name__ == '__main__':
TestApp().run()
Whenever you use One() or Two() you are creating new instances of One or Two, and these new instances are unrelated to the instances that are in your GUI. If you want to access the instances in your GUI, you must arrange for that. For example, in the One class, you can save reference to the Two instance:
class One(BoxLayout):
def __init__(self, **kwargs):
super(One, self).__init__(**kwargs)
self.orientation = 'vertical'
# text input
self.textA = TextInput(font_size='60sp')
self.add_widget(self.textA)
self.two = Two() # save a reference to the Two instance
self.add_widget(self.two)
Since the instance of One is the root of the App (returned by the build() method), you can access it from the App. Then your show() method can become:
def show(value):
one = App.get_running_app().root
two = one.two
two.labelA.text = one.textA.text
x = two.labelA.text
y = one.textA.text
print(x)
print(y)

how to reference kivy widget ids dynamically created using kv language through a for loop in main python file?

I have dynamically created textinput widgets in a .kv file using kv language. However i would like to set the text in main python file using a for loop by referencing their ids.
I have written the following code:
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
class setText(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
texts=['txtip1','txtip2']
IDS=self.ids.keys()
for i in range(IDS):
self.ids[IDS[i]].text=texts[i]
class DocApp(App):
def build(self):
return Builder.load_file("docapp\Doc.kv")
return setText()
if __name__=="__main__":
DocApp().run()
Doc.kv
# File: docapp.py
#: import TextInput kivy.uix.textinput.TextInput
GridLayout:
cols:1
on_kv_post:
[self.add_widget(TextInput(id=str(i))) for i in range(2)]
I have searched through other questions but couldn't find suitable solution. Please help
The problem in your code, is that id only works in the kv syntax.
So you need to reference your widgets some other way.
In case you need to do it that way, dynamically adding in kv and accessing in a loop in python, you could try something like the example below.
The text will change after 5 seconds in this case. If you know that the only children of this gridlayout will be the widgets you want to access, you might as well just iterate the children.
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
KV = """
#: import TextInput kivy.uix.textinput.TextInput
GridLayout:
cols:1
on_kv_post:
for i in range(2): app.dynamic_widgets.append(TextInput())
for wid in app.dynamic_widgets: self.add_widget(wid)
"""
class DocApp(App):
dynamic_widgets = []
def build(self):
Clock.schedule_once(self.set_text, 5)
return Builder.load_string(KV)
def set_text(self, dt):
texts=['txtip1','txtip2']
for text, wid in zip(texts, self.dynamic_widgets):
wid.text = text
if __name__=="__main__":
DocApp().run()
Now if you remove one of the widgets, you probably want to remove it from the list also.
You can create the TextInput widgets dynamically in kv by creating the kv dynamically. Here is a modification of your code that does that:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
class DocApp(App):
def build(self):
# schedule the call to add the texts
Clock.schedule_once(self.setText)
# return the root widget from the kvString
return layoutRoot
def setText(self, dt):
texts=['txtip1','txtip2']
IDS=self.root.ids.keys()
i = 0
for id in IDS:
self.root.ids[id].text=texts[i]
i += 1
if __name__=="__main__":
# Create the kv string here
kvString = '''
GridLayout:
cols:1
'''
for i in range(2):
kvString += ' TextInput:\n' + ' id: ' + str(i) + '\n'
# Load the kv string
layoutRoot = Builder.load_string(kvString)
# and finally, run the App
DocApp().run()
I have eliminated the setText class (assuming it was only a holder for the setText method), and placed that method in the App. The Clock.schedule_once() schedules the call to setText to happen after the App has been created.

How to remove the Added widget within that widget?

how can I remove the added widget from within that widget. am i making any sense? hopefully yes, hehehe...
anyway the simple code consist of buttons and label only.
My aim is if I click the add button it will show the widget which is a label & button. If i want to remove the widget i'll just click the close button in that widget.
but its not closing, its been two days already and i cant find whats the problem and its not giving me any error.
Thanks guys.
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
kv_file = Builder.load_string('''
<Screen1>:
BoxLayout:
Button:
on_release: root.add_button(True)
text: 'ADD'
size_hint: .2,.2
<Layout1>:
BoxLayout:
pos: self.x,300
size_hint: .5,.3
Label:
text: 'THIS IS A ADDED WIDGET'
Button:
text: 'Close'
on_release: root.closeBTN()
''')
class Layout1(FloatLayout):
def closeBTN(self):
AddWidget_Layout1().addEmps(True)
class AddWidget_Layout1(Widget):
def __init__(self, **kwargs):
super(AddWidget_Layout1,self).__init__(**kwargs)
self.count = 0
self.layout1 = Layout1()
def addEmps(self,xadd):
if xadd == 1:
self.add_widget(self.layout1)
elif xadd == True:
self.remove_widget(self.layout1)
class Screen1(Screen,AddWidget_Layout1):
def add_button(self,*args):
self.count += 1
print
if self.count == 1:
self.addEmps(1)
class projectApps(App):
def build(self):
return SM
SM = ScreenManager()
SM.add_widget(Screen1())
if __name__ == "__main__":
projectApps().run()
You have a few bugs in your code, first, you are testing xadd for 1 and True which is kinda the same thing:
def addEmps(self,xadd):
if xadd:
self.add_widget(self.layout1)
else: #was elif xadd == True which cannot happen...
self.remove_widget(self.layout1)
I would also change:
def closeBTN(self):
#AddWidget_Layout1().addEmps(True) #BAD! will create a new instance
self.parent.addEmps(False) # this looks better
Finally, I'm not sure why are you doing this since you are already using ScreenManager, so you can just switch to a different screen whenever you need instead of doing this weird widget removal thing ...
I hope this clears most of the issues

Resources