Making TextInput on_text_validate event accessible from another widget - python

I'm trying to create a custom widget as a module. My custom widget includes a TextInput and some other stuff inside a FloatLayout widget.
When i import my custom widget i want to use on_text_validate event for TextInput within the Floatlayout.
As you know we can add the button behavior to any widget via ButtonBehavior class but i couldn't find a way to add on_text_validate event to my custom widget.
That's my module named mymodule.py inside my project folder.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
#from kivy.uix.behaviors import FocusBehavior
Builder.load_string("""
<SearchBar>:
id: search_bar
size_hint: 1,.1
pos_hint: {'top':1}
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
TextInput:
id: input
hint_text: 'search'
size_hint: .82,None
height: dp(33)
pos_hint: {'center_x':.565,'center_y': .5}
multiline: False
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
padding_x: 20,150
""")
class SearchBar(FloatLayout):
pass
I call it from main.py as
#:import SearchBar mymodule.SearchBar
And that's my main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
Builder.load_string("""
#:import SearchBar mymodule.SearchBar
<MyTest>:
SearchBar:
id: search
size_hint: 1,.1
# on_text_validate: root.do_something()
""")
class MyTest(FloatLayout):
def do_something(self,*args):
print('done')
class Test(App):
def build(self):
return MyTest()
Test().run()

