I got two classes that have two methods that I want to be able to access from different classes, so I inherited from App in both classes to use get_running_app with kivy properties (hope you understand what I'm saying). Now I have a problem because when I try calling App.get_running_app().method(), it only looks for the method in the first class, but the method is in the second class. I know that inheriting from App in two classes isn't really good but that was at the time the easiest way because I didn't have to call get_running_app for both classes. I had like the main class (where the def build is) and one just for main screen and I needed to call a method in main screen class. Now I have that method and the method in the main class.
So my question is, is there any way of distinguishing two classes with get_running_app()? Or could you give me some tips on how to solve this problem in different way if you have some idea?
Here is the code like you asked, just forget what this code does, I simplified it like this just to show the core problem and the error it doesn't matter what does.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.properties import NumericProperty
class GameWidget(Screen, App):
num = NumericProperty(0)
change_value = ObjectProperty(None)
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
self.num = 0
self.button = Button(text="go back", size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y':0.4},
on_press=lambda x: self.go_main_screen())
self.add_widget(self.button)
self.label = Label(text=str(self.num), size_hint=(0.2, 0.2), pos_hint={'x':0.2, 'y':0.2})
self.add_widget(self.label)
def change_value(self):
self.num += 1
self.label.text = str(self.num)
def go_main_screen(self):
self.manager.transition.direction = 'right'
self.manager.current = 'main'
class Main_screen(Screen):
def __init__(self, **kwargs):
super(Main_screen, self).__init__(**kwargs)
self.button = Button(text='click me', size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y': 0.4},
on_release=lambda x: self.call_methods())
self.button2 = Button(text='go to next screen', size_hint=(0.2, 0.2), pos_hint={'x':0.4, 'y':0.6},
on_release=lambda x: self.ch_screen())
self.add_widget(self.button)
self.add_widget(self.button2)
def ch_screen(self):
self.manager.transition.direction = 'left'
self.manager.current = 'game'
def call_methods(self):
App.get_running_app().change_value()
App.get_running_app().print_some_stuff()
class Manager(ScreenManager):
pass
class MyApp(App):
print_some_stuff = ObjectProperty(None)
def build(self):
sm = Manager()
sm.add_widget(Main_screen(name='main'))
sm.add_widget(GameWidget(name='game'))
return sm
def print_some_stuff(self):
print('you clicked')
MyApp().run()
The App.get_running_app() method will always return the same object, which should be the currently running App. Since you can only have one running App, that should always be the MyApp instance in your code. However, I believe you have uncovered a bug in the App code, because App.get_running_app() actually returns the last created App, not the currently running App. So, in your code, it will always return the GameWidget instance. That bug will not affect a correctly structured code.
The following problems/solutions appear in your code:
You should not have more than one App class in your App. Change:
class GameWidget(Screen, App):
to:
class GameWidget(Screen):
You can access Screens using the get_screen() method of the ScreenManager, which is your App root. So you can chaange:
App.get_running_app().change_value()
to:
App.get_running_app().root.get_screen('game').change_value()
Related
I have a problem when creating and using data table in kivy, this is my code structure in brief.
There are two buttons in the screen one inside the Example class and one outside the Example class
and the two buttons should execute the same function.
I need to call the method update_row_data to update the data table rows from Example() class when click any one of the two buttons.
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.button import MDRaisedButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
Builder.load_string("""
<All>:
Test:
Example:
""")
class All(Screen):
pass
class Test(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.test()
def test(self):
self.add_widget(MDRaisedButton(
text="Change 2 row",
pos_hint={"center_x": 0.5},
on_press = Example().update_row_data,
y=24,
))
class Example(BoxLayout):
data_tables = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.build_()
def build_(self):
self.layout = MDFloatLayout()
self.layout.add_widget(
MDRaisedButton(
text="Change 2 row",
pos_hint={"center_x": 0.5},
on_release=self.update_row_data,
y=24,
)
)
self.data_tables = MDDataTable(
pos_hint={"center_y": 0.5, "center_x": 0.5},
size_hint=(0.9, 0.6),
use_pagination=False,
column_data=[
("No.", dp(30)),
("Column 1", dp(40)),
("Column 2", dp(40)),
("Column 3", dp(40)),
],
row_data=[(f"{i + 1}", "1", "2", "3") for i in range(3)],
)
self.layout.add_widget(self.data_tables)
self.add_widget(self.layout)
def update_row_data(self, instance_button):
self.data_tables.row_data = [('1','1','1','1')]
print(self.data_tables.row_data)
class App(MDApp):
def build(self):
return All()
App().run()
The problem is you are creating a new instance of class Example and invoke it's method update_row_data each time you press the button. That way though it indeed does the job, you can't see it happening because it is not in your current GUI model.
The fix is to acess exactly that Example class you have added to your root. One of the many ways to do that is keeping a reference of that class (say, by id) and access that properly. Below is the corresponding implementation of this idea,
First create a reference in kvlang,
<All>:
Test:
Example:
id: example
Now in method test of class Test,
def test(self):
self.add_widget(MDRaisedButton(
text="Change 2 rows",
pos_hint={"center_x": 0.5},
on_press = self.update_row,
y=24,
))
def update_row(self, btn):
# Access that class already added to your `root`.
example_cls = self.parent.ids.example
# Now make the call.
example_cls.update_row_data(btn)
Note:
You have added both of your classes to a Screen (without specifying relative size or pos, you can check it yourself by adding canvas). Thus they will sit on top of other and may be troublesome if there are different types of widgets. You better chose ScreenManager instead.
I am building a multiple screen App with Kivy and I would like to use the ScreenManager to navigate between the multiple screens. I have seen examples and documentation for how to create the screens within a .kv file, but I want to know how to create them within the .py file.
Problem: When I create the screen subclasses as shown below, my app
window returns a blank screen.
Question: What is the correct way to
create the Screen subclasses within a .py file?
Right now I have two Screen subclasses defined: 'welcomeScreen', and 'functionScreen'. Each consists of a layout with some widgets.
kivy.require('1.9.1')
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
import kivy.uix.boxlayout
import kivy.uix.button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
# Create the screen manager and add widgets to the base sm widget
sm = kivy.uix.screenmanager.ScreenManager()
sm.add_widget(Screen(name='welcomeScreen'))
sm.add_widget(Screen(name='functionScreen'))
# sm.current= 'welcomeScreen'
return sm
class welcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(welcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = kivy.uix.boxlayout.BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = kivy.uix.button.Button(text= 'Click to continue')
welcomeBox.bind(on_press= self.callback)
welcomeBox2 = kivy.uix.button.Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(instance):
print('The button has been pressed')
sm.switch_to(Screen(name= 'functionScreen'))
# sm.current = Screen(name= 'functionScreen')
class functionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(functionScreen, self).__init__(**kwargs) #init parent
functionPage = kivy.uix.floatlayout.FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pox_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
# sm.add_widget('Name') #Add more names later when you create more screens
# OR#
# for i in ScreenDirectory:
# sm.add_widget(ScreenDirectory[i])
PanelBuilderApp().run()
if __name__ == '__main__':
pass
I understand I can add the definitions to a .kv file, and I will probably do this as the app grows. However, I like being explicit as I am learning how to use kivy.
I think you think using Screen(name='welcomeScreen') you are using welcomeScreen but that is not true, if you want to use welcomeScreen you should use it directly.
On the other hand you have typographical errors so I have corrected, I recommend you follow the kivy tutorials, obviously you must have a solid OOP base (and I think you do not have it so your task is to reinforce).
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcomeScreen'))
sm.add_widget(FunctionScreen(name='functionScreen'))
return sm
class WelcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(WelcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = Button(text= 'Click to continue', on_press=self.callback)
welcomeBox2 = Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(self, instance):
print('The button has been pressed')
self.manager.current = 'functionScreen'
class FunctionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(FunctionScreen, self).__init__(**kwargs) #init parent
functionPage = FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pos_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
if __name__ == '__main__':
PanelBuilderApp().run()
Using Kivy 1.10.0 with Python 2.7.9 I am trying to get the TextInput value entered by user when Button (my_button2) is clicked .And although I have been able to get this working with GridLayout it seems like the method I am using is not working with ScreenManager with BoxLayout . Error received is : AttributeError: 'ScreenTwo' object has no attribute 'inpt' when my_button2
After clicking 'Next Screen ' button , it takes me to page where user enters text value , and 'print' button should print it
Please see below :
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class ScreenOne(Screen):
def __init__ (self,**kwargs):
super (ScreenOne, self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_button1 = Button(text="Next Screen ",size_hint_y=None,size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen2'
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super (ScreenTwo,self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
self.add_widget(layout)
inpt = TextInput(text="Some text ",size_y=50)
layout.add_widget(inpt)
my_button2 = Button(text="Print ")
my_button2.bind(on_press=self.click)
layout.add_widget(my_button2)
Home_btn = Button(text="Back")
Home_btn.bind(on_press=self.home)
layout.add_widget(Home_btn)
def click(self,my_button2):
entered_value = self.inpt.text
print entered_value
def home(self,*args):
self.manager.current = 'screen1'
class TestApp(App):
def build(self):
my_screenmanager = ScreenManager()
screen1 = ScreenOne(name='screen1')
screen2 = ScreenTwo(name='screen2')
my_screenmanager.add_widget(screen1)
my_screenmanager.add_widget(screen2)
return my_screenmanager
if __name__ == '__main__':
TestApp().run()
second screen
When you use self you are trying to access members of the class, but in your case inpt it is not since it is a variable any, if you want to be a member of the class you must put forward self, in your case change:
inpt = TextInput(text="Some text ",size_y=50)
layout.add_widget(inpt)
to:
self.inpt = TextInput(text="Some text ",size_y=50)
layout.add_widget(self.inpt)
Note: I recommend you read OOP basics, if you are not going to have many of these problems.
I'm making a label that should update every second or so. I tried making it with the clock schedule but it doesn't seem to work. The weird is if I use a button to call the same function it works fine.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
class FirstLayout(BoxLayout):
r = 0
def __init__(self, **kwargs):
super(FirstLayout, self).__init__(**kwargs)
self.change = self.ids.temp_label
def my_callback(self, *args):
self.r += 1
print self.r
t = str(self.r)
self.change.text = t
class TryApp(App):
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1)
return FirstLayout()
app = TryApp()
app.run()
the .kv file:
<FirstLayout>:
orientation: 'vertical'
Label:
id: temp_label
text: 'something'
Button:
on_press: root.my_callback()
When I run the code I get the prints showing that the function is running but the label doesn't update. Anything wrong with my logic there?
Thank you in advance.
PS: I know there are several questions about this here, sadly those I found were replied with answers about using the Clock which I already do
The problem is that the callback is for an instance that you don't use:
def build(self):
Clock.schedule_interval(FirstLayout().my_callback, 1) #<--- FirstLayout created and never used
return FirstLayout() #this one will be used :(
Instead, You need to call the method of the FirstLayout that you are using
def build(self):
first_layout = FirstLayout() # "There should be one ..." :)
Clock.schedule_interval(first_layout.my_callback, 1)
return first_layout
Error:
TypeError: changetxt() takes no arguments but 2 given, or global name play_btn not defined.
I tried adding instance, self as well into the def args but still have the same error.
import kivy
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.lang import Builder
class launchScreenMenu(FloatLayout):
def __init__(self, **kwargs):
super(launchScreenMenu, self).__init__(**kwargs)
menuanchor = AnchorLayout(anchor_x='left',anchor_y='bottom')
menu = StackLayout(orientation='bt-lr',size_hint=(0.5,1))
about_btn = Button(text='About',size_hint=(0.3,0.1))
help_btn = Button(text='Settings',size_hint=(0.3,0.1))
settings_btn = Button(text='Help',size_hint=(0.3,0.1))
menu.add_widget(about_btn)
menu.add_widget(help_btn)
menu.add_widget(settings_btn)
menuanchor.add_widget(menu)
return self.add_widget(menuanchor)
class launchScreenBtn(AnchorLayout):
def __init__(self, **kwargs):
super(launchScreenBtn, self).__init__(**kwargs)
play_btn = Button(text="Play")
self.anchor_x = 'center'
self.anchor_y = 'center'
self.size_hint = 0.2,0.2
self.add_widget(play_btn)
play_btn.bind(on_press=self.changetxt)
def changetxt():
play_btn.text = 'Game Over'
class GameApp(App):
def build(self):
root = AnchorLayout()
root.add_widget(launchScreenMenu())
root.add_widget(launchScreenBtn())
return root
if __name__=='__main__':
GameApp().run()
All instance methods should always has self as their first argument:
def changetxt(self, *args):
self.play_btn = 'Game Over'
*args is just to be secure, in case it wasn't you who pass the argument.
Also, change all play_btn inside the class to self.play_btn:
self.play_btn = Button(text="Play")
Well, hope this helps!
The first argument of instance methods (think any function in a class, but there are exceptions) is a link to the instance itself. By convention this is called self. Reference
I don't know with 100% certainty what your second argument is. It may be play_btn since that's what's calling the function. That would be convenient since you're trying to reference that anyway, and it otherwise would be undefined.
How you want your def to read is like this:
def changetxt(self, play_btn):