How to run checkbox widget using Popup in Kivy? - python

Newbie kivy user here. I have created 2 Kivy apps, and each works individually but I can't integrate one to the other. I am trying to run the CheckBoxesApp in MyApp using Popup(onButtonPress3). When I ran my code, I got the error "WidgetException: add_widget() can be used only with instances of the Widget class." Might be related to the fact that CheckBoxesApp uses App while MyApp uses MDApp? Any help appreciated >>
main.py
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
from kivy.uix.widget import Widget
import kivy
kivy.require('1.10.1')
from kivymd.app import MDApp
from kivy.uix.popup import Popup
FruitsSellected = []
class Checkboxes(Widget):
def checkboxes_click(self, instance, value, text):
if value:
print(f"Select {text}")
self.myvar = FruitsSellected
#Close
if text == 'FINISH':
print(FruitsSellected)
self.save()
App.get_running_app().stop()
return FruitsSellected
FruitsSellected.append(str(text))
else:
print(f"UnSelect {text}")
FruitsSellected.remove(str(text))
def save(self):
with open("allergenlist.txt", "w") as fobj:
fobj.write(str(self.myvar))
class CheckBoxesApp(App):
def build(self):
return Checkboxes()
class MyApp(MDApp):
def build(self):
# Define a grid layout for this App
self.layout = GridLayout(cols=1, padding=10)
# Don't worry about this button, just a place holder
self.button = Button(text="TAKE PICTURE")
self.layout.add_widget(self.button)
# Add a button
self.button2 = Button(text="SELECT")
self.layout.add_widget(self.button2) # Add a button
# Attach a callback for the button press event
self.button.bind(on_press=self.onButtonPress)
self.button2.bind(on_press=self.onButtonPress3)
return self.layout
#Place Holder don't worry about this either
def onButtonPress(self, button):
layout = GridLayout(cols=1, padding=10)
popupLabel = Label(text="TAKE PICTURE")
closeButton = Button(text="Close the pop-up")
layout.add_widget(popupLabel)
layout.add_widget(closeButton)
# Instantiate the modal popup and display
popup = Popup(title='TAKE PICTURE',
content=layout)
popup.open()
# Attach close button press
closeButton.bind(on_press=popup.dismiss)
def onButtonPress3(self, button):
layout = CheckBoxesApp()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
if __name__ == '__main__':
MyApp().run()
Checkboxes.kv
<Checkboxes>
# Create Box Layout
BoxLayout:
# set orientation and size
orientation: "vertical"
size: root.width, root.height
# Creating label
# create Grid Layout
GridLayout:
# Set number of column
cols:4
# create checkboxes
Label:
text: "Apple"
CheckBox:
# When check box selected, it will call checkboxes_click() method
on_active: root.checkboxes_click(self, self.active, "Apple")
Label:
text: "Bannana"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Bannana")
Label:
text: "Orange"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Orange")
Label:
text: "Grape"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Grape")
Label:
text: "Melon"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Melon")
Label:
text: "Peach"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Peach")
Label:
text: "Pineapple"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "Pineapple")
Label:
text: "FINISH"
CheckBox:
on_active: root.checkboxes_click(self, self.active, "FINISH")

The content of a Popup must be a Widget, but an App is not a Widget, so your code:
def onButtonPress3(self, button):
layout = CheckBoxesApp()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
will fail.
I suggest changing that code to:
def onButtonPress3(self, button):
# layout = CheckBoxesApp()
layout = Checkboxes()
popup3 = Popup(title='SELECT', content=layout)
popup3.open()
Of course, you must load the Checkboxes.kv file explicitly for this to work.

Related

How to dynamically add widgets on a different screen depending on which button is pressed if the buttons were added dynamically as well?

