python kivy, why my "enter" button with "on_keyboard_down" not working - python-3.x

I am working on the same issue from my previous post. Now I added screen manager, and I can switch to different pages.
why is my kivy program not calling the function from another class?
I am still confused about OOP with GUIs, however I tried the following, and none of them worked.
I tried to add an instance of Screen 1() in the main app(), and it did nothing. Then, I tried to add the contractor method init into Screen_1() and it says I don't have a build method. And few other ideas that didn't work at all.
Then I realized that there is no link between Screen_Manager() and Screen_1() because all of my methods are in Screen_1(), but the build method is returning Screen_Manager(). On my kv file, there is this code:
<Screen_Manager>:
Screen_1:
Screen_2:
isn't this is where the program "links" between the Screen_Manager() class to other classes?
if someone can help me understand what I am not understanding and help me correct my problem, it would help me to learn about kivy. I understand, <> is like apply to rules, which has 2 widgets screen 1 and screen 2, and also have their own rules.
here is my main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.lang.builder import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.modules import keybinding
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager, Screen
class Screen_Manager(ScreenManager):
pass
class Screen_1(Screen):
main_display = ObjectProperty()
text_input = ObjectProperty()
def plus_1(self):
self.value = int(self.main_display.text)
self.main_display.text = str(self.value + 1)
def minus_1(self):
self.value = int(self.main_display.text)
self.main_display.text = str(self.value - 1)
def up(self):
self.main_display.text = self.text_input.text
self.text_input.text = ''
class Keyboard(Widget):
def __init__(self, instance):
super().__init__()
self.a = instance
self.keyboard = Window.request_keyboard(None, self)
self.keyboard.bind(on_key_down=self.on_keyboard_down)
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'enter':
self.a.up()
return True
class Screen_2(Screen):
pass
class MainApp(App):
def build(self):
self.title = "Number Control App"
self.sm = Screen_Manager()
key = Keyboard(self.sm)
return self.sm
if __name__=="__main__":
MainApp().run()
my kv file
<Screen_Manager>:
Screen_1:
Screen_2:
<Screen_1>:
name: "first"
main_display: display_1
text_input: text_input_1
#------inherenting from BoxLayout
BoxLayout:
orientation: "vertical"
#------begining of conent--
BoxLayout:
size_hint_y: 1
#-nexted content------
Label:
id: display_1
text: "0"
font_size: "150sp"
background_color: 0.1, 0.5, 0.6,
#--------
BoxLayout:
size_hint_y: .35
orientation: "horizontal"
#-nexted content------
Button:
text: "-"
font_size : "60sp"
on_press: root.minus_1()
#-nexted content------
Button:
text: "+"
font_size : "35sp"
background_color: (0, 1, 0, 1)
on_press: root.plus_1()
#--------
BoxLayout:
size_hint_y: .15
orientation: "horizontal"
#-nexted content-------
Button:
text: "Config"
size_hint_x: .25
on_release:
root.manager.current = "second"
root.manager.transition.direction = "left"
#-nexted content-------
TextInput:
id: text_input_1
size_hint_x: 1
hint_text: "Enter your initial # here"
multiline: False
#-nexted content-------
Button:
text: "Up"
size_hint_x: .25
on_press: root.up()
<Screen_2>:
name: "second"
Button:
text: "Go Back"
on_press:
app.root.current = "first"
root.manager.transition.direction = "right"enter code here
thanks again for taking the time to help.

Not sure I understand your question, but pressing Enter in your TextInput executed your code:
self.a.up()
but a in your KeyBoard is the Screen_Manager as set at the line:
self.sm = Screen_Manager()
key = Keyboard(self.sm)
and Screen_Manager has no up method. You can fix that by changing the code in your KeyBoard code to:
self.a.current_screen.up()
Note that this will only work when the current screen is Screen_1.

Related

Trying to mix boxlayout and floatlayout in Kivy Python but when I run I get a blank black box

