Kivy add_widget does not show new widgets - python

I'm trying to add widgets in a Scrollview placed inside a Screen, but I can not understand what is the problem with the code. The code does not give me any error but it does not show the new widget. When the button "add" is pressed, the funcion add() is called but it does not work.
Here there is the code.py
class ScreenManagement(ScreenManager):
pass
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class ClassAllScreen(BoxLayout):
pass
class ClassApp(App):
def add(self):
self.s2 = Screen2()
layout = GridLayout(cols=3)
layout.add_widget(Label(text='Test'))
layout.add_widget(TextInput())
layout.add_widget(CheckBox())
self.s2.ids.widget_list.add_widget(layout)
def build(self):
self.root = ClassAllScreen()
return self.root
kv = Builder.load_file("main2.kv")
if __name__ == '__main__':
ClassApp().run()
and the kv file:
<Screen1>:
GridLayout:
cols:1
<Screen2>:
GridLayout:
cols:1
Button:
size_hint_y: 0.1
text: 'add'
on_release:
app.add()
ScrollView:
size_hint: 1, 1
do_scroll_x: False
padding: 20
spacing: 20
GridLayout:
id: widget_list
cols: 1
spacing: 5
GridLayout:
rows: 1
<ScreenManagement>:
Screen1:
name: 'screen1'
Screen2:
name: 'screen2'
<ClassAllScreen>:
orientation:'vertical'
GridLayout:
cols: 1
GridLayout:
rows: 1
size_hint_y: 0.12
Button:
text: "Screen 2"
on_release:
app.root.ids.screenmanager.transition.direction = 'right'
app.root.ids.screenmanager.current='screen2'
Button:
text: "Screen 1"
on_release:
app.root.ids.screenmanager.transition.direction = 'left'
app.root.ids.screenmanager.current='screen1'
ScreenManagement:
id:screenmanager
Can someone help me?

In your add() method, the code:
self.s2 = Screen2()
is creating a new instance of Screen2. Then you are adding widgets to that new instance. However, that new instance is not part of your GUI. What you want is to add the widgets to the Screen2 instance that is in your GUI. To do that, replace the above code with:
self.s2 = self.root.ids.screenmanager.get_screen('screen2')
This code gets the ScreenManager using its id, and then uses get_screen() to the the Screen2 instance.

Related

(KivyMD) How to call a function when using 2 kv files/strings?

