Can the kivy language access inherited layouts and widgets? I want to create one basic BoxLayout that contains the styling and title Label for my widget. I want to be able to inherit from this widget and add additional widgets in different positions.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<SimpleBar>:
canvas.before:
Color:
rgba: 0, 0.5, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
id: my_layout
Label:
text: "hi"
<NewBar>:
Label:
text: "2"
''')
class SimpleBar(BoxLayout):
def log(self, value):
print(value)
class NewBar(SimpleBar):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(dir(self))
class GeneralApp(App):
def build(self):
return NewBar()
if __name__ == '__main__':
GeneralApp().run()
Above is my basic running widget.
I want NewBar's "2" Label to be located before SimpleBar's 'hi' Label like below.
<NewBar>:
BoxLayout:
id: my_layout
Label:
text: "2"
Label:
text: "hi"
I know that - can negate items. However, <-NewBar> removes all of my styling.
Is there any way to do this in the kivy language?
Here's a fun thing: you don't need to specify all classes used in kv lang in the lang itself - you can also add them using Factory.register method later in code. Here's an example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory
from functools import partial
Builder.load_string('''
<MyWidget>:
Foo
Bar
''')
class MyWidget(BoxLayout):
pass
class MyApp(App):
def build(self):
Factory.register('Foo', cls=partial(Label, text='foo'))
Factory.register('Bar', cls=partial(Label, text='bar'))
return MyWidget()
if __name__ == '__main__':
MyApp().run()
Let's use it to create a template base widget we later fill with various content. We use a placeholder that we later replace with another widget:
<BaseWidget>:
orientation: 'vertical'
Label:
size_hint: None, 0.1
text: 'title'
Placeholder
In the Python code we register a placeholder class in the __init__ method of this base template class.
class BaseWidget(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(BaseWidget, self).__init__(**args)
Now let's define a content class.
<TwoButtonWidget>:
Button:
text: 'button 1'
Button:
text: 'button 2'
And finally create a customized class that use our base class as a template and replaces its placeholder with a content class. This class don't have its own kivy rules (these are moved into content class) so when we inherite from our base template, no extra widgets are inserted.
# content class
class TwoButtonWidget(BoxLayout):
pass
# Base class subclass
class CustomizedWidget(BaseWidget):
placeholder = TwoButtonWidget # set contetnt class
A full example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.factory import Factory
Builder.load_string('''
<BaseWidget>:
orientation: 'vertical'
widget_title: widget_title
placeholder: placeholder
Label:
size_hint: None, 0.1
id: widget_title
Placeholder
id: placeholder
<TwoButtonWidget>:
button1: button1
Button:
text: 'button 1'
id: button1
Button:
text: 'button 2'
<ThreeButtonWidget>:
orientation: 'vertical'
Button:
text: 'button a'
Button:
text: 'button b'
Button:
text: 'button c'
''')
class BaseWidget(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(BaseWidget, self).__init__(**args)
class TwoButtonWidget(BoxLayout):
pass
class ThreeButtonWidget(BoxLayout):
pass
class CustomizedWidget1(BaseWidget):
placeholder = TwoButtonWidget
class CustomizedWidget2(BaseWidget):
placeholder = ThreeButtonWidget
class MyApp(App):
def build(self):
layout = BoxLayout()
c1 = CustomizedWidget1()
# we can access base widget...
c1.widget_title.text = 'First'
# we can access placeholder
c1.placeholder.button1.text = 'This was 1 before'
c2 = CustomizedWidget2()
c2.widget_title.text = 'Second'
layout.add_widget(c1)
layout.add_widget(c2)
return layout
if __name__ == '__main__':
MyApp().run()
You can easily extend it and for example, have multiple placeholders.
Applying this to your case:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory
from functools import partial
Builder.load_string('''
<SimpleBar>:
canvas.before:
Color:
rgba: 0, 0.5, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
Placeholder
Label:
text: "hi"
<NewBarContent>:
Label:
text: "2"
''')
class SimpleBar(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(SimpleBar, self).__init__(**args)
class NewBarContent(BoxLayout):
pass
class NewBar(SimpleBar):
placeholder = NewBarContent
class MyApp(App):
def build(self):
return NewBar()
if __name__ == '__main__':
MyApp().run()
With simple kv no, because if you put something in the widget (e.g. Label: ...), it'll call <widget>.add_widget() method and when such a method is called without additional parameters, it will by default place the widget after whatever was already placed before it. Therefore you can either search through the kivy/lang/parser.py file and add such a functionality(PR welcome), or do it within python in hm... __init__ or wherever you'll like to add the widget (maybe after some event).
To do that within python you can call <widget (or self)>.add_widget(<child>, index=<where>) according to the docs. For example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty
Builder.load_string('''
#:import Factory kivy.factory.Factory
<Ninja#Label>:
<SimpleBar>:
BoxLayout:
id: my_layout
Label:
text: "hi"
<ChildWithBenefits>:
placebefore:
[(Factory.Label(text='I am the first!'), 0),
(Factory.Ninja(text='No, I am!'), 2)]
''')
class SimpleBar(BoxLayout):
def log(self, value):
print(value)
class ChildWithBenefits(SimpleBar):
placebefore = ListProperty([])
def __init__(self, *args, **kwargs):
super(ChildWithBenefits, self).__init__(*args, **kwargs)
for child, index in self.placebefore:
print child.text
print type(child)
self.add_widget(child, index=index)
self.log('Hello!')
class GeneralApp(App):
def build(self):
return ChildWithBenefits()
if __name__ == '__main__':
GeneralApp().run()
If you want to do a composite widget, that accept new children and add them in one specific "container" widget, you have to do some python.
Basically the idea is to override add_widget so that once the basic structure of the widget is there, the new widgets are added using the new method.
Let's say you have this NestingWidget
class NestingWidget(BoxLayout):
title = StringProperty()
def activate(self):
# do something
pass
with this rule
<NestingWidget>:
Label:
text: root.title
BoxLayout:
Button:
on_press: root.activate()
and you want to use it like this:
FloatLayout:
NestingWidget:
title: 'first item'
Image:
source: '../examples/demo/pictures/images/Ill1.jpg'
this won't work immediatly, because the Image will be added as a direct child of NestingWidget, so it'll be under the Button.
Howether, you probably remarked that some widgets in kivy can accept new nested widgets, while being already complex on their own.
The trick to do that is — as said before — to override add_widget.
First, let's add an id to our container.
<NestingWidget>:
Label:
text: root.title
BoxLayout:
id: container
Button:
on_press: root.activate()
then, let's use it in add_widget.
class NestingWidget(BoxLayout):
…
def add_widget(self, *args, **kwargs):
if 'container' in self.ids:
return self.ids.container.add_widget(*args, **kwargs)
else:
return super(NestingWidget, self).add_widget(*args, **kwargs)
Related
I want to make a variable that can be modified by one screen, then the variable can change the text of different screen.
Here is the python file
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.properties import NumericProperty
import random
value = NumericProperty(0)
class MainWindow(Screen):
def vval(self, root):
root.value = 1
def vvval(self, root):
root.value = 2
class SecondWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("testing.kv")
class testing(App):
def build(self):
return kv
if __name__ == "__main__":
testing().run()
and here is the kivy file
WindowManager:
MainWindow:
SecondWindow:
<MainWindow>:
name: "main"
GridLayout:
size: root.width, root.height
rows: 2
Button:
text: "print 1"
on_release:
root.vval(root)
app.root.current = "second"
Button:
text: "print 2"
on_release:
root.vvval(root)
app.root.current = "second"
<SecondWindow>:
name: "second"
Label:
text: "successfully printed ", root.value
What I expected to happens is when I click one of the button in the the MainWindow, the variable, which I named it "value" will be modified to 1 or 2 depending on what button i click, then the screen changed to the SecondWindow and display the text "successfully printed x", the value of x is depends to the "value" variable.
I'm still new on kivy, if there is some error or ambiguity, I am sorry. Please share me your knowledge about this, it will be appreciated.
Generally, one would do this with a StringProperty object. The code below shows a working example, although I did take liberties in restructuring some things about your code. I admit I had some challenges with .bind of the StringProperty which I ended up resolving by building the ScreenManager object in the Python code instead of the .kv lines.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import NumericProperty, StringProperty
value = NumericProperty(0)
Builder.load_string('''
<MainWindow>
name: "main"
GridLayout:
size: root.width, root.height
rows: 2
Button:
text: "print 1"
on_release:
# arguments can be passed, including a 'self' of the button object
app.kv_button("1", "second", self)
# app.root.current = "second"
Button:
text: "print 2"
on_release:
# arguments can be passed, including a 'self' of the button object
app.kv_button("2", "second", self)
# app.root.current = "second"
<SecondWindow>
name: "second"
Label:
# maps to a StringProperty, root. would be the widget, this is app. which is the App()
text: app.result_text
''')
class MainWindow(Screen):
pass
class SecondWindow(Screen):
value: str
pass
class WindowManager(ScreenManager):
pass
# Builder.load_file("testing.kv")
class testing(App):
result_text = StringProperty()
def kv_button(self, _value: str, next_screen: str, *args):
print(f"{self} {_value} {args}")
self.result_text = _value
# change the screen using the WindowManager object that has been kept as a reference
self._main.current=next_screen
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._main = WindowManager()
self._main.add_widget(MainWindow())
self._main.add_widget(SecondWindow())
def build(self):
return self._main
if __name__ == "__main__":
testing().run()
I would like to have code in KV and PY files that will display a YES/NO popup that I can instantiate anywhere in my app to pop the question.
Will a good kindly person help me to convert code I found on internet to KV file and PY file content.
I would like to be able to create an instance of this popup at any point in my app for display.
This is the sample I am working from:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
def __init__(self,**kwargs):
self.register_event_type('on_answer')
super(ConfirmPopup,self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480,400),
auto_dismiss= False)
self.popup.open()
def _on_answer(self, instance, answer):
print "USER ANSWER: " , repr(answer)
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
I have tried the following:
KV file:
<YesNoPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
and PY file:
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
class YesNoPopup(GridLayout):
text = StringProperty()
def __init__(self, **kwargs):
self.register_event_type('on_answer')
super(YesNoPopup, self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title='PIN',
content=content,
size_hint=(0.6, 0.4),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print
"USER ANSWER: ", repr(answer)
self.dismiss()
Then in the App
def pin_action(self):
pop = PopupTest()
pop.build()
But this only shows popup with Title and separator bar.
I will appreciate explanations of why you do what you do so that I as an 82 year old can learn and understand since my OO education is 22 years old and my Python Kivy is still in the learning stage.
Thanks a million
You can simplify your code by just calling the _on_answer() method directly from the on_release: of the Button. Here is a modified version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: app._on_answer(root, 'yes')
Button:
text: 'No'
on_release: app._on_answer(root, 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480, 400),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print("USER ANSWER: ", repr(answer))
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
Now that I understand your question, the main problem is just that the bind() method creates a WeakMethod for the bound method. In your code:
def pin_action(self):
pop = PopupTest()
pop.build()
An instance of PopupTest is created (which contains the bound _on_answer() method). But after the build() method of PopupTest is executed, the Popup is opened and the pop variable gets garbage collected. So the _on_answer() method is also garbage collected as a result. Since bind() creates a WeakMethod, there is no method left for the dispatched event to trigger.
There are two ways (that I know of) to fix this issue. One is to just modify the pin_action() method to retain a reference to PopTest so that is does not get garbage collected:
def pin_action(self):
self.pop = PopupTest()
self.pop.build()
Another way is to use fbind() instead of bind(), since fbind() keeps a reference to the bound method, so it cannot be garbage collected:
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
# content.bind(on_answer=self._on_answer)
content.fbind('on_answer', self._on_answer)
In below code I need the button on the edit_button_tab to switch to edit_input_tab. I really need to switch it that way as I need to switch between predefined classes EditButton and EditInput. This is a part of a bigger program with few Buttons in different location of a layout and I cant define them within <MainTabbedPanel> class. I've tried many ways to call switch_to (example in the quotes) but they didn't work.
CODE
from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelStrip
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.factory import Factory
theRoot = """
#:import Factory kivy.factory.Factory
<EditButton>
orientation: 'vertical'
Button:
text: 'Switch to Edit Screen'
on_press: root.change_tab('edit_screen')
<EditInput>
orientation: 'vertical'
TextInput:
<UnCloseableHeader>
color: 0,0,0,1
disabled_color: self.color
# variable tab_width
text: 'tabx'
size_hint_x: None
width: self.texture_size[0] + 40
BoxLayout:
pos: root.pos
size_hint: None, None
size_y: 20
padding: 3
Label:
id: lbl
text: root.text
<MainTabbedPanel#BoxLayout>
size_hint: (1, 1)
default_tab: edit_button_tab
tab_width: 130
FloatLayout:
EditButton:
id: edit_button
EditInput:
id: edit_input
UnCloseableHeader:
id: edit_button_tab
text: 'Edit'
content: edit_button.__self__
UnCloseableHeader:
id: edit_input_tab
text: 'Edit Tab'
content: edit_input.__self__
MainTabbedPanel:
"""
class EditInput(BoxLayout):
def __init__(self, **kwargs):
super(EditInput, self).__init__(**kwargs)
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
def switch(self, tab):
print("SWITCH TO", tab, self.ids.keys())
self.switch_to(self.ids[tab])
class UnCloseableHeader(TabbedPanelHeader):
pass
Factory.register('UnCloseableHeader', cls=UnCloseableHeader)
sm = Builder.load_string(theRoot)
class TabbedPanelApp(App):
def build(self):
return sm
if __name__ == '__main__':
TabbedPanelApp().run()
EDIT
I've tried with below snippet. It prints IDS of MainTabbedPanel but does not change the tabs.
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
MainTabbedPanel.tab = tab
MainTabbedPanel()
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
tab = ''
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
self.tabs_showing = True
if self.tab != '':
Clock.schedule_once(self.switch)
def switch(self, dt):
print("SWITCH TO", self.tab, self.ids.keys())
self.switch_to(self.ids[self.tab])
Use App.get_running_app() to get an instance of your app
Use root to get an instance of your root
Snippets
def change_tab(self, tab):
print('TAB', tab)
mtp = App.get_running_app().root
mtp.switch_to(mtp.ids.edit_input_tab)
Notes
In your kv, you defined a Dynamic class,
<MainTabbedPanel#BoxLayout>. It should be a class rule,
<MainTabbedPanel> because in your Python code, you have defined
class MainTabbedPanel(TabbedPanel): i.e. inheritance mismatch and
class type mismatch.
This is a sample program to display "Hello" or "Good-bye" when you push the button 1 or 2, simplified from my real program.
What I want to do is to execute function btn1() and display "Hello" when the program starts, without pushing the button 1.
What lines of code and where should I add?
When I put "Greetings().btn1()" in class Greetings, error: "name Greetings is not defined" occured. Then I put the same command after def build(self) in class GreetingApp, but nothing happened.
In main.py,
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
class Greetings(BoxLayout):
greeting=StringProperty()
def btn1(self):
self.greeting='Hello.'
def btn2(self):
self.greeting='Good-bye.'
class GreetingApp(App):
def build(self):
return Greetings()
GreetingApp().run()
And in greeting.kv,
#: import main main
Greetings:
<Greetings>:
orientation: "vertical"
Label:
text: root.greeting
BoxLayout:
orientation: "horizontal"
Button:
text: '1'
on_press: root.btn1()
Button:
text: '2'
on_press: root.btn2()
Just call btn1 before returning root widget:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.lang import Builder
Builder.load_string('''
<Greetings>:
orientation: "vertical"
Label:
text: root.greeting
BoxLayout:
orientation: "horizontal"
Button:
text: '1'
on_press: root.btn1()
Button:
text: '2'
on_press: root.btn2()
''')
class Greetings(BoxLayout):
greeting = StringProperty()
def btn1(self):
self.greeting = 'Hello.'
def btn2(self):
self.greeting = 'Good-bye.'
class GreetingApp(App):
def build(self):
root = Greetings()
root.btn1()
return root
GreetingApp().run()
The __init__ method runs every time you create an instance of a class.
So what ever you want to do in creation time of the object, you can put in __init__. Initiate the object.
Put this in your Greetings class.
def __init__(self, **kwargs):
super(Greetings, self).__init__(**kwargs)
self.btn1()
Then you call it when you create the object.
I am trying to access title property of AccordionItem in python code, that is being set in kv file. The property is empty string even though it is correctly displayed in GUI.
Is there a way to access value of this property or am I missing something here?
kv file
<TableModifications>:
accordion: accordion
MDAccordion:
id: accordion
orientation: 'vertical'
size_hint_x: None
width: '240dp'
TableAccordionItem:
title:'Add' # I want to access this in python code
icon: 'plus'
TableAccordionItem:
title:'Edit'
icon: 'pencil'
TableAccordionItem:
title:'Remove'
icon: 'delete'
python code
class TableModifications(BoxLayout):
pass
class TableAccordionItem(MDAccordionItem):
def __init__(self, **kwargs):
super(TableAccordionItem, self).__init__(**kwargs)
print(self.title) # this is empty, why?
def add_widget(self, widget, *args):
super(TableAccordionItem, self).add_widget(widget, *args)
print(self.title) # this is empty, why?
You need to use kivy's ObjectProperty that will magically bind to widgets in your kv. Also, if a widget has already been added to your widget tree in kv lang, then you don't need to add that widget again in python. add_widget is not a method you need to override. If you want to add a widget you do something like: self.add_widget(Label(text="Hello")).
Example:
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.app import App
from kivy.lang import Builder
Builder.load_string("""
<RootWidget>:
accord_1: accord_1
accord_2: accord_2
accord_3: accord_3
Accordion:
orientation: "vertical"
AccordionItem:
id: accord_1
title: "Title 1"
Label:
text: "Accordion One Content."
AccordionItem:
id: accord_2
title: "Title 2"
Label:
text: "Accordion Two Content."
AccordionItem:
id: accord_3
title: "Title 3"
""")
class RootWidget(BoxLayout):
accord_1 = ObjectProperty(None)
accord_2 = ObjectProperty(None)
accord_3 = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(RootWidget, self).__init__(*args, **kwargs)
self.accord_3.add_widget(Label(text="Accordion Three Content."))
for ac in [self.accord_1, self.accord_2, self.accord_3]:
print ac.title
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()