Is there a way to reference a custom widget from the py file?
I made a widget in kv, but I want to reference it from py, then add it again to another widget in kv.
I tried doing this using id but I got an error (KeyError: 'words_entry').
This is what I tried:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
import os
class GetCount(Screen):
count_input = ObjectProperty(None)
def next(self):
# Setup next screen
text_inputs = [self.ids.words_entry for i in range(int(self.count_input.text))]
for text_input in text_inputs:
self.manager.ids.get_input.ids.grid.add_widget(text_input)
# Switch to next screen
self.manager.current = "get_input"
class GetInput(Screen):
pass
kv_file = Builder.load_string("""
ScreenManager:
GetCount:
name: "get_count"
id: get_count
GetInput:
name: "get_input"
id: get_input
<WordEntry#TextInput>:
id: words_entry
multiline: False
size_hint: (self.width, None)
<GetCount>:
count_input: count_input
FloatLayout:
Label:
text: "count"
size_hint: 1, 0.05
pos_hint: {"top":0.9}
TextInput:
id: count_input
size_hint: 0.8, 0.05
pos_hint: {"top":0.7, "x":0.1}
multiline: False
Button:
text: "Next"
on_release: root.next()
size_hint: 0.8, 0.05
pos_hint: {"top":0.5, "x":0.1}
<GetInput>:
ScrollView:
GridLayout:
size_hint_y: None
height: self.minimum_height
id: grid
cols: 1
""")
class MainApp(App):
def build(self):
return kv_file
if __name__ == "__main__":
app = MainApp()
app.run()
In this code, I want to add WordEntry to the GridLayout in GetInput from py (the reason is that I need to add multiple depending on the user's input).
You can use Factory to create an instance of a class that has been defined in kv. So your GetCount class can be:
from kivy.factory import Factory
class GetCount(Screen):
count_input = ObjectProperty(None)
def next(self):
# Setup next screen
for _ in range(int(self.count_input.text)):
new_word_entry = Factory.WordEntry()
self.manager.ids.get_input.ids.grid.add_widget(new_word_entry)
# Switch to next screen
self.manager.current = "get_input"
Related
I'm coding a book reader app with kivymd. I tryied making a MDList with the avaliable chapters and a button to download in every ListItem.
This is a piece of my code:
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.list import TwoLineListItem
from kivy.lang import Builder
from kivymd.uix.list import IconRightWidget
class Library(Screen):
pass
class Book(Screen):
pass
class SM(ScreenManager):
pass
KV = '''
SM:
Library:
Book:
<Library>:
MDFloatingActionButton:
pos_hint: {"center_x": 0.5, "center_y": 0.5}
on_release:
app.root.current = "book"
app.list_chapters()
<Book>:
name: "book"
MDBoxLayout:
orientation: "vertical"
MDToolbar:
id: toolbar
ScrollView:
MDList:
id: chap
'''
class Aplication(MDApp):
def build(self):
screen = Builder.load_string(KV)
self.chaps = True
return screen
def list_chapters(self):
if self.chaps:
for chap in range(0, 11):
chapter = TwoLineListItem(text= f'Chapter {chap}')
rightIcon = IconRightWidget(icon="arrow-down-circle-outline")
self.root.get_screen("book").ids.chap.add_widget(chapter)
chapter.add_widget(rightIcon)
self.chaps = False
Aplication().run()
For some reason, all of the IconRightWidget Buttons are getting in the corner of the last TwoListItem. What should I do?
I am trying to change the text of a label using an id, I tried it with stringProperty, with objectProperty without any properties. There has to be something I am missing in my code because it simply does not work whatever I try and any help would be greatly appreciated.
This bit of code is a simple screen with 2 buttons, one for going to the other screen and one for changing the label
from kivy.app import *
from kivy.uix.button import *
from kivy.graphics import *
from kivy.uix.widget import *
from kivy.uix.label import *
from kivy.uix.floatlayout import *
from kivy.uix.boxlayout import *
from kivy.uix.relativelayout import *
from kivy.uix.scrollview import ScrollView
from kivy.properties import ListProperty, StringProperty,ObjectProperty
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
class WindowManager(ScreenManager):
pass
class Name(FloatLayout):
def __init__(self, **kwargs):
super(Name, self).__init__(**kwargs)
def changeName(self):
print(self.ids)
self.name = self.ids.nameOfSong.text
print(self.name)
self.ids.nameOfSong.text = 'name'
self.name = self.ids.nameOfSong.text
print(self.name)
class MainWindow(Screen):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
self.pos = (0, 0)
self.size = (1,1)
self.z = Name()
self.add_widget(self.z)
def swap(self):
Name().changeName()
class SecondWindow(Screen,BoxLayout):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
class langApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MainWindow(name='main'))
sm.add_widget(SecondWindow(name='second'))
return sm
Builder.load_file("kiv.kv")
if __name__ == '__main__':
langApp().run()
My kiv.kv file, most of it is not connected to the problem (I think)
#:kivy 1.11.1
WindowManager:
MainWindow:
SecondWindow:
<MainWindow>:
name: "main"
FloatLayout:
pos: 0,0
size: root.width,root.height
Button:
on_release:
root.manager.transition.direction = 'left'
app.root.current = "second"
text: 'Stop'
pos_hint: {'x':.45,'y':.1}
size_hint: .1,.1
Button:
on_press: root.swap()
text: 'Next'
pos_hint: {'x':.65,'y':.1}
size_hint: .1,.1
<SecondWindow>:
name: "second"
FloatLayout:
pos: 0,0
size: root.width,root.height
Button:
on_release:
root.manager.transition.direction = 'right'
app.root.current = "main"
text: 'Stop'
pos_hint: {'x':.45,'y':.1}
size_hint: .1,.1
<Name>:
Label:
text: nameOfSong
font_size: 20
size_hint: None, None
pos_hint: {'x': 0.435, 'y': 0.25}
A few problems with your code:
First, your code as posted dos not run. The line in your kv:
text: nameOfSong
is illegal.
Second, the code:
def swap(self):
Name().changeName()
is creating a new instance of Name and calling changeName() on that new instance. However, that new instance is not the one that is displayed in your GUI.
To fix that, you just need to call changeName() on the instance of Name that is in your GUI. Conveniently, you have saved a reference to the correct instance with the line:
self.z = Name()
So, you can change the swap() method to use that instance of Name:
def swap(self):
self.z.changeName()
The other problem is that the changeName() method tries to use a non-existent id nameOfSong. To fix that (and to make your posted code runnable), just change the <Name> rule in your kv to define that id:
<Name>:
Label:
id: nameOfSong
text: 'Some Name'
font_size: 20
size_hint: None, None
pos_hint: {'x': 0.435, 'y': 0.25}
On an unrelated note, your code is building the App GUI twice. The line:
Builder.load_file("kiv.kv")
is building the GUI from the lines:
WindowManager:
MainWindow:
SecondWindow:
and your python code is building it again here:
def build(self):
sm = ScreenManager()
sm.add_widget(MainWindow(name='main'))
sm.add_widget(SecondWindow(name='second'))
return sm
You can delete those three lines from your kv file.
I'm trying to make a program that puts x amount of TextInputs based on the user's input. I can do that in the py file, but how do I do this in the kv file? I also need to be able to reference these TextInputs later on.
This is what I have so far:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
class GetCount(Screen):
count_input = ObjectProperty(None)
def next(self):
# Setup next screen
text_inputs = [TextInput(multiline=False) for i in range(int(self.count_input.text))]
for text_input in text_inputs:
self.manager.ids.get_input.ids.grid.add_widget(text_input)
# Switch to next screen
self.manager.current = "get_input"
class GetInput(Screen):
pass
kv_file = Builder.load_string("""
ScreenManager:
GetCount:
name: "get_count"
id: get_count
GetInput:
name: "get_input"
id: get_input
<GetCount>:
count_input: count_input
FloatLayout:
Label:
text: "count"
size_hint: 1, 0.05
pos_hint: {"top":0.9}
TextInput:
id: count_input
size_hint: 0.8, 0.05
pos_hint: {"top":0.7, "x":0.1}
multiline: False
Button:
text: "Next"
on_release: root.next()
size_hint: 0.8, 0.05
pos_hint: {"top":0.5, "x":0.1}
<GetInput>:
FloatLayout:
GridLayout:
id: grid
cols: 1
""")
class MainApp(App):
def build(self):
return kv_file
if __name__ == "__main__":
app = MainApp()
app.run()
The problem is, the TextInputs are being added and created by the py code, how can I create the widget in kv and then add it in py?
Question: How do I update Label text in this scenario?
Python code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
class SearchForPart(Widget):
def searchforpart(self):
Barty = 'text update';
demogar = DemoGarage()
demogar.ids.scrollref.UpdateParam(Barty)
class ScrollableLabel1(ScrollView):
text_variable_1 = StringProperty()
def __init__(self, **kwargs):
super(ScrollableLabel1, self).__init__(**kwargs)
self.text_variable_1 = 'initial'
def UpdateParam(self, param):
self.param = param
self.text_variable_1 = param
print(param)
class AnotherScreen(Screen):
pass
class MainScreen(Screen):
pass
class DemoGarage(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("garagemainexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
Kv code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
DemoGarage:
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "demogarage"
text: "Demo Garage"
pos_hint: {"x":0.25, "top": 0.4}
<DemoGarage>:
name: "demogarage"
ScrollableLabel1:
id:scrollref
SearchForPart:
id:searchpart
<ScrollableLabel1>:
Label:
id: mylabel
text: root.text_variable_1
font_size: 20
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
padding_y: 10
padding_x: 140
<SearchForPart>:
size_hint: 1,1
FloatLayout:
Label:
text: "Search for Part:"
font_size: 30
pos_hint: {"x": 0.95, "y": 0.85}
color: 0,1,0,1
size_hint: 0.3, 0.2
TextInput:
id: partname
multiline: False
font_size: 30
pos_hint: {"x": 0.85, "y": 0.15}
color: 0,1,0,1
on_text_validate: root.searchforpart()
size_hint: 1, 0.5
After initializing program, the Label text is successfully initialized and outputs 'initial text'.
Once the 'searchforpart' method runs (as a response to hitting enter in search box), even though 'print(param)' prints 'text update', the label text isn't updated.
Question: How do I update Label text in this scenario?
Am I misunderstanding a core concept of how changes to the Kivy event loop are triggered?
Use a Kivy property:
from kivy.properties import StringProperty
class (YourClass):
text_variable_1 = StringProperty()
When you reference this property in kv, and its value changes, the gui will automatically update. If you don't use a property this doesn't happen because the kv code has no way to know the value changed.
Also, you don't have to follow pep8, but the property name should start with a lower case letter - it probably doesn't matter for your example, but can matter in kv if you try to set the property there.
I am trying to make a screen view with buttons. The problem is that the amount of buttons needed each time will change, therefore I am not able to use the kv file to make theses buttons. I am having trouble adding buttons through the normal python code. Any help is appreciated.
import kivy
import webbrowser
import MSQLHandler
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Label
from kivy.uix.widget import Widget
from kivy.uix.listview import ListItemButton
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition, CardTransition, SwapTransition, FadeTransition, WipeTransition, FallOutTransition, RiseInTransition, NoTransition
from kivy.lang import Builder
from kivy.uix.button import Button
class Login_Failed(Screen):
def changeScreen(self, next_screen):
self.manager.current = next_screen
class Loged_In(Screen):
def changeScreen(self, next_screen):
self.manager.current = next_screen
class Registers(Screen):
def changeScreen(self, next_screen):
self.manager.current = next_screen
class Login_Screen(Screen):
teacher_connect_image = ObjectProperty()
username_text_input = ObjectProperty()
password_text_input = ObjectProperty()
teacherid_text_input = ObjectProperty()
def LoginButton(self):
if self.teacherid_text_input.text == "":
Number = 0
else:
Number = int(self.teacherid_text_input.text)
MSQLHandler.LoginSystem(self.username_text_input.text, self.password_text_input.text, Number)
def changeScreen(self, next_screen):
self.manager.current = next_screen
if MSQLHandler.LoginSystem(self.username_text_input.text, self.password_text_input.text, Number) is True:
print("Returns True")
changeScreen(self, "Loged_In")
elif MSQLHandler.LoginSystem(self.username_text_input.text, self.password_text_input.text, Number) is False:
print("Returns False")
changeScreen(self, "Login_Failed")
else:
print("Null")
class ScreenManagerr(ScreenManager):
pass
class MainKivyFrameApp(App):
def build(self):
Registers().add_widget(Button(text="Helpp"))
return ScreenManagerr()
mainFrame = MainKivyFrameApp()
mainFrame.run()
If you look to where the app is being built, you will see: Registers().add_widget(Button(text="Helpp"))
This is where I have tried to add a Button to the screen of Registers. This doesn't give me any errors, but it still does not show the button.
Solution
In the kv file, add an event (on_enter or on_pre_enter) in each screen and bind it to a callback method as shown in the following snippets and example. Remember to remove the widgets that were added dynamically before leaving the current screen, by adding an event (on_leave or on_pre_leave).
Snippets
<Registers>:
on_pre_enter: self.add_buttons(n)
on_leave: self.remove_buttons()
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.properties import ObjectProperty, NumericProperty
class MyScreenManager(ScreenManager):
total_button = NumericProperty(2)
class Login(Screen):
login = ObjectProperty(None)
def on_pre_enter(self):
Window.size = (400, 300)
def check_password(self, instance, password):
if password == "pwd":
instance.current = "registers"
class Registers(Screen):
container = ObjectProperty(None)
def on_pre_enter(self):
Window.size = (800, 600)
def add_buttons(self, n):
print("Registers: n={}".format(n))
for i in range(n):
self.container.add_widget(Button(text="Button #{}".format(i), id=str(i)))
def remove_buttons(self, *args):
for child in [child for child in self.container.children]:
self.container.remove_widget(child)
class Welcome(Screen):
pass
class TestApp(App):
title = "ScreenManager - Add Widgets Dynamically"
def build(self):
return MyScreenManager()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
#:import SwapTransition kivy.uix.screenmanager.SwapTransition
<MyScreenManager>:
transition: SwapTransition()
Login:
Registers:
on_pre_enter:
self.add_buttons(app.root.total_button)
on_leave:
self.remove_buttons()
Welcome:
<Login>:
id: login
name: "login"
login: login
GridLayout:
size_hint: (0.5, 0.5)
pos_hint: {"center_x": 0.5, "center_y": 0.6}
rows: 3
padding: 20
Label:
size_hint: (0.2, 0.2)
text:"Password:"
font_size: 30
halign: "center"
valign: "middle"
TextInput:
id: password
size_hint: (0.2, 0.06)
cursor_blink: True
font_size: 20
multiline: False
password: True
Button:
text: "Continue"
size_hint: (0.2, 0.08)
on_release:
root.login.check_password(root.manager, password.text)
<Registers>:
name:'registers'
container: container
BoxLayout:
orientation: 'vertical'
Button:
text: 'Return to Login'
on_press: root.manager.current = 'login'
Button:
text: 'Next Screen'
on_press: root.manager.current = 'welcome'
BoxLayout:
id: container
orientation: 'vertical'
<Welcome>:
name:'welcome'
BoxLayout:
Label:
text: 'Welcome!'
Button:
text: 'Return to Registers'
on_press:
app.root.total_button = 5
root.manager.current = 'registers'
Output