I have a kivymd app that has a screen with a button on it. When you click this button an MDCard will appear on the screen. When you click on this new MDCard it will call a function that will print a message on the terminal. However, I am having trouble getting this MDCard to call the function. I am getting the error:
AttributeError: 'MDCard' object has no attribute 'option'
The MDCard is in a separate kv string from the main kv string. Essentially I have two kv strings. When you press the button, the second kv string will be added as a widget to the first kv string.
I figured it is because the second kv string doesn't have a class as a root but I don't know how to do this. How can I get the MDCard to call the function??
MAIN.PY
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from button_screen import button_screen
MainNav = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: 'Go to Button Screen'
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "go_to_button_screen"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "words_nav_item"
button_screen:
name: "go_to_button_screen"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class main_test(MDApp):
def build(self):
self.theme_cls.primary_palette = "Red"
return Builder.load_string(MainNav)
main_test().run()
BUTTON SCREEN .PY FILE
from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
button_screen_kv = '''
<button_screen>:
MDGridLayout:
cols: 1
size: root.width, root.height
spacing: 40
md_bg_color: [0,0,.1,.1]
MDGridLayout:
cols: 1
size_hint_y: None
height: 40
MDGridLayout:
cols: 1
Button:
text: "Click to add card"
on_release:
root.add_card("card 1")
MDGridLayout:
id: add_card_here_id
cols: 1
'''
md_card_kv = '''
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.option("MDCard was clicked")
MDLabel:
id: LabelTextID
text: "this is an MDCard"
halign: 'center'
'''
class button_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Builder.load_string(button_screen_kv)
self.md_card_widget = Builder.load_string(md_card_kv)
def option(self, string):
print(f"{string}")
def add_card(self, *args):
self.ids.add_card_here_id.add_widget(self.md_card_widget)
I came up with the following that works however, I am still new to programming so I don't know if this is a stable solution. Please advise me there
The Solution:
I added another class class user_card(MDGridLayout) in the buttonscreen.py and placed the kv string there.
I set that class as the root widget in the kv string
in the init method I placed a function that will return the kv string return self.user_card_string
then I added that class as a parameter in the add_widget and passed the col as an argument: self.ids.add_card_here_id.add_widget(user_card(cols=1))
Everything works but I have never used two classes before. So I am unsure if this will present a future problem.
button_screen.py:
from kivy.lang import Builder
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.screen import MDScreen
button_screen_kv = '''
<button_screen>:
MDGridLayout:
cols: 1
size: root.width, root.height
spacing: 40
md_bg_color: [0,0,.1,.1]
MDGridLayout:
cols: 1
size_hint_y: None
height: 40
MDGridLayout:
cols: 1
Button:
text: "Click to add card"
on_release:
root.add_card("card 1")
MDGridLayout:
id: add_card_here_id
cols: 1
'''
md_card_kv = '''
<user_card>:
MDGridLayout:
cols: 1
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.option()
MDLabel:
id: LabelTextID
text: "this is an MDCard"
halign: 'center'
'''
class user_card(MDGridLayout):
user_card_string = Builder.load_string(md_card_kv)
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
self.load_card()
def option(self, *args):
print("MDCard was clicked")
def load_card(self):
return self.user_card_string
class button_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Builder.load_string(button_screen_kv)
def add_card(self, *args):
self.ids.add_card_here_id.add_widget(user_card(cols=1))

Kivy Python: buttons and classes for multiple screens

I have a problem with my Kivy Python Code. I have 2 screens: 1st is to navigate to the second screen and on the 2nd screen there is a button to add text to a scrollview...navigating is working but it does not add any text to the scrollview...I think I need some help here! AttributeError: 'super' object has no attribute 'getattr'
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock, mainthread
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.effects.scroll import ScrollEffect
from kivy.lang import Builder
Builder.load_string("""
<MenuScreen>:
name: 'mainmenu'
BoxLayout:
spacing: 1
orientation: "vertical"
Label:
text: "MAIN MENU"
Button:
text: 'Go to Screen 2'
on_release:
root.manager.current = 'screen2'
root.manager.transition.direction = "left"
Button:
text: 'Quit'
on_release: root.manager.current = app.exit_software()
<Screen2>:
name: 'screen2'
BoxLayout:
spacing: 1
orientation: "vertical"
ScrollView:
id: scroll_view
always_overscroll: False
BoxLayout:
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Label:
id: label
text: "You can add some Text here by pressing the button"
size_hint: None, None
size: self.texture_size
Button:
text: 'Add text!'
size_hint_y: 0.1
on_release: app.add_text()
Button:
text: 'Back to main menu'
size_hint_y: 0.1
on_release:
root.manager.current = 'mainmenu'
root.manager.transition.direction = "right"
""")
# Declare both screens
class MenuScreen(Screen):
pass
class Screen2(Screen):
pass
class AddTextApp(App):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def build(self):
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='mainmenu'))
sm.add_widget(Screen2(name='screen2'))
return sm
def add_text(self):
self.root.ids.label.text += f"Some new Text\n"
self.root.ids.scroll_view.scroll_y = 0
def exit_software(self):
App.get_running_app().stop()
if __name__ == '__main__':
AddTextApp().run()
Thank you very much in advance!
The error occurred because self.root.ids gets access to widgets located in the root widget of the main class. To access the secondary screen elements, you need to add it to the main class (in your case, in ScreenManager) and set its id. Also, you have a lot of imported excess, so that it is clearly visible, I advise you to use Pycharm or something like that.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
kv = """
<MenuScreen>:
name: 'mainmenu'
BoxLayout:
spacing: 1
orientation: "vertical"
Label:
text: "MAIN MENU"
Button:
text: 'Go to Screen 2'
on_release:
root.manager.current = 'screen2'
root.manager.transition.direction = "left"
Button:
text: 'Quit'
on_release: root.manager.current = app.exit_software()
<Screen2>:
name: 'screen2'
BoxLayout:
spacing: 1
orientation: "vertical"
ScrollView:
id: scroll_view
always_overscroll: False
BoxLayout:
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Label:
id: label
text: "You can add some Text here by pressing the button"
size_hint: None, None
size: self.texture_size
Button:
text: 'Add text!'
size_hint_y: 0.1
on_release: app.add_text()
Button:
text: 'Back to main menu'
size_hint_y: 0.1
on_release:
root.manager.current = 'mainmenu'
root.manager.transition.direction = "right"
ScreenManager:
MenuScreen:
id: menu_scr
Screen2:
id: scr_2
"""
class MenuScreen(Screen):
pass
class Screen2(Screen):
pass
class AddTextApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return Builder.load_string(kv)
def add_text(self):
self.root.ids.scr_2.ids.label.text += f"Some new Text\n"
self.root.ids.scr_2.ids.scroll_view.scroll_y = 0
#staticmethod
def exit_software():
App.get_running_app().stop()
if __name__ == '__main__':
AddTextApp().run()

