Kivy: How to add buttons to StackLayout from a list - python

I want to use a list of values like ['btn1', 'btn2','btn3'] to create buttons dynamically on my layout.
After that I want capture the name of the button when pressed
but I'm stuck at adding buttons step please help
This is my main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.button import Button
Builder.load_file('design.kv')
class MyLayout(Widget):
btns = ['btn1', 'btn2','btn3']
for i in btns:
self.add_widget(Button(text = i))
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyApp().run()
and this is my design.kv file
<MyButton#Button>:
font_size: 24
size_hint: [.50, None]
height: 60
<MyLayout>
GridLayout:
rows: 2
size: root.width, root.height
Label:
size_hint: 1, None
size: 120 , 120
text: "Welcome"
font_size: 42
rgb: utils.get_color_from_hex('#5ee2e5')
ScrollView:
do_scroll_x: False
do_scroll_y: True
StackLayout:
canvas:
Color:
rgb: utils.get_color_from_hex('#5ee2e5')
Rectangle:
pos: self.pos
size: self.size
cols:1
pos_hint: {'center_x':.5}
size_hint_y: None
spacing: 5
padding: 10
height: self.minimum_height
top: self.height
MyButton:
text: "Btn1"

You can do that by adding an id for the StackLayout in kv:
StackLayout:
id: stack
Then add a method in the MyLayout class to add the Buttons, along with a call to that method in the MyApp class:
class MyLayout(Widget):
def add_butts(self, dt):
btns = ['btn1', 'btn2', 'btn3']
for i in btns:
self.ids.stack.add_widget(Button(text=i, size_hint=(None, None), size=(100, 60)))
class MyApp(App):
def build(self):
ml = MyLayout()
Clock.schedule_once(ml.add_butts)
return ml

Related

How Can I Remove BoxLayout?