A possible solution is to create an event with the same name and shoot it with the TextInput event:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<SearchBar>:
id: search_bar
size_hint: 1,.1
pos_hint: {'top':1}
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
TextInput:
id: input
hint_text: 'search'
size_hint: .82,None
height: dp(33)
pos_hint: {'center_x':.565,'center_y': .5}
multiline: False
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
padding_x: 20,150
on_text_validate: root.dispatch('on_text_validate', *args)
""")
class SearchBar(FloatLayout):
def __init__(self, **kwargs):
super(SearchBar, self).__init__(**kwargs)
self.register_event_type('on_text_validate')
def on_text_validate(self, *args):
pass

Related

AttributeError: 'super' object has no attribute '__getattr__' in Kivy

I am facing an error when trying to add buttons to a specific GridLayout using ids. Ideally the code below should generate 10 buttons in the GridLayout with an id of grids, but instead the error that shows up is
AttributeError: 'super' object has no attribute 'getattr'
The code in my main.py file is -->
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import mainthread
Builder.load_file("design.kv")
class RoundedButton(Button):
pass
class RootWidget(ScreenManager):
pass
class MainScreen(Screen):
def on_enter(self):
for i in range(10):
button = RoundedButton()
self.ids.grids.add_widget(button)
class MainApp(App):
def build(self):
return RootWidget()
class RootWidget(ScreenManager):
pass
if __name__ == "__main__":
MainApp().run()
and the code in my design.kv file is -->
<MainScreen>:
canvas.before:
Color:
rgba: (245/255,245/255,245/255,245/255)
Rectangle:
pos: self.pos
size: self.size
GridLayout:
id: box1
cols:1
spacing:5
GridLayout:
id: box
cols:1
size_hint_y: 0.10
TextInput:
id: ti
hint_text: 'Search'
size_hint: 1, 0.05
text_size: self.width, self.height
background_normal: ''
ScrollView:
id: scrolls
do_scroll_x:False
spacing: 10, 5
GridLayout:
id: grids
cols:1
RoundedButton:
text: "hi"
size_hint: .98, .25
<RoundedButton#Button>
background_color: (0,0,0,0)
background_normal: ''
canvas.before:
Color:
rgba: (1,0,0,1)
RoundedRectangle:
size: self.size
pos: self.pos
radius: [10]
<RootWidget>:
MainScreen:
name: "main_screen"
I was wondering what the issue is
You need to call another function with Clock.schedule_once in on_enter function and try to load your widgets in it. It happens because kivy doesn't let you add widgets before first frame so we can give order kivy to add widgets soon as possible with that.
Example code below:
def on_enter(self):
Clock.schedule_once(self.load_buttons)
def load_buttons(self,*args):
for i in range(10):
button = RoundedButton()
self.ids.grids.add_widget(button)

BoxLayout 'cols' doens't work in AnchorLayout

Here is my kivyfile.kv
WindowManager:
FirstWindow:
SecondWindow:
<FirstWindow>:
name: "first"
AnchorLayout:
anchor_x: "left"
anchor_y: "top"
BoxLayout:
cols:2
orientation: 'vertical'
size_hint: None, None
size: self.minimum_size
Button:
text: "Button 1"
size_hint: (None,None)
width:200
height:30
Button:
text: "Button2"
size_hint: (None,None)
width:200
height:30
TextInput:
id: url
multiline: False
size_hint: (None,None)
width:200
height:30
TextInput:
id: url2
multiline: False
size_hint: (None,None)
width:200
height:30
Here is my .py file:
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class FirstWindow(Screen):
pass
class SecondWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv=Builder.load_file('kivyfile.kv')
class Downloader(App):
def build(self):
return kv
if __name__ == '__main__':
Downloader().run()
What I am trying to achive, is to get a BoxLayout at the top left corner, which has two columns - Button and TextInput NEXT TO it. However, it only does all components one under each other. I have also a future question on how to add another BoxLayout NEXT TO the previous BoxLayout that I have. As in - how to make it be on the right side of existing BoxLayout, how to position it.

How Can I Remove BoxLayout?

I want to remove a Boxlayout in my widget, but I cant.
There is a Boxlayout at the begining of the app called "Speca". After my sellections it must be removed.
When you run the app, sellect something in "Home" Spinner and click any of the new Toggle buttons.
Wtih the press of any of the Toggle buttons "Speca" must disappear.
Please help.
Here is main.py:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.spinner import Spinner
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, StringProperty, BooleanProperty
from kivy.properties import ListProperty
from collections import OrderedDict
from kivy.uix.togglebutton import ToggleButton
data1=["mother","father","son"]
data2=["uncle","aunt","grandfather"]
data3=["jack","mike","simon"]
data4=["1898","1975","1985","1885"]
amd="0dp"
class MainWidget(Widget):
def remove_layout(self, *ignore):
self.remove_widget(self.layout)
global amd
an0=tuple(list(OrderedDict.fromkeys(data1)))
cal5= ObjectProperty()
cal6= ObjectProperty()
def btn10(self,text):
if self.cal5:
self.cal5.parent.remove_widget(self.cal5)
self.cal5 =ModelSpecifications()
a=data2
b=data3
c=data4
mi=[]
n=0
while n < len(a):
aba=n
mi.append(aba)
n+=1
for i in mi:
self.b1=MyTButton(text=str(i),size_hint=(1,None),height="100dp",group="selections")
self.cal5.add_widget(self.b1)
self.ids.scd.add_widget(self.cal5, index=3)
def on_state(self, togglebutton): #<----THIS IS THE TUGGLEBUTTON I WANT USE
global amd
tb = togglebutton
text1=tb.text
if text1=="0":
amd="50dp"
elif text1=="1":
amd="100dp"
else:
amd="200dp"
if self.cal6:
self.cal6.parent.remove_widget(self.cal6)
self.cal6 =Spec()
self.ids.scd.add_widget(self.cal6, index=2)
class SecondPage(ScrollView):
pass
class Speca(BoxLayout): #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
pass
class Spec(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint=(1,None)
self.height=amd
class MyTButton(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class ModelSpecifications(BoxLayout): #this is the class I want add after my spinner selection
pass
class Calculation(GridLayout):
pass
class MyApp(App):
pass
MyApp().run()
here is my.kv :
MainWidget:
<MainWidget>:
hideable: hideable
ScreenManager:
id: scmanager
size: root.width, root.height
Screen:
id: scndpage
name: "second"
SecondPage:
Calculation:
id:scd
cols:1
height: self.minimum_height
row_default_height: "70dp"
size_hint_y: None
spacing:"10dp"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
size_hint: 1, None
height: "50dp"
pading:"10dp"
spacing:"10dp"
orientation: "vertical"
BoxLayout:
orientation: "horizontal"
Label:
text:"Name:"
color: 0,0,0,1
TextInput:
text:"---"
color: 0,0,0,1
Label:
text:"Surname:"
color: 0,0,0,1
TextInput:
text:"-----"
color: 0,0,0,1
BoxLayout:
id:scdd
size_hint: 1, 1
height: "100dp"
orientation: "vertical"
BoxLayout:
size_hint: 1, None
height: "50dp"
orientation: "horizontal"
Label:
text: " Sellection:"
color: 0,0,0,1
Spinner:
text: 'Home'
values: root.an0
on_text: app.root.btn10(self.text)
Speca: #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
Button:
text:" Calculate"
Button:
text:"Sellect"
Button:
text:"Back"
<ModelSpecifications>:
id:anss
pading:"10dp"
spacing:"10dp"
size_hint: 1, None
height: "100dp"
orientation: "horizontal"
<MyTButton#ToggleButton>: #<----THIS IS THE TUGGLEBUTTON I WANT USE
on_state:
app.root.on_state(self)
<Speca>: #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
orientation:"vertical"
Label:
color: (1,1,0,1)
text:"I WANT TO REMOVE THIS PART WHEN I PRESS ANY OF TOGGLE BUTTONS"
Please run the app and see it.
Looks to me like speca is a child of the calculation widget with ID scd. So give speca an ID and then Remove speca via ids in on_srate python function.
Kv
SecondPage:
Calculation:
id:scd
speca:
id: speca
py
def on_state():
self.root.ids.scd.remove_widget(self.root.ids.speca)
YES.
When I make those changes;
Kv
SecondPage:
Calculation:
id:scd
speca:
id: speca
py
def on_state():
self.ids.scd.remove_widget(self.ids.speca)
It works.

RecycleView with multiple colums with different class

I have a kind of major problem here. I want to create a list at the bottom of my app. I have a dictionary which works on this guy's solution: How to put multiple columns into a kivy RecycleView?
HOWEVER, I want to add that widget with button. Everything looks fine but I have problem with ids.
Please take a look at it, "FinalList" class cant find variables.
When you run it click "Calculate selections" it adds another class which has another button called "Confirm". Click "Confirm", it should add list at the bottom but it does not.
here is my main.py code;
from kivy.animation import Parallel
from kivy.app import App
from kivymd.app import MDApp
from kivy.uix.gridlayout import GridLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.core.window import Window
from kivy.lang import Builder
from functools import partial
from kivy.properties import ObjectProperty, StringProperty, BooleanProperty
#Window.size=((640/1.5),(960/1.5))
items = [{'SP1': 'Artikelnummer', 'SP2': 'Name', 'SP3': 'Groesse'},
{'SP1': '510001', 'SP2': 'Big Pump', 'SP3': '1.50 L'},
{'SP1': '523001', 'SP2': 'Leonie Still', 'SP3': '1.50 L'},
{'SP1': '641301', 'SP2': 'Cola Mix', 'SP3': '1.50 L'}
]
class MainWidget(Widget):
cal2= ObjectProperty()
cal4= ObjectProperty()
def btn4(self):
self.cal2 = MaterialS() #MaterialS is my class with different widget
self.ids.scd.add_widget(self.cal2, index=0) #scd is the id of my main widget, index =0 help me to insert to the bottom
def btn8(self):
self.cal4 = RV() **#HERE I CALL THE RV CLASS**
self.ids.scd.add_widget(self.cal4, index=0)
class SecondPage(ScrollView):
pass
class Calculation(GridLayout):
pass
class MaterialS(BoxLayout):
def btn7(self,x):
self.ids[x].disabled= True
def btn9(self,x):
self.ids[x].disabled= True
class FinalList(BoxLayout):
pass
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'spalte1_SP': str(x['SP1']), 'spalte2_SP': str(x['SP2']), 'spalte3_SP': str(x['SP3'])} for x in items]
class MyApp(MDApp):
pass
MyApp().run()
here is my.kv file;
MainWidget:
<MainWidget>:
ScreenManager:
id: scmanager
size: root.width, root.height
Screen:
id: scndpage
name: "second"
SecondPage:
Calculation:
id:scd
cols:1
height: self.minimum_height
row_default_height: "70dp"
size_hint_y: None
spacing:"10dp"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
Button:
id: cslect
text:"Calculate Selections"
on_press: app.root.btn4()
<MaterialS>:
pading:"10dp"
spacing:"10dp"
size_hint: 1, None
height: "250dp"
orientation:"vertical"
BoxLayout:
size_hint: 1, 1
orientation:"horizontal"
Label:
text:"cutter head material"
color: 0,0,0,1
Button:
id: btnStd
text:"Confirm"
on_press: app.root.btn8()
on_release: root.btn9("btnStd")
<Optionals>:
BoxLayout:
orientation: "vertical"
Button:
id: btnStd
text:"Confirm"
on_press: app.root.btn8()
on_release: root.btn9("btnStd")
<FinalList>:
orientation: 'horizontal'
spalte1_SP: 'spalte1'
spalte2_SP: 'spalte2'
spalte3_SP: 'spalte3'
Label:
id: SP1
text: app.root.spalte1_SP **#HERE IS THE MAIN PROBLEM**
Label:
id: SP2
text: app.root.spalte2_SP **#HERE IS THE MAIN PROBLEM**
Label:
id: SP3
text: app.root.spalte3_SP **#HERE IS THE MAIN PROBLEM**
<RV>:
viewclass: 'FinalList'
RecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
You just need to correct your references to the properties of the FinalList. Like this:
<FinalList>:
orientation: 'horizontal'
spalte1_SP: 'spalte1'
spalte2_SP: 'spalte2'
spalte3_SP: 'spalte3'
Label:
id: SP1
text: root.spalte1_SP #**#HERE IS THE MAIN PROBLEM**
Label:
id: SP2
text: root.spalte2_SP #**#HERE IS THE MAIN PROBLEM**
Label:
id: SP3
text: root.spalte3_SP #**#HERE IS THE MAIN PROBLEM**

How do I define what happens when user enter's text in TextInput and presses enter in Kivy?

I am trying to create a simple Chatbot application UI using Kivy and Python but I'm stuck at the very first stage.
How do I access the TextInput widget inside the BoxLayout to fetch it's contents?
I read that the function on_text_validate() is called whenever you press 'Enter' on the TextInput. However this code down below doesn't seem to be functioning.
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.screenmanager import Screen, ScreenManager, FadeTransition
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty
from kivy.lang import Builder
Builder.load_string('''
#:import AnchorLayout kivy.uix.anchorlayout
#:import Layout kivy.uix.layout
<ChatBotScreen>:
BoxLayout:
orientation: 'vertical'
ScrollView:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
botOutput: root.botOutput
ScrollView:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
userInput: root.userInput
TextInput:
id: ti_userinput
multiline: False
''')
class ChatBotScreen(Screen):
userInput = StringProperty()
botOutput = StringProperty()
def on_text_validate(self):
text_input_userInput = self.ids['ti_userinput'].text
self.ids['ti_userinput'].text = ''
print(text_input_userInput)
def UserInput(self):
pass
def BotOutput(self):
pass
sm = ScreenManager(transition=FadeTransition())
sm.add_widget(ChatBotScreen(name='mainchat'))
class MyApp(App):
def build(self):
return sm
Please guide me.
The problem in your case is that on_text_validate is not a method of the Screen class, but of TextInput, so you will never call on_text_validate of the ChatBotScreen class, what you can do is invoke that method from the on_text_validate event of the TextInput:
Builder.load_string('''
#:import AnchorLayout kivy.uix.anchorlayout
#:import Layout kivy.uix.layout
<ChatBotScreen>:
BoxLayout:
orientation: 'vertical'
ScrollView:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
botOutput: root.botOutput
ScrollView:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
userInput: root.userInput
TextInput:
id: ti_userinput
multiline: False
on_text_validate: root.on_text_validate()
''')
class ChatBotScreen(Screen):
userInput = StringProperty()
botOutput = StringProperty()
def on_text_validate(self):
text_input_userInput = self.ids['ti_userinput'].text
self.ids['ti_userinput'].text = ''
print(text_input_userInput)
sm = ScreenManager(transition=FadeTransition())
sm.add_widget(ChatBotScreen(name='mainchat'))
class MyApp(App):
def build(self):
return sm

Categories