How to make editable label work in kivy - python

Following code opens window with label on top and label turns to textinput once you click on it.
But, once you start typing and insert first key (any key), text gets shortened, and you lose part of the text suddenly. For example: you click on label > textinput appears > you type '1' > text becomes 'Press here and then try 1'.
How to change below code to stop text disappearing?
import kivy
kivy.require('1.10.1')
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.base import runTouchApp
from kivy.properties import BooleanProperty, ObjectProperty
#https://github.com/kivy/kivy/wiki/Editable-Label
class EditableLabel(Label):
edit = BooleanProperty(False)
textinput = ObjectProperty(None, allownone=True)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and not self.edit:
self.edit = True
return super(EditableLabel, self).on_touch_down(touch)
def on_edit(self, instance, value):
if not value:
if self.textinput:
self.remove_widget(self.textinput)
return
self.textinput = t = TextInput(
text=self.text, size_hint=(None, None),
font_size=self.font_size, font_name=self.font_name,
pos=self.pos, size=self.size, multiline=False)
self.bind(pos=t.setter('pos'), size=t.setter('size'))
self.add_widget(self.textinput)
t.bind(on_text_validate=self.on_text_validate, focus=self.on_text_focus)
def on_text_validate(self, instance):
self.text = instance.text
self.edit = False
def on_text_focus(self, instance, focus):
if focus is False:
self.text = instance.text
self.edit = False
if __name__ == '__main__':
root = FloatLayout()
lbl = 'Press here and then try to edit (type a character), but text gets shortened suddenly.'
label = EditableLabel(text=lbl, size_hint_y=None, height=50, pos_hint={'top': 1})
root.add_widget(label)
runTouchApp(root)

According to the docs:
The selection is automatically updated when the cursor position changes. You can get the currently selected text from the TextInput.selection_text property.
and in your case when you click on the Label and appear in TextInput the cursor changes position so a text is selected. And when a text is selected and you write something it is replaced, that's why the text disappears.
The solution is to clean the selection:
from kivy.clock import Clock
class EditableLabel(Label):
[...]
def on_edit(self, instance, value):
if not value:
if self.textinput:
self.remove_widget(self.textinput)
return
self.textinput = t = TextInput(
text=self.text, size_hint=(None, None),
font_size=self.font_size, font_name=self.font_name,
pos=self.pos, size=self.size, multiline=False)
self.bind(pos=t.setter('pos'), size=t.setter('size'))
self.add_widget(self.textinput)
t.bind(on_text_validate=self.on_text_validate, focus=self.on_text_focus)
Clock.schedule_once(lambda dt: self.textinput.cancel_selection())

Related

Popup widget doesn't reopen in Kivy

