I state that I am not an expert in Python and I started very recently with Kivy! I would like to know if it is possible, and if it makes sense, to add widgets such as buttons or labels while the app is running. For example a button that each time it is pressed adds a new button to a screen. I don't know if I've been clear enough.
This example illustrates the process by creating a new Button each time another Button is pressed, you can also delete (remove) the created buttons.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class CustomBox(BoxLayout):
def add_buttons(self, *args):
"""Method for creating new Button."""
i = len(self.ids.inner_box.children) # Just to distinguish the buttons from one another.
btn = Button(
text = f"Button {i+1}",
size_hint_y = None,
height = "64dp",
)
self.ids.inner_box.add_widget(btn) # Referencing container by its 'id'.
Builder.load_string("""
<CustomBox>:
orientation: "vertical"
spacing: dp(2)
Button:
size_hint_y: 0.5
text: "Add Button"
color: 0, 1, 0, 1
on_release: root.add_buttons()
ScrollView: # To see and add all the buttons in progress.
BoxLayout: # Button's container.
id: inner_box
orientation: "vertical"
spacing: dp(5)
padding: dp(5)
size_hint_y: None # Grow vertically.
height: self.minimum_height # Take as much height as needed.
Button:
size_hint_y: 0.5
text: "Delete Button"
color: 1, 0, 0, 1
on_release: inner_box.remove_widget(inner_box.children[0]) if inner_box.children else None # Here '0' means last added widget.
""")
class MainApp(App):
def build(self):
return CustomBox()
if __name__ == '__main__':
MainApp().run()
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class BeginnerApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
self.count = 0
a = root.add_widget(Button(text='add button', on_press=self.add_button))
b = root.add_widget(Button(text='remove button', on_press=self.delete_button))
return root
def add_button(self, *args):
self.count += 1
self.root.add_widget(Button(text=str(self.count)))
def delete_button(self, *args):
if self.count > 0:
self.root.remove_widget(self.root.children[0])
self.count -= 1
else:
pass
if __name__ == '__main__':
app = BeginnerApp()
app.run()
Note 1: when you bind a method, specify its name without brackets. Otherwise, binding does not work normally.
on_press=self.add_button
on_press=self.delete_button
Note 2: to add widgets in layout you can use method "add_widget()".
self.root.add_widget(Button())
For delete widget you can use method "remove_widget()". To delete a widget in layout, you need to specify this widget. This can be done through the "children" method. The last widget in the "children" method is numbered "0". So you can delete the last widget in layout.
self.root.remove_widget(self.root.children[0])
Note 3: When declaring methods, don't forget *args.
def add_button(self, *args):
Related
i'm working on a qr code scanner using kivy zbarcam, I managed to make it work but the problem is the camera initializes right away and does not release the camera after leaving the screen. I tried zbarcam.stop() and root.ids.zbarcam_id.ids.xcamera.play=False but what it does is just unscheduling and doesn't really release the use of camera. I tried to build this in buildozer but it's so slow since the camera is using a lot of memory even when not in use.
Can somebody know what is the workaround on this?
This is my code:
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
from kivymd.app import MDApp
class QRScreen(Screen):
pass
class LoginScreen(Screen):
pass
class ScannerScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._after_init)
# self.ids.zbarcam_id.ids.xcamera.play=True
def _after_init(self, dt):
"""
Binds `ZBarCam.on_symbols()` event.
"""
zbarcam = self.ids.zbarcam_id
zbarcam.bind(symbols=self.on_symbols)
def on_symbols(self, zbarcam, symbols):
"""
Loads the first symbol data to the `QRFoundScreen.data_property`.
"""
# going from symbols found to no symbols found state would also
# trigger `on_symbols`
if not symbols:
return
symbol = symbols[0]
data = symbol.data.decode('utf8')
print(data)
self.manager.get_screen('qr').ids.data.text= data
self.manager.transition.direction = 'left'
self.manager.current = 'qr'
def on_leave(self):
zbarcam = self.ids.zbarcam_id
zbarcam.stop()
class DemoApp(MDApp):
def build(self):
# screen =Screen()
self.title='Demeter'
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "DeepPurple"
self.help = Builder.load_file('main.kv')
return self.help
DemoApp().run()
kv file:
#:import ZBarCam kivy_garden.zbarcam.ZBarCam
ScreenManager:
LoginScreen:
ScannerScreen:
QRScreen:
<LoginScreen>:
name: "login"
MDFlatButton:
text:'release'
on_press:
root.manager.current = 'scanner'
root.manager.transition.direction = "right"
<ScannerScreen>:
name: 'scanner'
ZBarCam:
id: zbarcam_id
play: False
MDFlatButton:
id: iyu
text:'release'
on_press:
root.ids.zbarcam_id.ids.xcamera.play=False
MDFlatButton:
id: ads
text:'replay'
pos_hint: {"center_x": 0.5, "center_y": 0.95}
on_press:
root.ids.zbarcam_id.ids.xcamera.play=True
<QRScreen>:
name: 'qr'
MDIconButton:
icon:'arrow-left'
pos_hint: {'center_x':0.1,'center_y':0.95}
on_press:
root.manager.current = 'scanner'
root.manager.transition.direction = "right"
MDLabel:
id:data
text: "asdgasda"
pos_hint: {'center_y':0.5}
theme_text_color: "Custom"
text_color: 0, 1, 0, 1
I was having the same problem and finally found the solution (based on this)
In my case I initate ZBarCam in the .py file instead of .kv (see example).
To actually close the camera, you can call the following:zbarcam.ids['xcamera']._camera._device.release() in your on_leave() function.
If you initiate/manage ZBarCam in the .kv, I think that the equivalent would be something like: root.ids.zbarcam.ids.xcamera._camera._device.release()
If compiled with buildzoner for Android, I have to use self.zbarcam.ids['xcamera']._camera = None instead
I have the same error, I already install garden, kivy-garden, kivy, kivymd, pyzbar, and all the requriments in the buildozer.spec file, but when I run it, the log said ModuleNotFoundError: No module named 'kivy_garden.zbarcam', I have being trying a lot of solutions in other requriments, but any of that works.
Framework: I am talking about an Android app written in Python 2.7 and packaged with Builtdozer
I have a Builder inside the app with a button
Builder.load_string('''
FloatLayout:
orientation: 'horizontal'
Button:
text: str(root.name)
font_size: '20sp'
pos_hint: {'x':.0, 'y':.3}
size_hint: .4, .8
''')
I want to create a function, change_name, that, if I press the above button, opens the android keyboard to accept an user raw_input
The raw_input provided by the user has to replace the text of the above button.
What I thought is:
1) Create a variable name = StringProperty('Me')
2) Create a function:
def change_name(self):
self.name = raw_input()
3) Call the function inside my button with a on_release
Builder.load_string('''
FloatLayout:
orientation: 'horizontal'
Button:
text: str(root.name)
font_size: '20sp'
pos_hint: {'x':.0, 'y':.3}
size_hint: .4, .8
on_release: root.change_name()
''')
It is correct? Because actually, running the app on Ubuntu, I am trying to click on the button but the app does not ask for an input (it seems blocked).
As a consequence I believe it will not work also on Android.
Could you please help me understanding where am I wrong?
raw_input allows to get input from stdin (terminal). On Android you will not have the terminal available. In addition, raw_input is blocking, this causes the main event loop of your app to be freeze and will cause your app to stop responding.
You shouldn't use raw_input but Kivy's own methods.
On the other hand, you want to make your button editable (as if it were a TextInput). You can create your own custom Button class or use WindowBase.request_keyboard() to request the keyboard manually. However, you can do a little trick by hiding a TextInput and use it to enter the text:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
kv_text= ('''
<MyWidget>:
FloatLayout:
orientation: 'horizontal'
Button:
text: 'Hello'
font_size: '20sp'
pos_hint: {'x':.0, 'y':.3}
size_hint: .4, .8
on_release: root.change_name(self)
Button:
text: 'World'
font_size: '20sp'
pos_hint: {'x':0.6, 'y':.3}
size_hint: .4, 0.8
on_release: root.change_name(self)
''')
class MyWidget(FloatLayout):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.hide_input = TextInput(size_hint=(None, None),
size = (0, 0),
multiline = False)
self.hide_input_bind = None
def change_name(self, instance):
if self.hide_input_bind:
self.hide_input.unbind_uid('text', self.hide_input_bind)
self.hide_input.text = instance.text
self.hide_input.focus = True
self.hide_input_bind = self.hide_input.fbind('text', self._update_text, instance)
def _update_text(self, button, instance, value):
button.text = value
class MyKivyApp(App):
def build(self):
return MyWidget()
def main():
Builder.load_string(kv_text)
app = MyKivyApp()
app.run()
if __name__ == '__main__':
main()
App working on Android (Kivy Launcher):
I want my Kivy app to have multiple ListView instances. I'm stuck when it comes to assigning different properties, especially callbacks. The following code illustrates my problem:
from kivy.app import App
from kivy.uix.listview import ListItemButton
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
Builder.load_string('''
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import ListItemButton kivy.uix.listview.ListItemButton
<smRoot>:
Screen1:
Screen2:
<ListItemButton>:
on_press: app.callback1(self)
height: dp(25)
size_hint: (1,.1)
<Screen1>:
name:'screen1'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen1'
size_hint_y: .1
ListView:
id:lstScreen1
adapter:
ListAdapter(data=[{'text':'item1'},{'text':'item2'},{'text':'item3'}],
args_converter=lambda row_index, rec: {'text': rec['text']},
cls=ListItemButton,
selection_mode='single',
allow_empty_selection=False)
Button:
text: 'Switch Screen'
size_hint_y: .1
on_press: root.manager.current = 'screen2'
<ListItemButton>:
on_press: app.callback2(self)
height: dp(25)
size_hint: (1,.1)
<Screen2>:
name:'screen2'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen2'
size_hint_y: .1
ListView:
id:lstScreen2
adapter:
ListAdapter(data=[{'text':'item1'},{'text':'item2'},{'text':'item3'}],
args_converter=lambda row_index, rec: {'text': rec['text']},
cls=ListItemButton,
selection_mode='single',
allow_empty_selection=False)
Button:
text: 'Switch Screen'
size_hint_y: .1
on_press: root.manager.current = 'screen1'
''')
class smRoot(ScreenManager):
pass
class Screen1(Screen):
MyButt = ListItemButton
pass
class Screen2(Screen):
pass
class myApp(App):
def build(self):
smroot = smRoot()
return smroot
def callback1(self, btn_instance):
index = btn_instance.index
print 'From Screen 1: ' + str(index)
def callback2(self, btn_instance):
index = btn_instance.index
print 'From Screen 2: ' + str(index)
myApp().run()
Here, I want each of the ListView instances on Screens 1 and 2 to have separate callbacks. But when I click on either screen, the output is the same, like so:
From Screen 2: 0
From Screen 1: 0
Ideally, I was hoping to implement something like a Dynamic Class. But when I tried something like this:
<MyButt#ListItemButton>:
#on_press: app.callback1(self)
height: dp(25)
size_hint: (1,.1)
<Screen1>:
name:'screen1'
MyButt:
on_press: app.callback1(self)
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen1'
size_hint_y: .1
ListView:
id:lstScreen1
adapter:
ListAdapter(data=[{'text':'item1'},{'text':'item2'},{'text':'item3'}],
args_converter=lambda row_index, rec: {'text': rec['text']},
cls=MyButt,
selection_mode='single',
allow_empty_selection=False)
I get a NameError: name 'MyButt' is not defined error. As far as I can tell, I've adhered to the same syntax in the documentation example linked above, so I'm confused.
I haven't tried creating a custom class in Python, but I would rather keep the UI elements separate in the .kv code (If I'm not mistaken, isn't that the basic idea behind kv?).
Any help/pointers/suggestions in this regard would be much appreciated.
You can't use a widget class from the python side (after ":") in kv without importing it, but since MyButt is not defined in any module, being a dynamic class (defined in pure kv) you can use Factory to use it.
first, import Factory at the top of your kv file.
#:import Factory kivy.factory.Factory
then change
ListView:
id:lstScreen1
adapter:
ListAdapter(data=[{'text':'item1'},{'text':'item2'},{'text':'item3'}],
args_converter=lambda row_index, rec: {'text': rec['text']},
cls=MyButt,
to
ListView:
id:lstScreen1
adapter:
ListAdapter(data=[{'text':'item1'},{'text':'item2'},{'text':'item3'}],
args_converter=lambda row_index, rec: {'text': rec['text']},
cls=Factory.MyButt,
Background: I have been learning Python - and through it - Kivy, by making an app. I have been using the .kv file and Builder.load_string methods to create my graphics, but have decided to try using solely python, and moving all of my layouts over into python.
The Problem: When I began using screens, I was unable to bind the correct code to the buttons to make the screens transition. When I am writing the line, 'self.manager.etc...' auto-complete shows me a list of valid properties to use.
So after 'self.' it shows that I can use 'manager', and after 'manager.' it does not think that the screen's manager has a 'current' or 'transition' property. I must have messed up in how I connected the screen to the manager, but I cannot fathom how.
class HomePage(Screen):
def __init__(self, **kwargs):
super(HomePage, self).__init__(**kwargs)
layout = FloatLayout()
notification = Label(text='upcoming: ....', font_size='16sp', size_hint=(0,0), pos_hint={'center_x':.5, 'top':0.9})
layout.add_widget(notification)
button_row=BoxLayout(size_hint_y=.1, spacing=20)
profile_button=Label(text='Profile')
button_row.add_widget(profile_button)
layout.add_widget(button_row)
self.add_widget(layout)
def transit():
self.manager.current = profile_button # <- this should work, right?
profile_button.bind(on_press=transit)
class ScreenApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(HomePage(name='home'))
return sm
if __name__ == "__main__":
ScreenApp().run()
You should add in your imports the transition
from kivy.uix.screenmanager import Screen, ScreenManager, FadeTransition
And pass it in the builder
class ScreenApp(App):
def build(self):
sm = ScreenManager(transition=FadeTransition())
as for the current you should add a second screen, give it a name and and use that name to change to that screen.from the https://github.com/kivy/kivy/blob/master/kivy/uix/screenmanager.py
by default, the first added screen will be shown. If you want to
show another one, just set the 'current' property. sm.current = 'second'
Also current is a string property, you can not set it to a label
:attr:current is a :class:~kivy.properties.StringProperty and
defaults to None.
So your full code should be something like
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen, ScreenManager, FadeTransition
class HomePage(Screen):
def __init__(self, **kwargs):
super(HomePage, self).__init__(**kwargs)
layout = FloatLayout()
notification = Label(text='upcoming: ....', font_size='16sp', size_hint=(0,0), pos_hint={'center_x':.5, 'top':0.9})
layout.add_widget(notification)
button_row=BoxLayout(size_hint_y=.1, spacing=20)
profile_button=Button(text='Profile') # changed to a button
button_row.add_widget(profile_button)
profile_button.bind(on_press=self.transit) # moved here the bind action
layout.add_widget(button_row)
self.add_widget(layout)
def transit(self, *args):
# unintended to become a class method and reference to it with self
print "ok"
self.manager.current = "screen2"
class ProfilePage(Screen):
def __init__(self, **kwargs):
super(ProfilePage, self).__init__(**kwargs)
layout = FloatLayout()
labelP = Label(text="Profile Page")
layout.add_widget(labelP)
self.add_widget(layout)
class ScreenApp(App):
def build(self):
sm = ScreenManager(transition=FadeTransition())
# create the first screen
screen1 = HomePage(name='Home') #your home page
screen2 = ProfilePage(name='screen2') # the second screen
sm.add_widget(screen1)
sm.add_widget(screen2)
return sm
if __name__ == "__main__":
ScreenApp().run()
I am using kvlang to define the UI layout for my kivy test app. I use a Button as the root widget and I want a Popup to appear when the button is pressed.
To do this I specify "on_press: app.do_popup()" property in the .kv file.
Can I define the popup that will appear as a named class in the same .kv file and reference it from the do_popup() definition, or do I have to define it in the application's main.py file?
Thanks and Regards!
EDIT:
here is the case where the widget is defined in the .py file:
.py:
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class kkkApp(App):
def do_popup(self):
pu = Popup(title = 'title', size_hint = (0.5, 0.5))
box = BoxLayout(orientation = 'vertical')
lab = Label(text = 'text', size_hint = (1, 5))
but = Button(text = 'Close', size_hint = (1, 1))
box.add_widget(lab)
box.add_widget(but)
pu.add_widget(box)
pu.open()
but.bind(on_release = pu.dismiss)
kkkApp().run()
.kv:
<MaButton#Button>:
text: '123'
<MaPopup#Popup>:
auto_dismiss: True
# ... more stuff defining the popup with button and behavior...
MaButton:
id: main_button
on_release: app.do_popup()
the question is whether there is some way to leverage kvlang and write less python using the MaPopup defined in the kv-file as follows:
.py:
from kivy.app import App
class kkkApp(App):
def do_popup(self):
pu = MaPopup(title = 'title', size_hint = (0.5, 0.5))
pu.open()
kkkApp().run()
where the omitted stuff is defined in the .kv file
Yes, you can defined any object/layout in the .kv file and reference it in the main .py file and I believe that's the way to go in Kivy. However, instead of deriving from the Popup class (as in <MaPopup#Popup>), I would suggest defining the content of the popup and provide it when creating a Popup instance in the main .py file. There is no need to subclass Popup when all you are interested in are its contents.
So, to create the popup content in .kv based on what you declared in your .py file:
<MaPopup#BoxLayout>:
orientation: "vertical"
Label:
text: "text"
size_hint: (1,5)
Button:
text: "Close"
size_hint: (1,1)
on_release: root.cancel()
Since MaPopup is a BoxLayout and not a Popup, it does not have a dismiss method, so we'll bind root.cancel() to on_release and give it functionality in the .py file.
In you .py file, you'll need to create a Class with the same name AND define the cancel attribute that was referenced in the .kv file (which will be None for starters):
from kivy.properties import ObjectProperty
class MaPopup(BoxLayout):
cancel = ObjectProperty(None)
This will allow you to create any instances you want of MaPopup and insert it wherever you want in your .py file.
Finally, you can create a Popup using an instance MaPopup as its contents. There are a few extra lines that will help you make the opening/closing of Popups of more general use
class kkkApp(App):
# Use this class attribute to reference Popups
_popup = None
# Use this method to close Popups referenced by _popup
def _dismiss_popup(self):
self._popup.dismiss()
def do_popup(self):
# Instantiate MaPopup and give functionality to cancel button
popup_content = MaPopup(cancel=self._dismiss_popup)
self._popup = Popup(title = 'title', size_hint = (0.5, 0.5), content=popup_content)
self._popup.open()
Notice that we created an instance of MaPopup named popup_content and provided this instance as the Popup content using the content= keyword. We also added functionality to the cancel button at this stage, which gives you flexibility to bind the on_release event to any other method you want. You could create another MaPopup a give an entirely different function to the cancel button.