How to remove specific row from gridlayout? Rows are added from popup

I would like to remove specific row from the gridlayout with delete button. Rows are created in popup. Im able to add delete button to each row but cant figure out how to add logic in it.
Here is the codes i have tried. I played with it for a while. I tried it many ways and this is just where i stopped.
Py.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty, BooleanProperty, ObjectProperty
from kivy.uix.textinput import TextInput
class Row(BoxLayout):
x1 = StringProperty('')
x2 = StringProperty('')
x3 = BooleanProperty(False)
x4 = ObjectProperty()
def __init__(self, x1, x2, x3, x4, **kwargs):
super(Row, self).__init__(**kwargs)
self.x1 = x1
self.x2 = x2
self.x3 = x3
self.x4 = x4
def remove_row(self):
self.remove_widget(Row)
class MyPopup(Popup):
pass
class MainScreen(Screen):
pass
class SecondScreen(Screen):
def fire_popup(self):
pops = MyPopup()
pops.open()
class ScreenManagement(ScreenManager):
def changescreen(self, value):
try:
if value !='main':
self.current = value
except:
print('No Screen named'+ value)
class testiApp(App):
def build(self):
self.title = 'Hello'
def add_more(self, x1, x2, x3, x4):
addbutton = self.root.get_screen('Page2').ids.empty
addbutton.add_widget(Row(x1, x2, x3, x4))
def remove(self):
container = self.root.get_screen('Page2').ids.empty
if len(container.children) > 0:
container.remove_widget(container.children[0])
testiApp().run()
KV.
<MyPopup>:
id:pop
size_hint: .4, .4
auto_dismiss: False
title: 'XXX!!'
BoxLayout:
orientation:'vertical'
BoxLayout:
orientation:'horizontal'
Label:
text:'X1'
TextInput:
id: X1
Label:
text:'X2'
TextInput:
id:X2
CheckBox:
id:X3
Button:
id:X4
text:'Delete'
BoxLayout:
orientation:'horizontal'
Button:
text:'Lisää'
on_release: app.add_more(X1.text, X2.text, X3.active, X4)
Button:
text: 'Close'
on_press: pop.dismiss()
<Row>:
x1:''
x2:''
x3:False
x4:
Label:
text: root.x1
Label:
text: root.x2
CheckBox:
active: root.x3
Button:
text:'poista'
on_release: root.remove_row()
ScreenManagement:
MainScreen:
name:'Main'
SecondScreen:
name:'Page2'
<MainScreen>:
name:'Main'
BoxLayout:
orientation:'vertical'
GridLayout:
id:container
cols:2
Label:
text:'testfield1'
TextInput:
id: textfield1
Label:
text:'testfield2'
TextInput:
id: textfield2
Button:
text:'Next Page'
on_release: app.root.current ='Page2'
<SecondScreen>:
name:'Page2'
BoxLayout:
orientation:'vertical'
BoxLayout:
orientation:'vertical'
Label:
text:'Popup Test'
ScrollView:
bar_width: 5
bar_color: 1,0,0,1 #red
bar_inactive_color: 0,0,1,1 #blue
effect_cls: 'ScrollEffect'
scroll_type:['bars','content']
GridLayout:
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
row_default_height: 60
cols:1
id:empty
BoxLayout:
Button:
text:'Open Popup'
on_press: root.fire_popup()
Button:
text:'remove'
on_release: app.remove()
Question 2 - Popup message to confirm delete
... want that Poista(Delete) button to open popup asking " are you
sure? Yes or No" How should i bind the remove_row?
Solution
kv file
Create a class rule, <ConfirmDeleteRow>: with inheritance of Popup widget
The content of the Popup widget is a Label widget with text of 'Are you sure?', and two Button widgets with text of 'Yes', and 'No' respectively.
Using on_release event to bind the 'Yes' button to invoke remove_row() method
Using on_release event to bind both buttons to close the popup window by invoking dismiss() method
Bind the delete button to invoke a new method, confirm_delete()
Snippets - kv file
<ConfirmDeleteRow>: # class rule
size_hint: .4, .4
auto_dismiss: False
title: 'Delete'
BoxLayout: # content
orientation:'vertical'
Label:
text: 'Are you sure?'
BoxLayout:
size_hint: 1, 0.2
Button:
text: 'Yes'
on_release:
root.dismiss()
root.row.remove_row()
Button:
text: 'No'
on_release:
root.dismiss()
...
<Row>:
x1:''
...
Button:
text:'poista'
on_release: root.confirm_delete()
Py file
Implement class ConfirmDeleteRow() with a new class attribute, row = ObjectProperty(None) and a constructor accepting an additional parameter, row
Implement a new method, confirm_delete() to instantiate ConfirmDeleteRow() and pass self (i.e. row) as argument
Snippets - py file
class ConfirmDeleteRow(Popup):
row = ObjectProperty(None)
def __init__(self, row, **kwargs):
super(ConfirmDeleteRow, self).__init__(**kwargs)
self.row = row # save row object
class Row(BoxLayout):
...
def confirm_delete(self):
confirm_delete_row = ConfirmDeleteRow(self) # pass self / row object
confirm_delete_row.open()
Question 1 - remove specific row
... would like to remove specific row from the gridlayout with delete
button
Solution
When the button, 'poista' (delete) is pressed, self in remove_row() method is referring to the instantiated Row object for that specific row in the ScrollView. Therefore, to remove that specific row, you have to refer to its parent in order to remove it/child from the parent.
Replace self.remove_widget(Row) with self.parent.remove_widget(self)
Snippets
def remove_row(self):
self.parent.remove_widget(self)
Output

