I have made a random workout generator and i would like there to be a home button on the final screen so that users can go back to the first screen and generate a new workout (my attempts so far have ended up with a second workout overlapping the first or a completely blank screen after using root.clear_widgets).Some widgets are added in the .py file and some from .kv and i never initialised my screen classes, new to kivy/python so not sure if this will affect potential solutions... Would appreciate any help! I've included my app class which has a currently empty home method called from a button in my .kv file.
kv = Builder.load_file("my.kv")
class MyMainApp(App):
sm = WindowManager()
def home(self):
pass
def build(self):
Window.clearcolor = (1, 1, 1, 1)
return kv
if __name__ == "__main__":
MyMainApp().run()
I think what you are looking for is the ScreenManager
https://kivy.org/doc/stable/api-kivy.uix.screenmanager.html
Related
for example... lets say i have alot of products.
i want to be able to have the program know a certain products was clicked... easy enough
but then i want the program to create a screen based off of a template
basically on the fly.
thanks for the help
this is what i have tried so far.
def Create_New_Product_Screen(self,question,Screen_Name):
print(self,Screen_Name+'Made it here')
sm = ScreenManager()
screen = Screen(name=Screen_Name)
sm.add_widget(screen)
#sm.current = Screen_Name
app = MDApp.get_running_app()
app.change_screen(Screen_Name)
def Create_New_Screen(self,Screen_Name):
app = MDApp.get_running_app()
sm = app.root.ids['screen_manager']
screen = Screen(name=Screen_Name)
sm.add_widget(screen)
sm.current = Screen_Name
Now i just need to figure out how to make templates for the screens
but this worked... i had to find the screen manager in the kivy file
and then call it and create the screen then change to it.
I have made an easy app where i try to show my issue. When using python to draw a line in kivy (using the with self.canvas method) the line gets drawn from a center_x and center_y of 50.
Using kivy Lang draws the line correctly in the center. Here is a simple code example:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def draw(self):
with self.canvas:
Line(points=[self.center_x,self.center_y,self.center_x,self.center_y+100], width=2)
class TestApp(App):
def build(self):
test = TestWidget()
test.draw()
return test
if __name__ == '__main__':
TestApp().run()
and the corresponding test.kv file:
#:kivy 1.11.1
<TestWidget>:
canvas:
Line:
width: 5
points: (self.center_x,self.center_y,self.center_x,self.center_y+100)
result is like this:
Any idea why using python code is not working?
Both codes are not equivalent, in the case of python you are only establishing that the position of the line is based on the center of the widget at that time, that is, at the beginning, instead in .kv it is indicating that the line position is always it will be with respect to the center of the Widget.
TL; DR;
Explanation:
In kv the binding is a natural and implicit process that in python is not doing it, besides that its implementation is simpler, for example the following code:
Foo:
property_i: other_object.property_j
In that case its equivalent in python is:
foo = ...
other_object = ...
other_object.bind(property_j=foo.setter("property_i"))
And it is not:
foo.property_i = other_object.property_j
Since with the bind you are indicating before a change of property_j update property_i with that value, but in the last code you only indicate at this moment copy the value of property_j in property_i.
In your particular case, the center you take is before displaying the widget and kivy taking default configurations and other properties into consideration, changes the geometry of the widget after it is displayed.
Solution
Making the necessary modifications taking into account the previous explanation the following code is an equivalent of the .kv code
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def __init__(self, **kwargs):
super(TestWidget, self).__init__(**kwargs)
with self.canvas:
self.line = Line(width=2)
self.bind(center=lambda *args: self.draw())
def draw(self):
self.line.points = [
self.center_x,
self.center_y,
self.center_x,
self.center_y + 100,
]
class TestApp(App):
def build(self):
test = TestWidget()
return test
if __name__ == "__main__":
TestApp().run()
With kv language your declarations automatically update when their dependencies change. That means that when the widget's centre goes from (50, 50) to something else, the line is redrawn in the new location.
Your Python code doesn't do this, it just draws the line once using whatever its centre is at the moment the code runs. That value is (50, 50), since that's the default before positioning has taken place.
The solution is to write code in Python that updates the line when the widget centre changes, something along the lines of declaring the line with self.line = Line(...) and self.bind(centre=some_function_that_updates_self_dot_line).
I am trying to initialize some values of widgets in Kivy on program start, with no success. Simplified Python code is:
inital_text = "init text"
class MainApp(App):
def initialize_widgets(self):
self.root.ids.my_label.text = initial_text
if __name__ == '__main__':
MainApp().run()
MainApp.initialize_widgets(App)
And the relevant piece from kv file is:
Label:
id: my_label
text: "default text"
When I run the program, the label says "default text". When quitting it, I get the error:
line 5, in initialize_widgets
self.root.ids.my_label.text = initial_text
AttributeError: type object 'App' has no attribute 'root'
I have tried other workarounds, some quite desperate ones, with no success.
Use on_start event to initialize the label's text on program start. In the example below, a Clock.schedule_once event was added to show the initial value and then the change.
Note
In this example, the root widget is Label widget (root) and the root's dictionary (root.ids) is empty. If there is another widget with id: my_textinput, added as children of the root then root's dictionary will contain one id i.e. my_textinput. A print(self.root.ids) will demonstrate this.
self - The keyword self references the “current widget instance” i.e. App.
Kivy Language » ids
When the kv file is processed, weakrefs to all the widgets tagged with ids are added to the root widget’s ids dictionary.
Snippets
from kivy.app import App
initial_text = "init text"
class MainApp(App):
def on_start(self):
self.root.text = initial_text
if __name__ == '__main__':
MainApp().run()
Example
main.py
from kivy.app import App
from kivy.clock import Clock
initial_text = "init text"
class MainApp(App):
def on_start(self):
Clock.schedule_once(self.initialize_widgets, 5)
def initialize_widgets(self, dt):
self.root.text = initial_text
if __name__ == '__main__':
MainApp().run()
Output
I have a button which was made in python file and I have been trying to make it so it changes screens from one to another.
def callback(instance):
print("Test 1")
sm = ScreenManager()
sm.add_widget(ScreenTwo(name="ScreenTwo"))
print("Test2")
class ScreenOne(Screen):
def on_enter(self):
self.add_widget(ImageURLButton(source=icon2, size=(100,100), size_hint=(0.1, 0.1), on_press=callback, pos_hint={"x":0.90, "top":1.0}))
class ScreenTwo(Screen):
pass
class ScreenManagement(ScreenManager):
pass
When I do click the button all it does it prints "Test1" and "Test2" without it changing the screen. Sorry if this is really obvious to others but I do not know how to fix it, could anyone help me please?
Would be better if you posted a MCVE, but I made one myself. Here is how it can be done:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
def callbackTo2(instance):
sm.current="ScreenTwo"
def callbackTo1(instance):
sm.current="ScreenOne"
class ScreenOne(Screen):
def __init__(self, *args):
super(ScreenOne, self).__init__(name='ScreenOne')
self.add_widget(Button(text='Switch To Screen Two', size_hint=(0.1, 0.1), on_press=callbackTo2, pos_hint={"x":0.90, "top":1.0}))
class ScreenTwo(Screen):
def __init__(self, *args):
super(ScreenTwo, self).__init__(name='ScreenTwo')
self.add_widget(Button(text='Switch To Screen One', size_hint=(0.1, 0.1), on_press=callbackTo1, pos_hint={"x":0.90, "top":1.0}))
sm = ScreenManager()
class ScreenPlayApp(App):
def build(self):
sm.add_widget(ScreenOne())
sm.add_widget(ScreenTwo())
return sm
if __name__ == '__main__':
app = ScreenPlayApp()
app.run()
Note that there is only one ScreenManager instance, all the screens can be added to the ScreenManager initially, and screens are switched by using sm.current=. Also, you can build your Screen in its __init__() method. Using the on_enter causes the members of the screen to be rebuilt each time the screen is displayed. Also, you cannot use both 'size' and 'size_hint' for the same widget unless you are setting 'size_hint' to 'None'.
I'm working through the Kivy tutorial, programming guide, and find the following code is not actually printing the button position anywhere, as far as I can tell---that is, the btn_pressed() method doesn't seem to do anything.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(Button(text='btn 1'))
cb = CustomBtn()
cb.bind(pressed=self.btn_pressed)
self.add_widget(cb)
self.add_widget(Button(text='btn 2'))
def btn_pressed(self, instance, pos):
print ('pos: printed from root widget: {pos}'.format(pos=pos))
class CustomBtn(Widget):
pressed = ListProperty([0, 0])
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.pressed = touch.pos
# we consumed the touch. return False here to propagate
# the touch further to the children.
return True
return super(CustomBtn, self).on_touch_down(touch)
def on_pressed(self, instance, pos):
print ('pressed at {pos}'.format(pos=pos))
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
Does anyone have any hints or ideas why this isn't working? Is this the intended behavior and I missed something or is there an error in the tutorial?
Specifically, while the instructions above produce buttons that can be clicked and flash---there doesn't seem to be any output corresponding to the method:
def btn_pressed(self, instance, pos):
print ('pos: printed from root widget: {pos}'.format(pos=pos))
Maybe it's printing black on black?
The seemingly blank, unlabeled spot in the middle is the button that accepts location and prints location to the console. I was clicking on the buttons labeled "btn" and didn't notice this place existed.
This part of the tutorial is demonstrating how you can make custom buttons that do stuff precisely like this. It would be more clear if this were labeled, but that should be doable by looking at the API.
Anyway, the code is working as expected.