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)
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()
My question is the title. It is probably the same as this one, but I am not happy with the answer there or with my working minimal example below. That is because if I were to move the child I am making the call from one "nested layer" up or down (e.g. in my example: put the "special button" inside another Layout of the parent BoxLayout) the callback function would not work anymore.
How to do this properly (and the intended kivy way)?
Minimal Example:
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
Builder.load_string('''
<RootWidget>:
button1: b1
button2: b2
BoxLayout:
orientation: 'horizontal'
size: root.size
Button:
id: b1
text: 'I am the normal one'
SpecialButton:
id: b2
text: 'I am the special one'
<SpecialButton>:
on_release: root.clickityclick()
''')
class RootWidget(Widget):
button1 = ObjectProperty(None)
button2 = ObjectProperty(None)
class SpecialButton(Button):
def clickityclick(self):
self.parent.parent.button1.disabled = True ### <----- How to do this properly?
class MinimalExampleApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MinimalExampleApp().run()
#inclement's comment contains the answer to my question (specifically: 2nd-last example from his article ("Option 1"))
Option 1: Introduce a kivy-property in the widget from which you want to induce the change. In the kv rules set a property of the receiving widget to the one you introduced in the inducing widget (via the kv-id of the iducing widget). This way you could either have the receiving widget track a property directly or trigger a function when the property changes.
Option 2: Use App.get_running_app().root.ids ("in Python") to talk to the receiving widget directly.
Example for Option 1:
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, BooleanProperty
Builder.load_string('''
<RootWidget>:
button1: b1
button2: b2
BoxLayout:
orientation: 'horizontal'
size: root.size
Button:
id: b1
text: 'I am the normal one'
disabled: b2.switch # <----- track the property of the other ('special') button
SpecialButton:
id: b2
text: 'I am the special one'
<SpecialButton>:
on_release: root.clickityclick()
''')
class RootWidget(Widget):
button1 = ObjectProperty(None)
button2 = ObjectProperty(None)
class SpecialButton(Button):
switch = BooleanProperty(False) # <----- introduce a property which is then tracked by the other ('normal') button
def clickityclick(self):
self.switch = not self.switch # <----- only change your own property
class MinimalExampleApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MinimalExampleApp().run()
Example for Option 2:
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
Builder.load_string('''
<RootWidget>:
button1: b1
button2: b2
BoxLayout:
orientation: 'horizontal'
size: root.size
Button:
id: b1
text: 'I am the normal one'
SpecialButton:
id: b2
text: 'I am the special one'
<SpecialButton>:
on_release: root.clickityclick()
''')
class RootWidget(Widget):
button1 = ObjectProperty(None)
button2 = ObjectProperty(None)
class SpecialButton(Button):
def clickityclick(self):
App.get_running_app().root.ids.b1.disabled = \
not App.get_running_app().root.ids.b1.disabled # <---- directly access the other ('normal') button
class MinimalExampleApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MinimalExampleApp().run()
A beginner of python/kivy coding. I'm trying to make a question/answer- type program.
Attached code shows a simplified example of my issue. It shows text input dialog and one button dialog alternately by remove_widget/add_widget.
My issue is, at first, text input dialog has focus in text input, but next time it appears, it loses its focus, despite stating self.gridlayout.txtinput.focus = True. Any idea how I can keep its focus?
I tried adding delay time, also tried adding txtinput.focus description in AnswerChoiceScreen's on_enter, but none of them worked.
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
sm = ScreenManager()
class QALayout1(BoxLayout):
pass
class QALayout2(BoxLayout):
pass
class AnswerChoiceScreen(Screen):
def __init__(self, **kwargs):
super(AnswerChoiceScreen, self).__init__(**kwargs)
self.gridlayout = None
self.gridlayout = QALayout2()
self.add_widget(self.gridlayout)
def _create_layout(self):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.add_widget(self.gridlayout)
def button1_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = QALayout2()
self.add_widget(self.gridlayout)
self.gridlayout.txtinput.focus = True
def buttonOK_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = QALayout1()
self.add_widget(self.gridlayout)
class myApp(App):
def build(self):
self.anschoi = AnswerChoiceScreen(name = 'anschoi')
sm.add_widget(self.anschoi)
sm.current = 'anschoi'
return sm
if __name__ == '__main__':
myApp().run()
my.kv
<AnswerChoiceScreen>:
BoxLayout:
orientation: 'vertical'
padding: 10,40,10,40
spacing: 40
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_press: root.parent.button1_clicked()
<QALayout2>:
txtinput: txtinput
orientation: 'vertical'
TextInput:
id: txtinput
text: ''
multiline: False
focus: True
ButtonOK:
id:ButtonOK
text: 'OK'
on_press: root.parent.buttonOK_clicked()
<Button0#Button>:
<Button1#Button>:
<ButtonOK#Button>:
Strange enough, all you need to do is to change
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_press: root.parent.button1_clicked()
to:
<QALayout1>:
Button1:
id: btn1
text: 'OK'
on_release: root.parent.button1_clicked()
Changing on_press to on_release. I believe it has to do with the focus of your TextInput being set on the on_touch_down (on_press) event, but then it loses focus due to the on_touch_up (on_release) event. So using the on_release avoids that problem. You can see this happen, by running your original code and pressing that OK button, but don't release it. The TextInput will have focus until you release your mouse button.
And you don't even need the line:
self.gridlayout.txtinput.focus = True
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.
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)