I am extremely new to kivy and Python but I managed to get a few simple buttons with some random generation that outputs to a label running in floatlayout. I wanted to implement aspects of BoxLayout to make it easier to align buttons vertically on the left hand side of the app so I could use the rest of the open space for displaying the output. What I am seeing when I tried to implement aspects of BoxLayout is no errors but just a big black box when I run the program. What am I missing?
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
import random
from kivy.core.window import Window
#Setting the size of the window that opens
Window.fullscreen = False
Window.size = (300,300)
#Deciding if user can resize the window or not
from kivy.config import Config
Config.set('graphics', 'resizable', True)
Config.write()
class Generators(FloatLayout,BoxLayout):
def __init__(self,**kwargs):
super(Generators,self).__init__(**kwargs)
self.defaultlayout=BoxLayout(orientation='vertical')
self.dcanvas = FloatLayout()
#Setting up the labels
self.name_label = Label(text = "Random Table Output", size_hint=(.2, .15),pos_hint={'x':.5, 'y':.9})
self.main_label = Label(text = "Press a random generator on the side and output will display here.", size_hint=(1, .55),pos_hint={'x':0, 'y':.35})
#Setting the details of every button
self.louche_button = Button(text = "LOUCHE", size_hint=(.15, .10),pos_hint={'x':.0, 'y':.9},on_press = self.update)
self.randomname_button = Button(text = "Random Name\n Generator", size_hint=(.15, .10),pos_hint={'x':0, 'y':.8},on_press = self.name_gen)
self.food_button = Button(text = "Food", size_hint=(.15, .10),pos_hint={'x':0, 'y':.7})
self.go_button = Button(text = "Go", size_hint=(.15, .10),pos_hint={'x':0, 'y':.6})
self.walk_button = Button(text = "Walk", size_hint=(.15, .10),pos_hint={'x':.0, 'y':.5})
self.inventory_button = Button(text = "Inventory", size_hint=(.15, .10),pos_hint={'x':0, 'y':.4})
#Adds the widgets for each button and label designed above
self.defaultlayout.add_widget(self.louche_button)
self.defaultlayout.add_widget(self.randomname_button)
self.defaultlayout.add_widget(self.food_button)
self.defaultlayout.add_widget(self.go_button)
self.dcanvas.add_widget(self.walk_button)
self.dcanvas.add_widget(self.inventory_button)
self.defaultlayout.add_widget(self.main_label)
self.defaultlayout.add_widget(self.name_label)
def update(self,event):
LOUCHE = ['LOCALITY: Something specifically related to the current environment happens.\nThe buildings now on fire. The ground collapses. It\'s flooding. Moonquake!' , 'OFFER: Offer a bargain, an extra, or a perk for a cost.\noffer a better position, with risk. Offer a temptation.', 'UNEXPECTED DANGER: Make something up or roll it up at random.\nTie it in if you want now or worry about how it fits in later', 'CALLBACK: Use something that they\'ve given you. A backstory element.\nAn off-handed comment. Gear. A character sheet aspect', 'HARM: Deal damage', 'END SOMETHING: End an ongoing effect, bonus, or fictional advantage. Take a \nresource away, something you possess, whether it\'s a piece of gear, \nan ability, or an ally']
self.main_label.text = (str(random.choice(LOUCHE)))
def name_gen(self,event):
first = ['Bob ', 'Aaron ', 'Keisha ']
last = ['Quebman', 'Harp', 'DaFuq']
self.main_label.text = (str(random.choice(first))) + (str(random.choice(last)))
class app1(App):
def build(self):
return Generators()
if __name__=="__main__":
app1().run()```
Extending two classes that have differing implementations of the same method (do_layout() for example) is a bad idea. You do not need to extend a class in order to use that class. You can build your App display using FloatLayout and BoxLayout directly.
Here is an example where the Generators class extends just BoxLayout:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
import random
from kivy.core.window import Window
#Setting the size of the window that opens
Window.fullscreen = False
Window.size = (300,300)
#Deciding if user can resize the window or not
from kivy.config import Config
Config.set('graphics', 'resizable', True)
kv = '''
Generators:
orientation: 'horizontal' # Generators is a BoxLayout
BoxLayout: # left side of App
orientation: 'vertical'
size_hint: 0.25, 1
Button:
text: "LOUCHE"
size_hint: (1, .10)
pos_hint: {'x':.0, 'y':.9}
on_press: root.update()
Button:
text: "Random Name\\n Generator"
size_hint: (1, .10)
pos_hint: {'x':0, 'y':.8}
on_press: root.name_gen()
Button:
text: "Food"
size_hint: (1, .10)
pos_hint: {'x':0, 'y':.7}
Button:
text: "Go"
size_hint: (1, .10)
pos_hint: {'x':0, 'y':.6}
Button:
text: "Walk"
size_hint: (1, .10)
pos_hint: {'x':.0, 'y':.5}
Button:
text: "Inventory"
size_hint: (1, .10)
pos_hint: {'x':0, 'y':.4}
FloatLayout: # Right side of App
size_hint: 0.75, 1
Label:
text: "Random Table Output"
size_hint: (.2, .15)
pos_hint: {'center_x':.5, 'top':1}
Label:
id: main_label
text: "Press a random generator on the side and output will display here."
size_hint: (1, .55)
pos_hint: {'x':0, 'y':.35}
'''
class Generators(BoxLayout):
def update(self, *args):
LOUCHE = ['LOCALITY: Something specifically related to the current environment happens.\nThe buildings now on fire. The ground collapses. It\'s flooding. Moonquake!' , 'OFFER: Offer a bargain, an extra, or a perk for a cost.\noffer a better position, with risk. Offer a temptation.', 'UNEXPECTED DANGER: Make something up or roll it up at random.\nTie it in if you want now or worry about how it fits in later', 'CALLBACK: Use something that they\'ve given you. A backstory element.\nAn off-handed comment. Gear. A character sheet aspect', 'HARM: Deal damage', 'END SOMETHING: End an ongoing effect, bonus, or fictional advantage. Take a \nresource away, something you possess, whether it\'s a piece of gear, \nan ability, or an ally']
self.ids.main_label.text = (str(random.choice(LOUCHE)))
def name_gen(self, *args):
first = ['Bob ', 'Aaron ', 'Keisha ']
last = ['Quebman', 'Harp', 'DaFuq']
self.ids.main_label.text = (str(random.choice(first))) + (str(random.choice(last)))
class app1(App):
def build(self):
return Builder.load_string(kv)
if __name__=="__main__":
app1().run()

How to prevent multiple instances of the Root-Widget in Kivy

OK, I try to make my first App in Kivy and I have a Problem with multiple Instances of my Root Widget.
I need to add widgets to my app while running. (to display a spreadsheet)
But because of the multiple instances of the Root Widget, I can't access the widgets with the kivy ids lookup object.
https://kivy.org/docs/guide/lang.html
I made three Buttons to display the problem in the console
Button -1-, -2-, and -3-
Please explain me why there are multiple instances and how I can prevent it, so the code will work.
Thanks for your help!
File --> Match.py
#!python
from __future__ import print_function
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
import gc
class my_gc():
def search_g(self):
for obj in gc.get_objects():
if isinstance(obj, Root):
try:
print(obj.ids)
except:
print('fail')
#Root Widget
class Root(BoxLayout):
sm = ObjectProperty(None)
sc0 = ObjectProperty(None)
sc1 = ObjectProperty(None)
sc2 = ObjectProperty(None)
sc3 = ObjectProperty(None)
mylbl0 = StringProperty('screen 0')
mylbl1 = StringProperty('screen 1')
mylbl2 = StringProperty('screen 2')
mylbl3 = StringProperty('screen 3')
def find_ids(self):
print(self.ids)
class Screen_Manager(ScreenManager):
pass
class Screen_0(Screen):
pass
class Screen_1(Screen):
pass
class Screen_2(Screen):
pass
class Screen_3(Screen):
pass
class Match(App):
rootwidget = Root()
sm = Screen_Manager()
gc = my_gc()
def build(self):
pass
Match().run()
File --> Match.kv
#Root Widget (BoxLayout)
Root:
sm: sm
sc1: sc0
sc1: sc1
sc1: sc2
sc1: sc3
BoxLayout:
spacing: '20dp'
orientation: 'vertical'
Screen_Manager:
id: sm
Screen_0:
id: sc0
name: 'sc0'
manager: 'sm'
BoxLayout:
id: box_0-0
Label:
id: lbl_0
text: app.rootwidget.mylbl0
Label:
id: lbl_0-1
text: root.mylbl0
Screen_1:
id: sc1
name: 'sc1'
manager: 'sm'
Label:
id: lbl1
text: root.mylbl1
Screen_2:
id: sc2
name: 'sc2'
manager: 'sm'
Label:
id: lbl2
text: root.mylbl2
Screen_3:
id: sc3
name: 'sc3'
manager: 'sm'
Label:
id: lbl3
text: root.mylbl3
#Tab-Buttons
BoxLayout:
size_hint: 1 , None
height: '60dp'
orientation: 'horizontal'
Button:
text: '-0-'
on_press: root.sm.current = sc0.name
Button:
text: '-1-'
on_press: root.sm.current = sc1.name
Button:
text: '-2-'
on_press: root.sm.current = sc2.name
Button:
text: '-3-'
on_press: root.sm.current = sc3.name
Button:
text: '<'
size_hint: None , 1
width: '60dp'
on_press: root.sm.current = root.sm.previous()
Button:
text: '>'
size_hint: None , 1
width: '60dp'
on_press: root.sm.current = root.sm.next()
Button:
text: '-b1-'
size_hint: None , 1
width: '60dp'
#on_press: root.search_g() #<-- doesn't work
on_press: app.gc.search_g() #<-- works
Button:
text: '-b2-'
size_hint: None , 1
width: '60dp'
on_press: root.find_ids() #<-- doesn't work
on_press: app.rootwidget.find_ids() #<-- works
Button:
text: '-b3-'
size_hint: None , 1
width: '60dp'
on_press: print(root.mylbl1) #<-- works
on_press: print(app.rootwidget.mylbl1) #<-- works
Mutilple Instance - Root Widget
As per your code, you don't have mutiple instances of the root widget. Your root widget is Root: as per the kv file, match.kv.
root.search_g()
It doesn't work because your root which is the class Root(BoxLayout) does not have the search_g() method.
root.find_ids - works
When I commented off app.rootwidget.find_ids, the call to root.find_ids works i.e. it printed the ids. The keyword root is available only in rule definitions and represents the root widget of the rule (the first instance of the rule). When your kv file is parsed, kivy collects all the widgets tagged with id’s and places them in self.ids dictionary type property.
app.rootwidget.find_ids
It works but there were no ids because it is referencing to the method find_ids in the initiated object, class Root. The rootwidget is just a variable by name and it is a different object than the one from Root:
From the attached screen shot, you will notice that the object locations for root.find_ids and app.rootwidget.find_ids are different.
Adding Widgets Dynamically
To add widgets dynamically into your app while it is running, you have to do the following. For an example, please refer to my other post at Kivy/python : when i click row then it shows error IndexError: list index out of range
Python Script - match.py
Add an import statement as follow:
from kivy.properties import ObjectProperty
Declare a variable (e.g. container) or ObjectProperty type e.g. in the class Root
class Root(BoxLayout):
container = ObjectProperty(None)
Remove all widgets added dynamically before adding
Add widgets dynamically.
kv File - match.kv
Define a layout e.g. BoxLayout or GridLayout in your kv file.
Give an id to the layout e.g. container in your kv file.
Hook up the ObjectProperty to the id
Root:
container: container
...
# Container for adding widgets dynamically
BoxLayout:
id: container
Solution
The solution is to declare an ObjectProperty, rootwidget as follow:
snippet
class Match(App):
rootwidget = ObjectProperty(None)
title = "Solution App"
gc = my_gc()
def build(self):
self.rootwidget = Root()
return self.rootwidget
match.py
#!python
from __future__ import print_function
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
import gc
class my_gc():
def search_g(self):
print("my_gc.search_g called")
for obj in gc.get_objects():
if isinstance(obj, Root):
try:
print("obj={0}, ids={1}".format(obj, obj.ids))
for key, value in obj.ids.items():
print(" key={0}, value={1}".format(key, value))
except:
print('fail')
# Root Widget
class Root(BoxLayout):
sm = ObjectProperty(None)
sc0 = ObjectProperty(None)
sc1 = ObjectProperty(None)
sc2 = ObjectProperty(None)
sc3 = ObjectProperty(None)
mylbl0 = StringProperty('screen 0')
mylbl1 = StringProperty('screen 1')
mylbl2 = StringProperty('screen 2')
mylbl3 = StringProperty('screen 3')
def find_ids(self):
print("Root.find_ids called")
print("self={0}, self.ids={1}".format(self, self.ids))
for key, value in self.ids.items():
print(" key={0}, value={1}".format(key, value))
class Screen_Manager(ScreenManager):
pass
class Screen_0(Screen):
pass
class Screen_1(Screen):
pass
class Screen_2(Screen):
pass
class Screen_3(Screen):
pass
class Match(App):
rootwidget = ObjectProperty(None)
title = "Solution App"
gc = my_gc()
def build(self):
self.rootwidget = Root()
return self.rootwidget
if __name__ == "__main__":
Match().run()
match.kv
#Root Widget (BoxLayout)
<Root>:
sm: sm
sc1: sc0
sc1: sc1
sc1: sc2
sc1: sc3
BoxLayout:
spacing: '20dp'
orientation: 'vertical'
Screen_Manager:
id: sm
Screen_0:
id: sc0
name: 'sc0'
manager: 'sm'
BoxLayout:
id: box_0-0
Label:
id: lbl_0
text: root.mylbl0
Label:
id: lbl_0-1
text: root.mylbl0
Screen_1:
id: sc1
name: 'sc1'
manager: 'sm'
Label:
id: lbl1
text: root.mylbl1
Screen_2:
id: sc2
name: 'sc2'
manager: 'sm'
Label:
id: lbl2
text: root.mylbl2
Screen_3:
id: sc3
name: 'sc3'
manager: 'sm'
Label:
id: lbl3
text: root.mylbl3
#Tab-Buttons
BoxLayout:
id: secondBoxLayout
size_hint: 1 , None
height: '60dp'
orientation: 'horizontal'
Button:
text: '-0-'
on_press: root.sm.current = sc0.name
Button:
text: '-1-'
on_press: root.sm.current = sc1.name
Button:
text: '-2-'
on_press: root.sm.current = sc2.name
Button:
text: '-3-'
on_press: root.sm.current = sc3.name
Button:
text: '<'
size_hint: None , 1
width: '60dp'
on_press: root.sm.current = root.sm.previous()
Button:
text: '>'
size_hint: None , 1
width: '60dp'
on_press: root.sm.current = root.sm.next()
Button:
text: '-b1-'
size_hint: None , 1
width: '60dp'
on_press:
print("\non_press: {}".format(self.text))
# root.search_g() #<-- doesn't work
print(" app.gc_search_g()")
app.gc.search_g() #<-- works
Button:
text: '-b2-'
size_hint: None , 1
width: '60dp'
on_press:
print("\non_press: {}".format(self.text))
print(" root.find_ids()")
root.find_ids() #<-- works
print("\n app.root.find_ids()")
app.rootwidget.find_ids() #<-- works
Button:
text: '-b3-'
size_hint: None , 1
width: '60dp'
on_press:
print("\non_press: {}".format(self.text))
print(root.mylbl1) #<-- works
print(app.rootwidget.mylbl1) #<-- works
Output

Changing button text with popup text input kivy

So, I'm new to kivy and can't get this code to work. I'm trying to update a button text with a popup text input. The popup shows when the button is pressed, and when it's dismissed it should update the text on the button with whatever text was typed in it.
I've tried many variations of this code, but no one have worked. Either nothing happens or I get this error:
AttributeError: 'super' object has no attribute '__getattr__'
Here it is:
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
class MainWidget(GridLayout):
pass
class PopText(Popup):
def textChange(self):
MyButton().change()
def getText(self):
text = self.display
return text
class MyButton(AnchorLayout):
def change(self):
self.ids.equip_bt.text = PopText().getText()
print(self.ids.my_bt.text)
class MyApp(App):
def build(self):
return MainWidget()
if __name__ == "__main__":
MyApp().run()
my.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
on_dismiss: self.textChange()
display: pop_text.text
TextInput:
id: pop_text
focus: True
multiline: False
on_text_validate: root.dismiss()
<MyButton>:
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
cols: 1
rows: 2
MyButton:
MyButton:
Any ideas on how to solve this?
Here is a minimum example of what you are trying to achieve. The hard part is connection the button from the Popup to the Button which opened it. I am going through the app class to achieve that. I got to admit it is not a pretty solution.
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
class PopupBttn(Button):
def openPopup(self):
Pop = TextPopup().open(self.text)
class TextPopup(Popup):
def open(self, text, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file:
BoxLayout:
PopupBttn:
id: bttn
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: app.root.ids.bttn.text=root.ids.txtipt.text
on_release: root.dismiss()
Here is an updated version to use multiple buttons. Unfortunately, you will need to set ids and name to the string of id per Button.
python file
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.properties import StringProperty
class PopupBttn(Button):
def openPopup(self):
print(self.name)
Pop = TextPopup().open(self.text, self.name)
class TextPopup(Popup):
bttnid = StringProperty()
text = StringProperty()
def open(self, text, id, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
self.bttnid = id
def setText(self):
App.get_running_app().root.ids[self.bttnid].text = self.text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file
BoxLayout:
orientation: 'vertical'
PopupBttn:
name: 'one'
id: one
text: 'I am the first Button'
PopupBttn:
name: 'two'
id: two
PopupBttn:
name: 'three'
id: three
PopupBttn:
name: 'four'
id: four
text: 'I am the fourth button'
<PopupBttn>:
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
text: txtipt.text
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: root.setText()
on_release: root.dismiss()
In order to update both buttons, you need to assign unique id to each of them. Please refer to the example below for details.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
class MainWidget(GridLayout):
btn_top = ObjectProperty()
btn_bottom = ObjectProperty()
class PopText(Popup):
pass
class MyButton(AnchorLayout):
my_btn = ObjectProperty()
class TestApp(App):
title = "Changing button text with popup text input Kivy"
def build(self):
return MainWidget()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
TextInput:
focus: True
multiline: False
on_text_validate:
app.root.btn_top.my_btn.text = self.text
app.root.btn_bottom.my_btn.text = self.text
root.dismiss()
<MyButton>:
my_btn: my_bt
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
btn_top: btn_top
btn_bottom: btn_bottom
cols: 1
rows: 2
MyButton:
id: btn_top
MyButton:
id: btn_bottom
Output

Changing Kivy Spinner values dynamically

I´m looking for a way to set the values of a Kivy Spinner depending on the text chosen from the parent spinner. The problem is that I have defined both spinners in kv language. I´m not sure if the approach would be to destroy the second spinner each time a new value is chosen from the first one, then regenerate it (if so I have no idea how to do that because of the kv code), or if there would be a way to dynamically update the "values" of the second spinner based on an on_text procedure updating the list.
In all cases the below code does not work. Any help appreciated. Thanks.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
Builder.load_string('''
<MainScreen>:
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
BoxLayout:
size_hint: 1, .9
orientation: 'vertical'
padding: 80
spacing: 20
Spinner:
id: spinner_1
text: '< Select >'
values: root.pickType
on_text: root.updateSubSpinner(spinner_1.text)
Spinner:
id: spinner_2
text: '< Select >'
values: root.pickSubType
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
size_hint: 1, .1
Button:
on_press: root.onExit()
text: 'Exit'
font_size: 50
''')
class MainScreen(FloatLayout):
def __init__(self, **kwargs):
self.buildLists()
super(MainScreen, self).__init__(**kwargs)
def buildLists(self):
self.pickType = ['Select','#1','#2','#3']
self.pickSubType = ['Select']
def updateSubSpinner(self,text):
if text == '#1':
self.pickSubType.extend('A'+'B')
elif text == '#2':
self.pickSubType.extend('P'+'Q')
else:
self.pickSubType.extend('Y'+'Z')
def onExit(self):
App.get_running_app().stop()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
You should reference the child spinner values list, not the pickSubType list, which isn't even bound to it. Example:
def updateSubSpinner(self, text):
self.ids.spinner_2.text = 'Select'
if text == '#1':
self.ids.spinner_2.values = ['A', 'B']
elif text == '#2':
self.ids.spinner_2.values = ['P', 'Q']
else:
self.ids.spinner_2.values = ['Y', 'Z']
Writing values: root.pickSubType doesn't bind the pickSubType to the values, if the first isn't a ListProperty. You could upgrade it to a ListProperty if you like, but it is not necessary.

Kivy Scrollable Label and text file, label wont update

Hy, the problem is that with the current code, which is at this point preety much nothing but a text editor, when ever I try to make a scrollable label in the kv language and call it on the main screen at the push of a button, I get no error, theres just nothing there. I should mention that the text is taken from a stored file, and the only version that works is with a regular label. This is the code, I know its a bit long but its preety easy to understand so stay with me. Any sort of input is greatly apreciated and I thank you for taking the time.
#kivy.require("1.8.0")
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Line
from kivy.uix.gridlayout import GridLayout
kv = '''
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManager:
transition: FadeTransition()
MainScreen:
AddScreen:
AppendScreen:
<ScatterTextWidget>:
orientation: 'vertical'
TextInput:
id: main_input
font_size: 14
size_hint_y: None
height: root.height - botones_layout.height
font_color: [0.1,0.3,0.9,1]
focus: True
foreground_color: [0.2,0.5,0.9,1]
cursor_color: [0,0,1,1]
BoxLayout:
id: botones_layout
orientation: 'horizontal'
height: 30
Button:
id: home_button
text: "Back Home"
Button:
id: save_button
text: "Save to file"
on_press: root.saveToFile("Archive.txt", main_input.text)
<AppendTextWidget>:
orientation: 'vertical'
TextInput:
text: root.text
id: main_input
font_size: 14
size_hint_y: None
height: root.height - botones_layout.height
font_color: [0.1,0.3,0.9,1]
focus: True
foreground_color: [0.2,0.5,0.9,1]
cursor_color: [0,0,1,1]
BoxLayout:
id: botones_layout
orientation: 'horizontal'
height: 30
Button:
id: home_button
text: "Back Home"
on_release: app.root.current = "main"
Button:
id: save_button
text: "Save"
on_press: root.append(main_input.text)
#This does not work <--- <--- <---
<ScrollableLabel>:
Label:
text: root.text
font_size: 15
text_size: self.width, None
color: [0,255,0,1]
padding_x: 20
size_hint_y: None
pos_hint: {"left":1, "top":1}
height: self.texture_size[1]
<MainScreen>:
name: "main"
FloatLayout:
# This does work
Label:
text: root.text
font_size: 15
text_size: self.width, None
color: [0,255,0,1]
padding_x: 20
size_hint_y: None
pos_hint: {"left":1, "top":1}
height: self.texture_size[1]
ActionBar:
pos_hint: {'top':1}
ActionView:
use_separator: True
ActionPrevious:
title: "Text"
with_previous: False
ActionOverflow:
ActionButton:
text: "New"
on_release: app.root.current = "add"
ActionButton:
text: "Update"
on_press: root.clicked()
ActionButton:
text: "Add"
on_release: app.root.current = "append"
<AddScreen>:
name: "add"
FloatLayout:
ScatterTextWidget
<AppendScreen>:
name: "append"
FloatLayout:
AppendTextWidget
'''
class ScatterTextWidget(BoxLayout):
def saveToFile(self,name,text):
f = open(name, "w")
f.write("\n\n\n" + " " + ">>>" + text + "test"*500)
f.close()
class AppendTextWidget(BoxLayout):
text = StringProperty("")
def append(self,text):
with open("Archive.txt", "a") as f:
f.write("\n" + " " + ">>>" + text)
f.close()
class ScrollableLabel(ScrollView):
text = StringProperty('')
pass
class MainScreen(Screen):
text = StringProperty("")
def clicked(self):
with open("Archive.txt", "r") as f:
contents = f.read()
self.text = contents
pass
class AddScreen(Screen):
pass
class AppendScreen(Screen):
pass
class MyApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
MyApp().run()
Why it works:
Your text in MainScreen is updated from file, then passed to Label and the text is loaded. ScrollableLabel.text stays unchanged.
Why it doesn't work as you expect:
There's no communication between your classes, therefore only text changed is an actual self.text = MainScreen.text
How to make it work:
Either use something on a global range, i.e. variable in your App class, then a = App.get_running_app() and access variable through a.<variable> or use __init__ to initialize your ScrollableLabel inside the MainScreen and pass it the text
So it's basically a duplicate of this and that one is a duplicate of an older unsimplified question.

Resources