The Problem
I'm attempting to create dynamic buttons tied to "pages" in my program. Each button and page inherits a name from a database that I have tied to the program. In this case, I'm having trouble adding widgets to a screen and changing to that screen on button press. So far, I'm able to implement dynamic buttons that could change the screen and receive variables for text but the widgets aren't being added or aren't showing up.
The Code
Upon request, I recreated the code with the same problem my program was experiencing.
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty
from kivymd.uix.button import MDRectangleFlatButton
from functools import partial
kv = '''
ScreenManager
ScreenOne:
ScreenTwo:
ScreenThree:
<ScreenOne>:
ScrollView:
MDGridLayout:
id: screen_one
cols: 1
adaptive_height: True
padding: "40dp"
MDFlatButton:
text: "Next Page"
theme_text_color: "Custom"
text_color: 0, 0, 1, 1
pos_hint: {"center_x": .5, "center_y": .5}
on_press: root.manager.current = 'Screen 2'
<ScreenTwo>:
name: 'Screen 2'
on_pre_enter: root.on_load()
ScrollView:
MDGridLayout:
id: screen_two
cols: 1
adaptive_height: True
padding: "40dp"
<ScreenThree>:
name: 'Screen 3'
ScrollView:
MDGridLayout:
id: screen_three
cols: 1
adaptive_height: True
padding: "40dp"
'''
sm = ScreenManager()
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def on_load(self):
name_list = ['Page 1', 'Page 2', 'Page 3']
button_list = []
for x in name_list:
page_button = MDRectangleFlatButton(text = x, theme_text_color = "Custom")
button_list.append(page_button)
for x in button_list:
x.bind(on_press = partial(self.load_home, x.text))
self.ids.screen_two.add_widget(x)
def load_home(self, name, *largs):
self.manager.current = 'Screen 3'
Screen3 = ScreenThree()
Screen3.on_load(name)
def on_leave(self):
self.ids.screen_two.clear_widgets()
# Below is the particular code I'm having trouble with. It's supposed
# to display a page with a button with the same name as the button
# that sends you to Screen Three. However, the button is not showing up.
class ScreenThree(Screen):
def on_load(self, name, *kwargs):
test_button = MDRectangleFlatButton(text = name, theme_text_color = "Custom")
self.ids.screen_three.add_widget(test_button)
sm.add_widget(ScreenOne(name="Screen 1"))
sm.add_widget(ScreenTwo(name="Screen 2"))
sm.add_widget(ScreenThree(name="Screen 3"))
class TestApp(MDApp):
def build(self):
screen = Builder.load_string(kv)
return screen
TestApp().run()
All help, tips, and solutions would be greatly appreciated. Thank you in advanced.
I figured it out! Turns out, I can add the buttons from the same method as ScreenTwo's load_home() method by giving each screen ids via screenmanager in the kivy code:
ScreenManager
ScreenOne:
id: one
ScreenTwo:
id: two
ScreenThree:
id: three
And then use the add_widget function in the load_home() method:
def load_home(self, name, *largs):
test_button = MDRectangleFlatButton(text = name, theme_text_color = "Custom")
self.manager.ids.three.ids.screen_three.add_widget(test_button)
self.manager.current = 'Screen 3'

Textinput focus in Python-Kivy coding

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

Displaying an image over multiple buttons - kivy

