### main.py ###
from kivy.app import App
class MainScreen():
def button_clicked(self):
print("*")
class KivyApp(App):
pass
KivyApp().run()
### kivy.kv ###
MainScreen:
<MainScreen#BoxLayout>:
Button:
on_press: root.button_clicked()
Why I can not call button_clicked() function from kivy?
I know that I can call it if describe like
class MainScreen(BoxLayout)
in main.py. But why I can not with above codes?
So, I am not 100% sure about my answer but here is what I think is happening...
The py file runs first and then the kv file, so if in the py file you have Class MainScreen(BoxLayout): the MainScreen class is defined and inherits from BoxLayout... Then the rules are defined in the kV file as normal. When you use the #BoxLayout in the kV file the class is created again, so this over rides the first definition of MainScreen. As the py MainScreen has been over ridden by the kV MainScreen the attribute root.button_clicked() no longer exists....
If you really need use the #BoxLayout in the kV file you could add the function to the app class:
class LayoutApp(App):
def button_clicked(self):
print("*")
And then in the kV file:
MainScreen:
<MainScreen#BoxLayout>:
Button:
on_press: app.button_clicked()
Related
I've saved both my python and kv file in the same folder but the program is unable to load it implicitly using the app name. I've tried using Builder.load_file() to load it explicitly but it results in a file not found error.
The only way it works is if i use the Builder.load_string() function but it makes the code tough to manage and also very badly organized. I'd really appreciate some help fixing this error.
maybe you can check whether your app class name match your .kv file
e.g.
.py
class MyKivyApp(App): # <-- here you should name your .kv file as mykivy , it comes from the class name "MyKivyApp" without the "App"
def build(self):
...
so the .kv file name will be mykivy.kv
or you can try kivy.lang.Builder.load_string() in .py , it just like the .kv but in the form of string ...
e.g.
.py
import kivy
from kivy.uix.widget import Widget
kivy.lang.Builder.load_string(
'''
#:kivy 2.0.0
<MyApp>
Button:
text: 'Helo World !"
size: root.size
''')
class MyApp(Widget):
pass
class mykivyApp(App):
def build(self):
main = MyApp()
return main
sth like this
When i run the file, the buttons show up. Everything looks fine, until i click button 1 for the method Main.c() to get called. the interpreter spits out an exception claiming i haven't defined it yet, when i have. I cannot find a solution anywhere.
the .py file:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.lang.builder import Builder
kivy.require("1.11.1")
class Main:
#staticmethod
def c():
print("c")
class Start(App):
def setup(self):
Builder.load_file('start.kv')
Start().run()
the .kv file:
BoxLayout:
Button:
id: btn1
text: "Button 1"
on_press: Main.c()
Button:
text: "Button 2"
on_press: print(8 * 8)
There are many problems with your code, but, one way to do what you ask for is to
Create an instance of your Main class in your App class: self.main = Main()
Call this instance from your kv code: on_press: app.main.c()
Suppose I have a ThemeManager object as a class attribute in my RootWidget like so:
class RootWidget(Widget):
theme = ThemeManager()
The ThemeManager defines a function that returns a hex color.
class ThemeManager:
def get_color(self):
return '#ffffffff'
Let's say I create a Button in my RootWidget using a kv file. How would I be able to call the ThemeManager functions from the kv file? Here's an example that doesn't work:
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.lang import Builder
class ThemeManager:
def get_color(self):
return '#ffffffff'
class RootWidget(Widget):
theme = ThemeManager()
my_kv = Builder.load_string("""
#: import get_color_from_hex kivy.utils.get_color_from_hex
RootWidget:
Button:
color: get_color_from_hex(app.root.theme.get_color())
text: "Test"
""")
class TestApp(App):
def build(self):
return my_kv
if __name__ == '__main__':
TestApp().run()
Since your question is already answered, here's a stab at the explanation, it's actually pretty simple (I think).
app.root is None at the point where your Button is trying to read the function. Because the order of things is (loosely):-
RootWidget created
Once it and all it's children are done (init completed), the object gets passed to the line in build()
app.root is only set on the call to TestApp.run()
As to why 3. happens, the init method in app.py initializes self.root as None. It can then be set by load_kv (loads a kv with the same name as this app) or by run (which is what happens most of the time).
So you can call app.root in your on_press events (because these only happen in response to user interaction, when the app is fully created) but not in one-off widget initialization events.
Interestingly enough, root is not defined as an ObjectProperty in app.py, which means you can't bind to changes in it like you can with, say, the title and icon. Not sure if it'd ever change though, so this is probably moot.
Follow up question: Kivy outside rule inherence
main.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.stacklayout import StackLayout
from kivy.properties import ObjectProperty
from kivy.factory import Factory
class FancyButton(Button):
imp = ObjectProperty(None)
class Important(StackLayout):
def NoInspiration(self, smile):
print("Received: {}".format(smile))
def AddFancy(self):
temp = Factory.FancyButton(text='f', imp = self.ids.imp)
self.ids.boxy.add_widget(temp)
class TestApp(App):
def build(self):
pass
if __name__ == '__main__':
TestApp().run()
test.kv
#:kivy 1.9.0
#:import App kivy.app
<FancyButton>:
on_release: self.imp.NoInspiration(':)')
<Important>:
id: imp
BoxLayout:
id: boxy
orientation: 'vertical'
FancyButton:
text: "smiley"
imp: root
Button:
text: "add fancy"
on_release: imp.AddFancy()
BoxLayout:
Important
In test.kv the function call in FancyButton to the function NoInspiration() works, because the FancyButton defined in the .kv has imp: root, so it knows it should look for the function in < Important >:.
Question
However how does imp: root work when you create FancyButton through add_widget in Python?
Now when I press the button "add fancy" I get the error:
File "main.py", line 18, in AddFancy
temp = Factory.FancyButton(text='f', imp = self.ids.imp)
File "properties.pyx", line 756, in kivy.properties.ObservableDict.__getattr __ (kivy/properties.c:11093)
AttributeError: 'super' object has no attribute '__getattr __'
Follow up Question
Kivy outside rule inherence 2
Widget.ids only contain ids of its children (http://kivy.org/docs/api-kivy.uix.widget.html#kivy.uix.widget.Widget.ids. Id of the widget itself it's not needed because you can just pass it directly - in your case using self, since you're passing a reference to a widget from inside of a method:
class Important(StackLayout):
def NoInspiration(self, smile):
print("Received: {}".format(smile))
def AddFancy(self):
print(self.ids) # only returns {'boxy': <weakproxy at 0000000002D119A8 to BoxLayout at 0000000002D026A8>}
self.ids.boxy.add_widget(FancyButton(text='f', imp = self)) # no need to use a factory
I'm trying to get an application I've been building to stop at a certain point, and run a cleanup procedure on stop. This seems like it should be easy, but I keep encountering an error, and I haven't been able to track down a solution.
I use kivy 1.8.0 and Python 3.3. For ease, I've modified some code from the kivy documentation, since my code is based on the same framework, and both give me exactly the same error:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# Create both screens. Please note the root.manager.current: this is how
# you can control the ScreenManager from kv. Each screen has by default a
# property manager that gives you the instance of the ScreenManager used.
Builder.load_string("""
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto settings'
on_press: root.manager.current = 'settings'
Button:
text: 'Quit'
on_press: root.exit()
<SettingsScreen>:
BoxLayout:
Button:
text: 'My settings button'
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
""")
# Declare both screens
class MenuScreen(Screen):
def exit(self):
App.stop(self)
class SettingsScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
def on_stop(self):
print('leaving now') # this is where I'd want to run the end of program procedure
if __name__ == '__main__':
TestApp().run()
When I run this and click on the Quit button, I get the following error:
builtins.KeyError: 'on_stop'
I should also note that if I comment out the on_stop function in class TestApp, the error persists. Any idea what is going wrong?
You're passing an instance of MenuScreen to App.stop(), which is causing your error as it expects an App instance. You can retrieve the running app and stop it like so:
class MenuScreen(Screen):
def exit(self):
App.get_running_app().stop()
Here is a solution for your problem:
# Declare both screens
class MenuScreen(Screen):
def exit(self):
self.manager.app.stop()
class TestApp(App):
def build(self):
# Create the screen manager
sm = ScreenManager()
# monkey patch the screen manager instance to add the app
sm.app = self
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
return sm
though I do not think this is the canonical solution for your problem, as it is here involving monkey patching.
But what's wrong is that the stop() method you want to call exists only for an app instance, though the way you were handling it, you couldn't have access to the running App instance as it was declared after the code needing it.
So the solution is to move the ScreenManager build up inside the build method. And that's where I think my solution is not a good design: I'm basically adding a member to the ScreenManager by monkeypatching the instance, to give it a link to the App instance.
There you can call the close() function, and close your application gracefully.