Why isn't kv binding of the screen change working? - python

I've defined two buttons: one in kv and one in Python. They are located in different screens and are used to navigate between them. What I found strange is that the button that was defined in Python successfully switched the screen, while the one defined in kv did not. Perhaps I'm not accessing the App class method properly?
Here is the code of the issue:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
Builder.load_string('''
<MyScreen1>:
Button:
id: my_bt
text: "back"
on_release: app.back
''')
class MyScreen1(Screen):
pass
class TestApp(App):
def here(self, btn):
self.sm.current = "back"
def back(self, btn):
self.sm.current = "here"
def build(self):
self.sm = ScreenManager()
s1 = Screen(name = "here")
bt = Button(text = "here",
on_release = self.here)
s2 = MyScreen1(name = "back")
#s2.ids['my_bt'].bind(on_release = self.back)
self.sm.add_widget(s1)
s1.add_widget(bt)
self.sm.add_widget(s2)
return self.sm
TestApp().run()
So if I define the switching function in kv (on_release), I can't go to the "here" screen. But if I uncomment that line in Python and comment the on_release: app.back instead, everything works fine.
I'm pretty sure that this is the correct way to access the current app, since it doesn't give me any errors (which means that the method was successfully located)

That's a subtle difference between kv and python: In kv you actually have to write the callback as a function call (a python expression), in this case:
on_release: app.back(self)

Related

Insert a piece of code in the text of a Label that involves variables inside kv lang

I need to insert a piece of code in the text of a Label that involves variables inside kv lang as shown below:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.core.window import Window
Window.size=(200,200)
Builder.load_string("""
<Screen>
BoxLayout:
orientation:'vertical'
size:root.width,root.height
Label:
id:label
text:'Nothing'
Button:
id:button
text:'Insert'
on_release: root.insert_text()
""")
class Screen(Widget):
def insert_text(self):
self.ids.label.text='Something' if button.text=='Insert' else 'Nothing' #Label should say "Something" if Button's text says 'Insert'
class App(App):
def build(self):
return Screen()
if __name__=='__main__':
App().run()
When running this code I get the error: NameError: name 'button' is not defined
How can I avoid this error and have the entered code work within kv lang?
I already tried putting self.ids.button.text instead of button.text and despite not getting the error, it doesn't work inside the application either.
Edit: Also tried insert button.text as a raw string but kv lang just ignored it.
If you want Label text to change without a Button press, then you probably want to use a Property. You can reference a Property in kv and any changes to that Property will trigger evaluation where that Property is used. So, here is a modified version of your code that uses a Property named follow_changes that controls whether the Label text follows the Button text:
from kivy.app import App
from kivy.properties import BooleanProperty
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.core.window import Window
Window.size = (200, 200)
Builder.load_string("""
<Screen>
BoxLayout:
orientation:'vertical'
size:root.width,root.height
Label:
id:label
text: 'Something' if root.follow_changes and button.text == 'Insert' else 'Nothing' # use follow_changes Property
Button:
id:button
text:'Insert'
on_release:
root.follow_changes = True # turn on following_changes
Button:
text: "Change Button Text"
on_release:
button.text = 'Abba'
""")
class Screen(Widget):
follow_changes = BooleanProperty(False) # Property to control Label text
class App(App):
def build(self):
return Screen()
if __name__=='__main__':
App().run()
Since the error is name 'button' is not defined, you just need to define button. Like this:
def insert_text(self):
button = self.ids.button # define button
self.ids.label.text='Something' if button.text=='Insert' else 'Nothing' #Label should say "Something" if Button's text says 'Insert'

Python Kivy - Call methods across different class