I want to display a single image over multiple different buttons (as shown in the picture: https://i.stack.imgur.com/BQ99R.jpg)
When I try to do so, the image gets covered by the buttons and hidden in the background.
What I want to know is that is this even possible in kivy? If yes, where do I define the image, in the label or button?
This code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Base>:
GridLayout:
cols: 2
size_hint: 1, 1
Button:
text: "Button 1"
on_press: print(self.text)
Button:
text: "Button 2"
on_press: print(self.text)
Button:
text: "Button 3"
on_press: print(self.text)
Button:
text: "Button 4"
on_press: print(self.text)
Image:
source: 'wolf.png'
""")
class Base(FloatLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
class SampleApp(App):
def build(self):
return Base()
if __name__ == "__main__":
SampleApp().run()
produces this:
Of course you have to provide an image yourself ;o)

Kivy - How do you change a StringProperty value on a different screen?

My app gets data from a database and and is stored into variables in Python. The code below is a simplified version where you have two screens. The first screen has two buttons and the second screen has a label and a back button. The text of the label on the second screen will change depending on what button is pressed.
When run, the label is set to the value of the StringProperty, which is "Test". When one of the buttons are clicked the ChangeScreen function is run and works out the correct new label. The LabelUpdater function on the second is run which should change the string property but doesn't. How do I fix this issue? Thanks <3
Python:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class DemoScreen1(Screen):
def ChangeScreen(self, button_text):
if button_text == "Button 1":
new_label = "This is the new label when button 1 is pressed"
DemoScreen2.LabelUpdater(new_label)
else:
new_label2 = "This is the new label when button 2 is pressed"
DemoScreen2.LabelUpdater(new_label2)
self.parent.current = "demoscreen2"
class DemoScreen2(Screen):
screen2_label = StringProperty("Test")
def LabelUpdater(NEW_LABEL):
screen2_label = StringProperty(NEW_LABEL)
class AppScreenManager(ScreenManager):
pass
class Tester(App):
pass
if __name__ == '__main__':
Tester().run()
Kivy:
AppScreenManager:
DemoScreen1:
DemoScreen2:
<DemoScreen1>:
name: "demoscreen1"
orientation: "vertical"
GridLayout:
rows: 2
Button:
id: Button1
text: "Button 1"
on_release: root.ChangeScreen(Button1.text)
Button:
id: Button2
text: "Button 2"
on_release: root.ChangeScreen(Button2.text)
<DemoScreen2>:
name: "demoscreen2"
orientation: "vertical"
GridLayout:
rows:2
Label:
text: root.screen2_label
Button:
text:"Back"
on_release: app.root.current = "demoscreen1"
Use ids and reference through AppScreenManager aka the ScreenManager.
self.parent.ids.screen2.screen2_label = new_label
See full example below.
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class DemoScreen1(Screen):
def ChangeScreen(self, button_text):
if button_text == "Button 1":
new_label = "This is the new label when button 1 is pressed"
print('Palim')
#DemoScreen2.LabelUpdater(new_label)
self.parent.ids.screen2.screen2_label = new_label
else:
new_label2 = "This is the new label when button 2 is pressed"
self.parent.ids.screen2.screen2_label = new_label2
#DemoScreen2.LabelUpdater(new_label2)
self.parent.current = "demoscreen2"
class DemoScreen2(Screen):
screen2_label = StringProperty("Test")
def LabelUpdater(NEW_LABEL):
screen2_label = StringProperty(NEW_LABEL)
class AppScreenManager(ScreenManager):
pass
class Tester(App):
pass
if __name__ == '__main__':
Tester().run()
kv
AppScreenManager:
DemoScreen1:
id: screen1
DemoScreen2:
id: screen2
<DemoScreen1>:
name: "demoscreen1"
orientation: "vertical"
GridLayout:
rows: 2
Button:
id: Button1
text: "Button 1"
on_release: root.ChangeScreen(Button1.text)
Button:
id: Button2
text: "Button 2"
on_release: root.ChangeScreen(Button2.text)
<DemoScreen2>:
name: "demoscreen2"
orientation: "vertical"
GridLayout:
rows:2
Label:
text: root.screen2_label
Button:
text:"Back"
on_release: app.root.current = "demoscreen1"

Refactor on_press into class rule

In my RootWidget I have a label and two buttons. I want to dynamically change the text of the label whenever one of the buttons is clicked.
Here's a minimal working example of how I do it at the moment.
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
Button:
text: '1'
on_press: label.text = self.text
Button:
text: '2'
on_press: label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()
Obviously I want to refactor the line on_press: label.text = self.text. My first tries ended in
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
<MyButton>:
on_press: label.text = self.text
But obviously the MyButton-class doesn't know the property label of the RootWidget-class. And class rules inside class rules is also not allowed.
Is there a way of accomplishing binding the on_press-action dynamically?
You can refer to the Label like this:
<MyButton#Button>:
on_press: self.parent.ids.label.text = self.text
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
It's quite simple actually, because through kv you can access parent of a widget/rule easily with self.parent, so your code would look like:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
But:
text: '1'
But:
text: '2'
<But>:
on_press: self.parent.label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()

Categories