python/kivy : access attribute value in .kv file

When I click on Account(root.display_account()) then call display_account().After that RVACCOUNT() function call .After that when i click on +Add Account then def add_account(self): call
I have a class AccountPopup which define a attribute state_text and assign value text:'Testing' in .kv file
How to get value of state_text 'Testing' and pass in on_text: root.filter(self.text,state_text) and print in def filter function.
test.py
class AccountPopup(Popup):
state_text = ObjectProperty(None)
popupAccountCity = ObjectProperty(None)
def display_cities_treeview_account(self, instance):
if len(instance.text) > 0:
#if self.popupAccountCity is None:
self.popupAccountCity = TreeviewCityAccount(self.state_text.text)
self.popupAccountCity.filter(instance.text,self.state_text.text)
self.popupAccountCity.open()
class TreeviewCityAccount(Popup):
state_text = ObjectProperty(None)
def __init__(self,state_text, **kwargs):
print(state_text)
def filter(self, f,state):
print(state)
class RVACCOUNT(BoxLayout):
def add_account(self):
self.mode = "Add"
popup = AccountPopup(self)
popup.open()
class MainMenu(BoxLayout):
def display_account(self):
self.dropdown.dismiss()
self.remove_widgets()
self.rvaccount = RVACCOUNT()
self.content_area.add_widget(self.rvaccount)
class FactApp(App):
title = "Test"
def build(self):
self.root = Builder.load_file('test.kv')
return MainMenu()
if __name__ == '__main__':
FactApp().run()
test.kv
<AccountPopup>:
state_text:state_text
TextInput:
id:state_text
text:'Testing'
<TreeviewCityAccount>:
BoxLayout
orientation: "vertical"
TextInput:
id: treeview
size_hint_y: .1
on_text: root.filter(self.text,state_text)
<RVACCOUNT>:
BoxLayout:
orientation: "vertical"
Button:
size_hint: .07, .03
text: "+Add Account"
on_press: root.add_account()
<MainMenu>:
content_area: content_area
dropdown: dropdown
BoxLayout:
orientation: 'vertical'
#spacing : 10
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
MenuButton:
id: btn
text: 'Master'
size : (60,30)
on_release: dropdown.open(self)
CustDrop:
DropdownButton:
text: 'Account'
size_hint_y: None
height: '32dp'
on_release: root.display_account()
Can someone help me?
You should reference it as self.state_text everywhere, also make it a StringProperty in the py file and can than access it as
on_text: root.filter(self.text,root.state_text)
root in kv refers to the most left widget aka <TreeviewCityAccount>: in your case.
See https://kivy.org/docs/api-kivy.lang.html
Alternatively you can work with ids in the kv file.
the value you are looking for is not in your immediate root which is why this is not working.The say thing to do is get the full path to that property like so:
Snippet:
<AccountPopup>:
id: ac_popup
#bunch of code
<TreeviewCityAccount>:
#chunk of code
TextInput:
id: tree view
on_text:root.filter(self.text,app.ac_popup.state_text
Also,generally,it's a good idea to id your classes mate
Disclaimer:code not tested

accessing data in the kivy language from ScreenManager

How can I access to the kivy data from MyScreenManager ? How can I access to Hellow or Timer data ? I cant use on_release: root.starttimer() in Hellow.
class Hellow(Screen):
pass
class Timer(Screen):
pass
class MyScreenManager(ScreenManager):
def starttimer(self):
#change text Hellow Button
root_widget = Builder.load_string('''
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
MyScreenManager:
transition: FadeTransition()
Hellow:
Timer:
<Hellow>:
AnchorLayout:
Button:
id: but
size_hint: None, None
size:300,100
text: 'make a foto'
font_size: 30
on_release: app.root.starttimer()
<Timer>:
name: 'Timer'
''')
class ScreenManagerApp(App):
def build(self):
print(self.ids)
return root_widget
if __name__ == '__main__':
ScreenManagerApp().run()
some text for stackoverflow (it says that I need to type more text),
Screen manager is only used to accept screen widgets if you try to add anything else like a button or label then it will throw an exception.
kivy.uix.screenmanager.ScreenManagerException: ScreenManager accepts only Screen widget.
Only one root object is allowed by .kv file In your case, you can access hello or Timer from each other.
<Hellow>:
name: 'hello'
...
Button:
id: but
...
on_release: root.parent.current = 'Timer'
<Timer>:
name: 'Timer'
Button:
text: "Take me back to hellow"
on_release: root.parent.current = 'hello'
but there could be another way too.
<Main>:
BoxLayout:
Button:
text: "Hello"
on_release: sm.current = 'Timer'
on_release: print(lbl.text)
Button:
text: "Timer"
on_release: sm.current = 'Hello'
ScreenManager:
id: sm
Screen:
name: hello
Label:
id: lbl
text: "I am hello"
Screen:
name: timer
Label:
text: "I am timer"
EDIT 1:
As you asked in your comment
class MyScreenManager(ScreenManager):
def __init__(self,**kwargs):
super(MyScreenManager,self).__init__(**kwargs)
def starttimer(self,event):
#print event.text
event.text = "changed text"
<Hellow>:
...
Button:
...
on_release: root.parent.starttimer(self)

Categories

Resources