I am new to coding, a few months with Python and trying to wrap my head around Kivy. I think there is a simple solution to this but I am struggling when it comes to calling a method in one class from another. Not even sure if this is possible, my OOP wouldn't be very strong!!
Would appreciate if someone could explain this to me! I've looked online but still struggling to understand what I need to do.
i have a simple code that had a label and 3 toggle buttons, the label text changes to show how many toggle buttons are pressed. Below is the original code.
What I am trying to do create the toggle buttons using a loop so that the number of toggle buttons can be easily altered. i have achieved this but when I try and bind the method to the toggle button the code fails with. I also tried defining a method within the "Tbtn" class to call the Main.Counter() but this didn't work either.
The line
self.bind(on_state = Main.counter())
in the init of the toggle button is where i am going wrong I think.
Any help and even an explanation would be great. Not the first time I have been stuck on this!! Thanks
Original Code:
from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Tbtn(ToggleButton):
pass
class Header_Box(BoxLayout):
pass
class Counter(BoxLayout):
pass
class Main(BoxLayout):
count = NumericProperty()
def counter(self,widget):
toggles = []
for child in self.ids.Seat_Box.children:
if isinstance(child, ToggleButton):
if child.state == 'down':
toggles.append(child.text)
self.count = len(toggles)
print(self.count)
class TestApp(App):
def build(self):
return Main()
TestApp().run()
The KV file:
<Main>:
name: "main"
BoxLayout:
orientation: "vertical"
Header_Box:
Label:
text: str(root.count)
Counter:
id: Seat_Box
Tbtn:
id: btn1
on_state: root.counter(self)
Tbtn:
id: btn2
on_state: root.counter(self)
Tbtn:
id: btn2
on_state: root.counter(self)
Code with for Loop:
from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Tbtn(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(on_state = Main().counter())
class Header_Box(BoxLayout):
pass
class Counter(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for x in range(3):
btn = Tbtn()
self.add_widget(btn)
class Main(BoxLayout):
count = NumericProperty()
def counter(self,widget):
toggles = []
for child in self.ids.Seat_Box.children:
if isinstance(child, ToggleButton):
if child.state == 'down':
toggles.append(child.text)
self.count = len(toggles)
print(self.count)
class TestApp(App):
def build(self):
return Main()
TestApp().run()
KV file:
<Main>:
name: "main"
BoxLayout:
orientation: "vertical"
Header_Box:
Label:
text: str(root.count)
Counter:
id: Seat_Box
Firstly, Remove self.bind(on_state = Main().counter()).
I suggest you to solve this in .kv side.
Way 1-.kv side:
Add this below your .kv file:
<Tbtn>:
on_state: app.get_running_app().root.counter(self)
Way 2-.py side: Add this in Tbtn class.
def on_release(self):
App.get_running_app().root.counter(self)
Although the other answer already solved your issue, the following made me post this one.
Any help and even an explanation would be great...
Technically the following line,
self.bind(on_state = Main().counter())
is wrong for various reasons. Let's try to figure this out.
The method on_state is kind of generic one not a default event (like on_press etc.). That's why bind(on_state = some_callback) won't work.
Again you did Main().counter() which actually creates a new instance of Main (which may or may not be related to the root, and here it's of course not) and assigned to its method.
It seems you want to just access one of Main widget's (which happens to be the root widget here) method.
Since you used kvlang, this could be done more efficiently as follows,
<Tbtn>:
on_state: app.root.counter()
You can find more about this in the kvlang doc.
Now in .py you just define the class along with some other changes,
class Tbtn(ToggleButton):
pass
.
.
.
class Main(BoxLayout):
count = NumericProperty()
def counter(self): # Pass no extra args as you haven't done in 'on_state' method.
toggles = []
.
.
.

How to clear textinputs by button from another page in Kivy?

My problem is probably mainly because of lack of skills but i couldnt find any similar posts. So I have textinputs on mainscreen. I need to have button in secondscreen which clear these textinputs.
I couldnt figure out how to can i call the clear_inputs method and pass textinput as arguments. I think with this clear_inputs method i could empty those textfields, but how to bind it to that button in another page?
Py.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty, BooleanProperty
class MainScreen(Screen):
pass
class SecondScreen(Screen):
def clear_inputs(self, text_inputs):
for text_input in text_inputs:
text_input.text = ''
class ScreenManagement(ScreenManager):
def changescreen(self, value):
try:
if value !='main':
self.current = value
except:
print('No Screen named'+ value)
class testiApp(App):
def build(self):
self.title = 'Hello'
testiApp().run()
KV.
ScreenManagement:
MainScreen:
name:'Main'
SecondScreen:
name:'Page2'
<MainScreen>:
name:'Main'
BoxLayout:
orientation:'vertical'
GridLayout:
cols:2
Label:
text:'testfield1'
TextInput:
id: textfield1
Label:
text:'testfield2'
TextInput:
id: textfield2
Button:
text:'Next Page'
on_release: app.root.current ='Page2'
<SecondScreen>:
name:'Page2'
Button:
text:'Clear textfields'
on_release:
The following enhancements (kv file & Python script) are required to clear the TextInput's text in another screen.
kv file
In order to access the TextInput widgets, add an id: container to the instantiated object, GridLayout:
Each screen has by default a property manager that gives you the instance of the ScreenManager used.
Bind the on_release event to method, clear_inputs() without any argument
Snippets - kv file
<MainScreen>:
name:'Main'
BoxLayout:
orientation:'vertical'
GridLayout:
id: container
...
Button:
text:'Next Page'
on_release: root.manager.current ='Page2'
<SecondScreen>:
name:'Page2'
Button:
text:'Clear textfields'
on_release: root.clear_inputs()
Py file
Add import statement, from kivy.uix.textinput import TextInput
Use ScreenManager's get_screen('Main') function to get the instantiated object, MainScreen
Use for loop to traverse the children of GridLayout: via ids.container
Use isinstance() function to check for TextInput widget
Snippets - Py file
from kivy.uix.textinput import TextInput
...
class SecondScreen(Screen):
def clear_inputs(self):
main = self.manager.get_screen('Main')
for child in reversed(main.ids.container.children):
if isinstance(child, TextInput):
child.text = ''
If I'm undestanding correctly, what you want to do is use a button in page X (Main?) to change the text in page Y (Page2?). I'm not an expert on Kivy, so there might be a better way, but here are a few thoughts:
1) I tried giving a class attribute parent to all screens, which turned out to be a bad idea because the name was already in used by Kivy. You could simply change it to parent_ or something and give it a go yourself. What you want is to pass the "parent" as a parameter to __init__ on creation:
class ScreenManagement(ScreenManager):
def __init__(self, children_, **kwargs):
# you might need to save the children too
self.children_ = children_
def add_child(self, child):
# maybe as dict
self.children_[child.name] = child
class SecondScreen(Screen):
def __init__(self, parent_, **kwargs):
super().__init__(**kwargs)
# maybe the screen manager or the main app?
self.parent_ = parent_
self.name_ = "Second"
....
def clear_inputs(self, text_inputs):
....
class MainScreen(Screen):
def __init__(self, parent_, **kwargs):
super().__init__(**kwargs)
# maybe the screen manager or the main app?
self.parent_ = parent_
# you may want to
....
# Get the appropriate screen from the parent
self.parent_.children_["Second"].clear_inputs(...)
2) I also saw another way from a youtube tutorial. Instead of running your app directly, assign it to a variable and reference that variable. This might still need tampering for advanced usecases:
# Use the global variable within your classes/methods
class Whatever:
def whatever2(self, params):
app.something()
....
app = testiApp()
app.run()

Button Text does not Update

>> BACKGROUND :
I want to update/change the text of a Button in the SecondScreen with a press of a Button in the MainScreen. Well I did some research and did what I want, and when I checked in the terminal the text did change. BUUT, the text shown on the SecondScreen did not.
>> THIS IS WHAT I DID :
((Mind you that I'm only using snippets of code for example, I'm going to post the whole code below.))
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
## on press it goes to it's root and do the "funcself" function in it
which is :
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
## it re-directs to the SecondScreen and do the "funcscreen" function
which is :
class SecondScreen(Screen):
def funcscreen(self):
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
and then I checked if I did it successfully by doing print(self.ids["button"].text), and yes!
It did change, but when I navigated to the next screen, the text shown still didn't change.
Anyone mind helping and explaining?
FULL CODE :
python file :
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
second = SecondScreen()
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
app = reproduce()
app.run()
kivy file :
<MainScreen>:
GridLayout:
rows:2
Label:
text: "PRESS TO GO TO THE NEXT PAGE"
GridLayout:
cols:2
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
Button:
text:">>>"
on_press:
app.change_screen("second")
root.manager.transition.direction = "left"
<SecondScreen>:
GridLayout:
rows:2
Label:
id:label
text: "PRESS TO CHECK AND RETURN TO PREV PAGE"
Button:
id:button
text:"TEXT BEFORE CHANGE"
on_press:
app.change_screen("first")
root.manager.transition.direction = "right"
app.check()
GridLayout:
cols: 1
ScreenManager:
id:sm
MainScreen:
id:main
name:"first"
SecondScreen:
id:second
name:"second"
Root Cause
It did not change because there are two instances of SecondScreen() i.e. one instantiated in the kv file and the other one instantiated in the App class, reproduce(). The view presented is created from the kv file and the second instance does not has a view associated to it.
Solution
There are two solutions to the problem, and remove second = SecondScreen() from the App class.
Kivy Screen ยป default property manager
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
Using get_screen()
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
Using App.get_running_app() & ids
class MainScreen(Screen):
def funcself(self):
App.get_running_app().root.ids.second.funcscreen()
Example
In the following example, there are two solutions provided but one of it is commented off.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
# App.get_running_app().root.ids.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
reproduce().run()
Output
The second attribute you define in your app class, is a new instantiation of the screen, and not really the instance you got in your screenmanager, which you add in kv. This is why when you check, you see its changed, but not on the right instance. And again when you call app.second.func, from mainscreen, again its the wrong instance.
But your app always has a root. In your case its the gridlayout. And every screen has a manager. There are a couple of ways to acces it. But you can do like this.
In your mainscreen class in kv:
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.manager.get_screen("second").ids["button"].text = "Something"
Here it gets the screenmanager, and uses its get_screen() method to get the screen named second, and then the id's of that kv rule.

Loading one line of text from file to Kivy label

I want to make a simple program that is just showing definitions that are stored in text file.One label and button to show next definition. I try to do it with documentation but i cannot find how to load text into label. Can someone show me to some good resources or code samples ?
My code for now (i want to build in on top of example from kivy website):
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text = 'Hello world')
if __name__ == '__main__':
MyApp().run()
The easiest way to update widgets in the UI are by binding to their properties. This can be done in code, but the real power of kivy in my opinion comes from using it's declarative UI language. Using kv, you get automatic binding.
Here is a quick example of what you might do:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = '''
BoxLayout:
orientation: 'vertical'
Label:
text: app.text
Button:
text: 'click me'
on_press: app.clicked()
'''
class MyApp(App):
text = StringProperty("hello world")
def build(self):
return Builder.load_string(kv)
def clicked(self):
self.text = "clicked!"
if __name__ == '__main__':
MyApp().run()
In the kv description of the UI, you tell kivy that you want the text on the Label to be bound to a StringProperty on the app which you defined on the class. The auto-binding means that anytime you set a value to that property (like in the clicked function), the UI will update with the new value automatically.

Categories