When pressing a key the pop-up window opens by pressing the button, it closes, but when the key is pressed again, calling the pop-up window gives an error
WidgetException('Cannot add %r, it already has a parent %r'
import json
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.popup import Popup
def read_json(file):
FileJson = open(file)
ObjJsom = json.load(FileJson)
return ObjJsom
data = read_json('Task.json')
counter = 0
task_Headline = data['Tasks'][counter]['Headline']
label = Label(text="Label test for StackoverFlow")
ConBox = BoxLayout(orientation="vertical")
clButt = Button(text="Close", size_hint=(1, 0.1))
ConBox.add_widget(label)
ConBox.add_widget(clButt)
def btn(instance):
show_popup(ConBox)
def show_popup(conten):
show = conten
popupWindow = Popup(title="Popup Window", content=show)
clButt.bind(on_press=popupWindow.dismiss)
popupWindow.open()
class Test(App):
def build(self):
butt = Button(text='Press')
butt.bind(on_press=btn)
return butt
if __name__ == '__main__':
Test().run()
When you create the Popup with:
popupWindow = Popup(title="Popup Window", content=show)
it adds the show to the Popup instance. That set the parent property of show. When the Popup is dismissed an a new Popup is created with the line above, it fails because the show instance still thinks its parent is the old Popup (even though it has been dismissed). And any widget can only have one parent. The fix is to remove the show instance from the old Popup like this:
def show_popup(conten):
# make sure the content has no parent
if conten.parent is not None:
conten.parent.remove_widget(conten)
show = conten
popupWindow = Popup(title="Popup Window", content=show)
clButt.bind(on_press=popupWindow.dismiss)
popupWindow.open()

Kivy, StackLayout, object order

I have a screen with a StackLayout. The first row of the stack includes a textinput and a "+" button which is meant to add another identical row below the actual one in a loop (i.e. another textinput with another "add" button). Then there is a "Save" button, which is supposed to be always at the end of the stack.
The dictionary is supposed to later grab the input from the text field, when pressing the save button, but this should not be relevant to my problem.
There are two problems with my code:
First, when I press the "+" button in a row, the button that gets highlighted is not this button itself, but the one in the row below (e.g. if there are three rows and I press the button in the second row, the button in the third row is highlighted)
Second and most importantly, starting from the second row onwards, each press of the "+" button adds a new row above the actual one, rather than below it.
I am aware that kivy assigns reverse order to the widgets (i.e. the one added last will be drawn first) and that is why I am manually assigning indexes to the new rows. However, I cannot achieve the desired behavior.
Here is a minimal working version:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
self.grid = StackLayout()
self.grid.pos_hint = {"x":0.05,"top":0.8}
self.grid.size_hint = (0.9,None)
self.add_widget(self.grid)
self.i = 1
self.n = 1
self.inputs = {}
self.ing1 = TextInput(size_hint=(0.9,'0.3sp'))
self.grid.add_widget(self.ing1)
self.inputs['0'] = 'ing1'
self.addIng = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addIng.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addIng)
self.doneButton = Button(text="Save")
self.grid.add_widget(self.doneButton, index=0)
def addIngredient (self, instance):
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.inputs[self.i] = 'ing{}'.format(self.i+1)
self.grid.add_widget(self.ing, index=self.n+1)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext, index=self.n+2)
self.i += 1
self.n += 2
print(self.inputs)
WMan = ScreenManager()
WMan.add_widget(AddWindow(name='add'))
class RecipApp(App):
def build(self):
return WMan
if __name__ == "__main__":
RecipApp().run()
What am I missing? Here is a screenshot for better clarity:
Screenshot
Here is a brute force method to do what you want by rebuilding the StackLayout each time a + `Button is pressed:
def addIngredient(self, instance):
tmp_children_list = self.grid.children[:]
self.grid.clear_widgets()
for index in range(len(tmp_children_list)-1, -1, -1):
child = tmp_children_list[index]
self.grid.add_widget(child)
if child == instance:
# this is the pressed Button, so add new row after it
self.n += 1
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.ing.text = str(self.n) # add text just for identification
self.grid.add_widget(self.ing)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext)
Here is a quick and dirty approach , where your problems should be fixed due to a remove and add of the save button. Between those to actions you add a new row before you finally add the save button again. Hope it helps you to adjust your code. The key part is the custom on_press method of the AddButton.
from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.window import Window
class AddButton(Button):
def __init__(self, **kwargs):
super(AddButton, self).__init__(**kwargs)
def on_press(self):
self.parent.remove_widget(self.parent.save_btn)
self.parent.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - self.parent.padding[1], Window.height * 0.1)))
self.parent.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
self.parent.add_widget(self.parent.save_btn)
class MyApp(App):
def build(self):
Window.size = (400, 600)
layout = StackLayout(orientation="lr-tb", padding=(20, 40))
layout.save_btn = Button(text="Save", size_hint=(None, None),
size=(Window.width - layout.padding[1], Window.height * 0.1))
layout.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - layout.padding[1], Window.height * 0.1)))
layout.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
layout.add_widget(layout.save_btn)
return layout
MyApp().run()

How to put two buttons in a kivy window?

I know this question may have been asked already, but I am a beginner to Kivy, so I would like for someone to explain how to put two buttons on the same screen.
The problem is I try to return the button variable, and it works. However, when I try to return two at the same time, it will give me an error.
here is my code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from functools import partial
class App(App):
def com1(self, instance, *args):
label1 = Label(text = "Hi")
return label1
def com2(self, instance, *args):
label = Label(text= "Bye")
def build(self):
button1 = Button(text = "Hi", size_hint = (0.25, 0.18), pos = (350, 100))
button1.bind(on_press=partial(self.com1, button1))
button2 = Button(text = "Bye", size_hint = (0.25, 0.18), pos = (350, 200))
button2.bind(on_press=partial(self.com2, button2))
return button1, button2
App().run()
The build method must return a single widget, so depending on how you want the buttons to be arranged you have several options such as BoxLayout, RelativeLayout, FloatLayout, etc. In this case for simplicity I will use a BoxLayout:
# ...
from kivy.uix.boxlayout import BoxLayout
# ...
class App(App):
# ...
def build(self):
button1 = Button(text="Hi", size_hint=(0.25, 0.18), pos=(350, 100))
button1.bind(on_press=partial(self.com1, button1))
button2 = Button(text="Bye", size_hint=(0.25, 0.18), pos=(350, 200))
button2.bind(on_press=partial(self.com2, button2))
boxlayout = BoxLayout()
boxlayout.add_widget(button1)
boxlayout.add_widget(button2)
return boxlayout
# ...

How can i update label text by pressing button

I have a question that i can't seem to find an anwser.
I have this label:
l = Label(text='Some Text', font_size=100)
I have also binded text input to 'l' that looks like this:
t = TextInput(font_size=80, size_hint_y=None, height=200, text='Time', halign='right')
t.bind(text=l.setter('text'))
So when i type something to my text input box, it instantly updates the label and shows result on screen.
But i want to update that text only when user presses 'Add' button.
I am new to kivy and i am still experiment with it.
Any help would be great.
Thanks!
#edit here is my code:
https://gist.githubusercontent.com/Gacut/d765231c2696831af8c8a3315fdabbfd/raw/e1bdcdd8aca588b1c9268e76f7a47fa7576d54cb/gistfile1.txt
and what i am trying to do, is this kind of app, but for android:
https://wumpa.app/
The
t.bind(text=l.setter('text'))
will do exactly what you describe, so that is not what you want. Instead, use the Button on_release property to call a method that does what you want. The simplest way to do that is to save references to the Widgets involved (t and lista), and use those references in the new method. Like this:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
class WumpaTime(App):
def build(self):
layout = BoxLayout(orientation='vertical')
title = Label(text='Wumpa Time Countdown', size_hint_y=None, height=100, )
self.t = TextInput(font_size=80, size_hint_y=None, height=200, halign='right')
self.lista = Label(font_size=100)
box = BoxLayout()
box2 = BoxLayout()
bremove = Button(text="Remove", size_hint=(None, None), size=(100, 100))
badd = Button(text="Add", size_hint=(None, None), size=(100, 200), on_release=self.update_label)
#t.bind(text=lista.setter('text'))
box.add_widget(self.t)
box.add_widget(badd)
layout.add_widget(title)
layout.add_widget(self.lista)
layout.add_widget(box)
return layout
def update_label(self, button_instance):
self.lista.text = self.t.text
WumpaTime().run()

How to output text and delete text with buttons

I'm trying to output text from button presses and add it to a textbox each time it's pressed as well as delete the text when the delete button is pressed - in normal python.
I've been trying to use kivy.uix.textinput, but I'm not sure on how to output the value from the buttons as well as delete it.
This is what I've done so far.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name='code_screen')
main_grid_lay = GridLayout(cols=2,cols_minimum={0:640, 1:175})
self.add_widget(main_grid_lay)
#Code ouput display
display_code = TextInput(text='Code!', readonly = True, id=output)
main_grid_lay.add_widget(display_code)
#Options Buttons
box_lay = BoxLayout(orientation='vertical')
main_grid_lay.add_widget(box_lay)
delete_button = Button(
text='Delete',
size_hint_x= None,
width=160,
id='' #unsure on how to delete
)
box_lay.add_widget(delete_button)
base_left = Button(
text='Base Left',
#on_release= b_left(),
#id='left',
)
base_right = Button(
text='Base Right',
on_release=self.degree_popup,
#on_release= b_right(),
)
#b_left = self.ids.output.text='left'
#b_left = self.ids.output.text='right'
box_lay.add_widget(base_left)
box_lay.add_widget(base_right)
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == '__main__':
MyMain().run()
It currently sends an error and is probably because of the ids. Not fully sure how ids work without using kv language. Any help is appreciated.
The id only makes sense in .kv, in python they are useless. In this case the solution is to access the objects in the methods connected to on_release but for this they must be class attributes.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name="code_screen")
self.display_code = TextInput(text="Code!", readonly=True)
self.delete_button = Button(
text="Delete", size_hint_x=None, width=160, on_release=self.delete_clicked
)
self.base_left = Button(text="Base Left", on_release=self.left_clicked)
self.base_right = Button(text="Base Right", on_release=self.right_clicked)
main_grid_lay = GridLayout(cols=2, cols_minimum={0: 640, 1: 175})
main_grid_lay.add_widget(self.display_code)
self.add_widget(main_grid_lay)
box_lay = BoxLayout(orientation="vertical")
box_lay.add_widget(self.delete_button)
main_grid_lay.add_widget(box_lay)
box_lay.add_widget(self.base_left)
box_lay.add_widget(self.base_right)
def delete_clicked(self, instance):
self.display_code.text = ""
def left_clicked(self, instance):
self.display_code.text += "left"
def right_clicked(self, instance):
self.display_code.text += "right"
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == "__main__":
MyMain().run()

Categories

Resources