I want to remove a Boxlayout in my widget, but I cant.
There is a Boxlayout at the begining of the app called "Speca". After my sellections it must be removed.
When you run the app, sellect something in "Home" Spinner and click any of the new Toggle buttons.
Wtih the press of any of the Toggle buttons "Speca" must disappear.
Please help.
Here is main.py:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.spinner import Spinner
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, StringProperty, BooleanProperty
from kivy.properties import ListProperty
from collections import OrderedDict
from kivy.uix.togglebutton import ToggleButton
data1=["mother","father","son"]
data2=["uncle","aunt","grandfather"]
data3=["jack","mike","simon"]
data4=["1898","1975","1985","1885"]
amd="0dp"
class MainWidget(Widget):
def remove_layout(self, *ignore):
self.remove_widget(self.layout)
global amd
an0=tuple(list(OrderedDict.fromkeys(data1)))
cal5= ObjectProperty()
cal6= ObjectProperty()
def btn10(self,text):
if self.cal5:
self.cal5.parent.remove_widget(self.cal5)
self.cal5 =ModelSpecifications()
a=data2
b=data3
c=data4
mi=[]
n=0
while n < len(a):
aba=n
mi.append(aba)
n+=1
for i in mi:
self.b1=MyTButton(text=str(i),size_hint=(1,None),height="100dp",group="selections")
self.cal5.add_widget(self.b1)
self.ids.scd.add_widget(self.cal5, index=3)
def on_state(self, togglebutton): #<----THIS IS THE TUGGLEBUTTON I WANT USE
global amd
tb = togglebutton
text1=tb.text
if text1=="0":
amd="50dp"
elif text1=="1":
amd="100dp"
else:
amd="200dp"
if self.cal6:
self.cal6.parent.remove_widget(self.cal6)
self.cal6 =Spec()
self.ids.scd.add_widget(self.cal6, index=2)
class SecondPage(ScrollView):
pass
class Speca(BoxLayout): #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
pass
class Spec(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint=(1,None)
self.height=amd
class MyTButton(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class ModelSpecifications(BoxLayout): #this is the class I want add after my spinner selection
pass
class Calculation(GridLayout):
pass
class MyApp(App):
pass
MyApp().run()
here is my.kv :
MainWidget:
<MainWidget>:
hideable: hideable
ScreenManager:
id: scmanager
size: root.width, root.height
Screen:
id: scndpage
name: "second"
SecondPage:
Calculation:
id:scd
cols:1
height: self.minimum_height
row_default_height: "70dp"
size_hint_y: None
spacing:"10dp"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
size_hint: 1, None
height: "50dp"
pading:"10dp"
spacing:"10dp"
orientation: "vertical"
BoxLayout:
orientation: "horizontal"
Label:
text:"Name:"
color: 0,0,0,1
TextInput:
text:"---"
color: 0,0,0,1
Label:
text:"Surname:"
color: 0,0,0,1
TextInput:
text:"-----"
color: 0,0,0,1
BoxLayout:
id:scdd
size_hint: 1, 1
height: "100dp"
orientation: "vertical"
BoxLayout:
size_hint: 1, None
height: "50dp"
orientation: "horizontal"
Label:
text: " Sellection:"
color: 0,0,0,1
Spinner:
text: 'Home'
values: root.an0
on_text: app.root.btn10(self.text)
Speca: #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
Button:
text:" Calculate"
Button:
text:"Sellect"
Button:
text:"Back"
<ModelSpecifications>:
id:anss
pading:"10dp"
spacing:"10dp"
size_hint: 1, None
height: "100dp"
orientation: "horizontal"
<MyTButton#ToggleButton>: #<----THIS IS THE TUGGLEBUTTON I WANT USE
on_state:
app.root.on_state(self)
<Speca>: #<------ THIS IS THE BOXLAYOUT I WANT TO REMOVE
orientation:"vertical"
Label:
color: (1,1,0,1)
text:"I WANT TO REMOVE THIS PART WHEN I PRESS ANY OF TOGGLE BUTTONS"
Please run the app and see it.
Looks to me like speca is a child of the calculation widget with ID scd. So give speca an ID and then Remove speca via ids in on_srate python function.
Kv
SecondPage:
Calculation:
id:scd
speca:
id: speca
py
def on_state():
self.root.ids.scd.remove_widget(self.root.ids.speca)
YES.
When I make those changes;
Kv
SecondPage:
Calculation:
id:scd
speca:
id: speca
py
def on_state():
self.ids.scd.remove_widget(self.ids.speca)
It works.

How to add custom widget to another custom widget in kivy

I am pretty new to the kivy language and faced this problem when try to run the following code. it just runs without any errors. But it just only generate a blank application. without any widgets.
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.boxlayout import MDBoxLayout
kv = """
<SCManager>:
Screen:
c_screen:
<c_screen>:
cols: 2
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
MDBoxLayout:
md_bg_color: (1, 1, 1 ,1)
padding: 10
adaptive_height: True
MDLabel:
text: "helow 2"
color: (0.0003, 0.34, 0.60,1)
font_style: "H6"
halign: "left"
adaptive_height: True
<navigator>:
canvas.before:
Color:
rgba: 1,1,0,1
Rectangle:
pos: self.pos
size: self.size
orientation: "vertical"
MDLabel:
text: "hellow"
adaptive_height: True
MDList:
OneLineAvatarListItem:
text : "Dashboard"
IconLeftWidgetWithoutTouch:
icon: "view-dashboard"
OneLineAvatarListItem:
text : "Manage Users"
IconLeftWidgetWithoutTouch:
icon: "account"
MDRoundFlatIconButton:
text: "bye"
font_size: 11
"""
class SCManager(ScreenManager):
pass
class navigator(MDBoxLayout):
pass
class c_screen(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.n = navigator()
self.add_widget(self.n)
class test(MDApp):
def build(self):
Builder.load_string(kv)
return SCManager()
if __name__ == '__main__':
test().run()
But if i add another widget(a label) to Screen from kv string, it works fine or if i use add_widget(c_screen) in SCManager it works fine.But i need to add c_screen class from the kv string. Is there any way to add widgets without adding any widgets to Screen??
here is the working code
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.boxlayout import MDBoxLayout
kv = """
<SCManager>:
Screen:
MDLabel:
text: "helow 2"
adaptive_height: True
c_screen:
<c_screen>:
cols: 2
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
MDBoxLayout:
md_bg_color: (1, 1, 1 ,1)
adaptive_height: True
MDLabel:
text: "helow 2"
adaptive_height: True
<navigator>:
canvas.before:
Color:
rgba: 1,1,0,1
Rectangle:
pos: self.pos
size: self.size
orientation: "vertical"
MDLabel:
text: "hellow"
adaptive_height: True
MDList:
OneLineAvatarListItem:
text : "Dashboard"
IconLeftWidgetWithoutTouch:
icon: "view-dashboard"
OneLineAvatarListItem:
text : "Manage Users"
IconLeftWidgetWithoutTouch:
icon: "account"
MDRoundFlatIconButton:
text: "bye"
font_size: 11
"""
class SCManager(ScreenManager):
pass
class navigator(MDBoxLayout):
pass
class c_screen(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.n = navigator()
self.add_widget(self.n)
class test(MDApp):
def build(self):
Builder.load_string(kv)
return SCManager()
if __name__ == '__main__':
test().run()

Kivy - Calling a pulsing method from one class to another

I have the following classes in my kivy app, and i would like to call the blink method in my mainApp class. The start pulsing and blink methods enable the MainWindow background to pulse. However it's not working inside the MainWindow class and i need to call it in my mainApp class. Commented out (build method in mainApp) is what i tried, which results to the error Exception: Invalid instance in App.root. My python file:
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ColorProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from plyer import filechooser
data = ""
class MainWindow(Screen):
def analyze_data(self):
global data
data = self.ids.user_input.text
data = analyze(data)
animated_color = ColorProperty()
pulse_interval = 4
def blink(self):
x = Clock.schedule_once(self.start_pulsing, 5)
return x
def start_pulsing(self, *args):
d = self.pulse_interval / 2
anim = Animation(animated_color=(69/255, 114/255, 147/255, 1), duration=d) + Animation(animated_color=(1, 1, 1, 1), duration=d)
anim.repeat = True
anim.start(self)
class OutputScreen(Screen):
def on_enter(self, *args):
self.ids.output_label.text = data
class mainApp(MDApp):
def __init__(self):
super().__init__()
def choose_file(self):
try:
filechooser.open_file(on_selection = self.handle_selection)
except:
pass
def handle_selection(self,selection):
global path
selection_ls = selection[0]
path = selection_ls
#print(path)
def change_screen(self,screen):
screemanager = self.root.ids['screenmanager']
screemanager.current = screen
def change(self):
self.change_screen('output')
def back(self):
self.change_screen('main')
'''
def build(self):
x = MainWindow().blink()
return x'''
and my kv file:
#:import utils kivy.utils
GridLayout:
cols:1
ScreenManager:
id: screenmanager
MainWindow:
id: main
name: 'main'
OutputScreen:
id: output
name: 'output'
<MainWindow>:
BoxLayout:
orientation:'vertical'
MDBottomNavigation:
panel_color: utils.get_color_from_hex("#ffffff")
MDBottomNavigationItem:
name:'analytics'
text:'analytics'
icon:'account-circle'
FloatLayout:
size: root.width, root.height
canvas.before:
Color:
rgba: root.animated_color
Rectangle:
pos:self.pos
size:self.size
TextInput:
multiline:True
id: user_input1
pos_hint:{"x" : 0.05, "top" : 0.9}
size_hint: 0.9, 0.37
Label:
markup: True
id:input_label
pos_hint:{"x" : 0, "top":1}
size_hint: 1 ,0.08
font_size : 32
bold: True
canvas.before:
Color:
rgb: utils.get_color_from_hex("01121c")
Rectangle:
size: self.size
pos: self.pos
Button:
pos_hint:{"top" : 0.51, "x" : 0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Submit'
on_press: root.analyze_data()
on_release: app.change()
Button:
pos_hint:{"top":0.42, "x":0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Upload'
on_release:app.choose_file()
Button:
id:'info_button'
pos_hint:{"top":0.47, "x":0.8}
size_hint: (None,None)
width : 50
height : 22
font_size : 23
text:'i'
on_release:root.analytics_info()
<OutputScreen>:
ScrollView:
GridLayout:
cols:1
MDIconButton:
icon:'arrow-left'
pos_hint:{'top':1,'left':1}
size_hint: 0.1,0.1
user_font_size : '64sp'
on_release: app.back()
Label:
id: output_label
multiline:True
text_size: self.width, None
size_hint: 1, None
height: self.texture_size[1]
color: 0,0,0,1
padding_x: 15
Any help will be much appreciated.
The build() method of an App should return a Widget that will become the root of the App. But your build() method returns a ClockEvent (the return from Clock.schedule_once()). Try changing your build() method to:
def build(self):
x = MainWindow()
x.blink()
return x
Since you do not call Builder.load_file(), I assume that your kv file is named main.kv, and therefore will get loaded automatically. If that is true, then you do not need a build() method at all. Instead add an on_start() method to your mainApp class, like this:
def on_start(self):
self.root.ids.main.blink()

Overlapping of custom Widget in ScrollView GridLayout in Kivy

this is a trial code that I want to implement in my final project.
Python Code:
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class Wid(BoxLayout):
def settxt(self,i):
lab = self.ids['lab']
but = self.ids['but']
lab.text = "Label Number {}".format(i)
but.text = "Button Number {}".format(i)
class Win1(Screen):
i=0
def addw(self):
box1 = self.ids['box1']
self.i = self.i +1
w = Wid()
w.settxt(self.i)
box1.add_widget(w)
def switch(self):
sm.current="win2"
class Win2(Screen):
def switch(self):
sm.current="win1"
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("test.kv")
sm = WindowManager()
screens = [Win1(name="win1"), Win2(name="win2")]
for screen in screens:
sm.add_widget(screen)
sm.current = "win1"
class Test(App):
def build(self):
return sm
if __name__ == '__main__':
Test().run()
Kivy Code:
<Wid>:
lab:lab
but:but
BoxLayout:
height: self.minimum_height
size: root.size
Label:
id: lab
Button:
id: but
<Win1>
name:"win1"
box1:box1
BoxLayout:
height: self.minimum_height
orientation: "vertical"
BoxLayout:
size_hint: 1,0.2
Button:
text:"window 2"
on_release:
root.switch()
Button:
text:"add wid"
on_release:
root.addw()
ScrollView:
GridLayout:
id:box1
orientation: "vertical"
spacing: 2
size_hint_y: None
height: self.minimum_height
row_default_height: 60
cols:1
<Win2>
name: "win2"
BoxLayout:
id: bl
height: bl.minimum_height
size_hint_y: None
Button:
text:"window 2"
on_release:
root.switch()
With the press of the switch, I expect that my custom widget to get in the gridlayout in the scrollview, one below the other. But instead, each new widget appears in the last cell of the layout and overlaps on the previous one and empty cells keep on forming above them.
Don't know where it's going wrong.
Here I have moved the kv to a separate file, and dynamically created the screens.
Key points: I add the screens dynamically in on_start, this is after the build has completed. I create the ScreenManager in kv, and use the id to add the screens. In the kv code I put the ScreenManger in a BoxLayout. This is a personal preference. I do this so when accessing objects the root widget is not the screen manager. Therefore in the switch() methods, the addressing uses the assigned id, rather than relying on the root widget being a screenmanager.
FWIW: If the switch code is only going to change screens I would move those single lines into KV.
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
class Wid(BoxLayout): # Change to layout
def settxt(self, i):
lab = self.ids['lab']
but = self.ids['but']
lab.text = "Label Number {}".format(i)
but.text = "Button Number {}".format(i)
class Win1(Screen):
i = 0
def addw(self):
box1 = self.ids['box1']
self.i = self.i + 1
w = Wid()
w.settxt(self.i)
box1.add_widget(w)
#staticmethod
def switch():
app = App.get_running_app()
app.root.ids.sm.current = "win2"
class Win2(Screen):
#staticmethod
def switch():
app = App.get_running_app()
app.root.ids.sm.current = "win1"
class WidgetQ1App(App):
def build(self):
return Builder.load_file('widgetq.kv')
def on_start(self):
screens = [Win1(name="win1"), Win2(name="win2")]
sm = self.root.ids.sm
for screen in screens:
sm.add_widget(screen)
WidgetQ1App().run()
And the KV code:
<Wid>: # Put widgets in a layout, not a widget.
lab:lab
but:but
BoxLayout:
size: root.size
Label:
id: lab
Button:
id: but
<Win1>
# name:"win1"
box1:box1
BoxLayout:
orientation: "vertical"
BoxLayout:
size_hint: 1,0.2
Button:
text:"window 2"
on_release:
root.switch()
Button:
text:"add wid"
on_release:
root.addw()
ScrollView:
GridLayout:
id:box1
orientation: "vertical"
spacing: 2
size_hint_y: None
height: self.minimum_height
row_default_height: 60
cols:1
<Win2>:
# name: "win2"
BoxLayout:
Button:
text:"window 2"
on_release:
root.switch()
BoxLayout:
ScreenManager:
id: sm
<win2>
name: "win2"
size_hint_y: None
height: bl.minimum_height
BoxLayout:
id: bl
Button:
text:"window 2"
on_release:
root.switch()
Your custom widgets don't have a height defined, try changing to something like the above.
Also, start your class names with an upper case letter, kv requires this in some cases. For instance, win2 should be Win2.
A number of issues here, see the comments. The biggest problem I see is putting items in a widget. Put Widgets in a layout, not other widgets.
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
kv = """
<Wid>: # Put widgets in a layout, not a widget.
lab:lab
but:but
BoxLayout:
size: root.size
Label:
id: lab
Button:
id: but
<Win1>
# name:"win1"
box1:box1
BoxLayout:
orientation: "vertical"
BoxLayout:
size_hint: 1,0.2
Button:
text:"window 2"
on_release:
root.switch()
Button:
text:"add wid"
on_release:
root.addw()
ScrollView:
GridLayout:
id:box1
orientation: "vertical"
spacing: 2
size_hint_y: None
height: self.minimum_height
row_default_height: 60
cols:1
<Win2>:
# name: "win2"
BoxLayout:
Button:
text:"window 2"
on_release:
root.switch()
ScreenManager:
id: sm
Win1:
name: 'win1'
Win2:
name: 'win2'
"""
class Wid(BoxLayout): # Change to layout
def settxt(self,i):
lab = self.ids['lab']
but = self.ids['but']
lab.text = "Label Number {}".format(i)
but.text = "Button Number {}".format(i)
class Win1(Screen):
i = 0
def addw(self):
box1 = self.ids['box1']
self.i = self.i + 1
w = Wid()
w.settxt(self.i)
box1.add_widget(w)
#staticmethod
def switch():
app = App.get_running_app()
app.root.current = "win2"
class Win2(Screen):
#staticmethod
def switch():
app = App.get_running_app()
app.root.current = "win1"
# class WindowManager(ScreenManager):
# pass
# kv = Builder.load_file("test.kv")
# sm = WindowManager()
#
# screens = [win1(name="win1"), win2(name="win2")]
# for screen in screens:
# sm.add_widget(screen)
#
# sm.current = "win1"
class WidgetQApp(App):
def build(self):
return Builder.load_string(kv)
WidgetQApp().run()

How to stimulate button in kivy?

How to make press method on this code working? When I press a button the list populates, but when I call it from Clock then not. I can see populate print on the console but the list does not appear in the view. I mean simply: how to stimulate pressing the button in the code?
from kivy.config import Config
Config.set('graphics', 'multisamples', '0')
from random import sample
from string import ascii_lowercase
import pyrebase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
kv = """
<Row#BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Label:
text: root.value
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
id: populate_btn
text: 'Populate list'
on_press: root.populate()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Test(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def populate(self):
print("populate")
self.rv.data = [{'value': ''.join(sample(ascii_lowercase, 6))}
for x in range(50)]
def press(self):
self.ids.populate_btn.dispatch('on_press')
def interval(dt):
x = Test()
x.press()
Clock.schedule_interval(interval, 3)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
The error is caused because the Test object created in interval function is different from the Test object that returns build method, besides that the Test object created in interval is eliminated since it is a local variable. So the solution is to use the same reference by passing it to the interval function for it I will use functools.partial() function.
# ...
from functools import partial
# ...
def interval(x, dt):
x.press()
class TestApp(App):
def build(self):
t = Test()
Clock.schedule_interval(partial(interval, t), 3)
return t
# ...

Categories