How to add Widgets from outside class with Kivy - python

Hy! I have this app here, a kind of "To Do List". When I press "Add Item" Button, it opens a PopUp Window and I want when "Up" Button is pressed to add "ItemTemplate" in "ItemsList". I am new in Kivy and I tried do that in last few days. How can I do that?
Thanks a lot!
Python code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
class FirstBox(BoxLayout):
def btn(self):
Pu().open()
class ItemsList(GridLayout):
pass
class ItemTemplate(BoxLayout):
pass
class Pu(Popup):
pass
class MyAppApp(App):
pass
MyAppApp().run()
Kv code:
FirstBox:
<FirstBox>:
orientation: "vertical"
BoxLayout:
ScrollView:
ItemsList:
size_hint: 1, None
height: self.minimum_height
Button:
size_hint: 1,0.2
text: "Add Item"
on_release: root.btn()
<ItemsList>:
cols: 1
size_hint_y: None
ItemTemplate:
ItemTemplate:
ItemTemplate:
<ItemTemplate>:
size_hint: 1, None
CheckBox:
size_hint: 0.15, 1
Button:
text: "Task Name"
Button:
text: "Some action"
size_hint: 0.15,1
<Pu>:
size_hint: 1, 0.3
BoxLayout:
orientation: "vertical"
TextInput:
Button:
text: "Up"

Give id to your ItemList for add widget and TextInput for get text from.
ItemsList:
id: viewlist
and
TextInput:
id: new_task_input_id
Use StringProperty for dinamicly change text of ItemTemplate:
from kivy.properties import StringProperty
class ItemTemplate(BoxLayout):
task_text = StringProperty()
Edit .kv side dinamic: ( ItemTemplate's Button )
Button:
text: root.task_text
Trigger Up Button in .kv side: on_release: root.add_item()
Create this function in Pu class and add this operations:
def add_item(self,*args):
firstbox = App.get_running_app().root #access correctly main layout
itemlist = firstbox.ids.viewlist #find viewlist by id
new_task_text = self.ids.new_task_input_id.text #get textinput text
item = ItemTemplate() #create custom item
item.task_text = new_task_text #set item text
itemlist.add_widget(item) #add item to layout
Short function:
App.get_running_app().root.ids.viewlist.add_widget(ItemTemplate(task_text=self.ids.new_task_input_id.text))
Lets close popup after add new item:
self.dismiss()
Also if you want to add this widget to top, need to give index in add_widget :
itemlist.add_widget(item,len(itemlist.children))

Related

How to insert value of List Item into another Screen in KivyMD

In the app I'm coding on button click on the bottom right of the screen there appears a popup window. On "Done" click the popup window closes (close_dialog method), and a new List Item appears. The text of the List Item is obtained from MDTextField of the popup. On List Items click we enter another screen <GroupScreen> (goto_group method).
So I have two questions:
As far as I understand if we make several List Items, they all lead to one instance of <Group Screen>. Am I right?
I want each of created List Items to lead to its unique <GroupScreen> instance. For example, I want List text to be copied to MDLabel (instead of "Welcome" text). How can I do that?
Code .py:
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.textfield import MDTextField
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.list import TwoLineAvatarListItem
Window.size = (288, 511)
sm = ScreenManager()
class GroupScreen(Screen):
pass
class DialogContent(BoxLayout):
pass
class MainScreen(Screen):
dialog = None
def show_dialog(self, *args):
'''
Create group creation popup
'''
if not self.dialog:
self.dialog = MDDialog(
title="Create new group",
type="custom",
content_cls=DialogContent(),
)
self.dialog.open()
def close_dialog(self, *args):
'''
Close popup on Done click
'''
self.dialog.dismiss()
self.new_window()
def new_window(self, *args):
'''
Create new group button
'''
mylist = TwoLineAvatarListItem(text = self.dialog.content_cls.textfield.text,
on_release = self.goto_group)
self.mdlist.add_widget(mylist)
def goto_group(self, *args):
sm.current = 'group'
class grudget4App(MDApp):
def build(self):
sm.add_widget(MainScreen(name='main'))
sm.add_widget(GroupScreen(name='group'))
scroll = ScrollView()
return sm
if __name__ == '__main__':
grudget4App().run()
Code. kv:
ScreenManager:
MainScreen:
GroupScreen:
<DialogContent>:
textfield: textfield
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField:
id: textfield
hint_text: "Group name"
MDFlatButton:
id: btn1
text: "Done"
text_color: self.theme_cls.primary_color
on_release: app.root.get_screen('main').close_dialog()
<MainScreen>:
name: 'main'
mdlist: mdlist
FloatLayout:
size_hint: 1, 0.89
ScrollView:
MDList:
id: mdlist
MDFloatingActionButton:
pos_hint: {'right': 0.95, 'y': 0.05}
icon: "icon.png"
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
on_release:
root.show_dialog()
<GroupScreen>:
name: 'group'
MDLabel:
text: "Welcome" #app.root.ids["textfield"].text
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x': 0.5, 'center_y': 0.3}
on_release:
root.manager.current = 'main'
From the documentation:
name
Name of the screen which must be unique within a ScreenManager.
I don't see any code creating new GroupScreens, but wherever you put that code, you must include a unique name="some_group_name" for it.
I my experience, in order to allow Buttons to operate in an MDDialog, you must add auto_dismiss=False to its declaration:
def show_dialog(self, *args):
'''
Create group creation popup
'''
if not self.dialog:
self.dialog = MDDialog(
text="Create new group",
type="custom",
auto_dismiss=False, # needed to allow Buttons to operate
content_cls=DialogContent(),
)
self.dialog.open()
Then, the new_window() method can create the new GroupScreen and the Button:
def new_window(self, *args):
'''
Create new group button
'''
group_name = self.dialog.content_cls.textfield.text
mylist = TwoLineAvatarListItem(text=group_name,
on_release = partial(self.goto_group, group_name)) # use partial to pass group name
self.mdlist.add_widget(mylist)
sm.add_widget(GroupScreen(name=group_name)) # actually create the new GroupScreen and add it
And the goto_group() method becomes:
def goto_group(self, group_name, *args):
sm.current = group_name # switch current screen to the passed in group
The App declaration can be:
class grudget4App(MDApp):
def build(self):
sm.add_widget(MainScreen(name='main'))
# sm.add_widget(GroupScreen(name='group'))
# scroll = ScrollView()
return sm
Note the above requirement for unique name for Screens. You should take care that the user doesn't create duplicate group names.

How to get inputs from user and save them in a list(Python Kivy)?

I'm beginner in kivy module. I want to put 8 textboxes in screen to get input from user and then, save this inputs in a list in order to use them later!
I searched in the internet but a didn't find any thing useful.
I think I should do sth like this code:
Save text input to a variable in a kivy app
But don't want to show the inputs in shell, I wanna save them in a list!
Py file
Use a for loop to traverse through a container of all widgets e.g. TextInput.
Snippets
for child in reversed(self.container.children):
if isinstance(child, TextInput):
self.data_list.append(child.text)
kv file
Use a container e.g. GridLayout
Add an id for the container
Add all those Label and TextInput widgets as child of GridLayout
Snippets
GridLayout:
id: container
cols: 2
Label:
text: "Last Name:"
TextInput:
id: last_name
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ObjectProperty, ListProperty
from kivy.lang import Builder
Builder.load_file('main.kv')
class MyScreen(Screen):
container = ObjectProperty(None)
data_list = ListProperty([])
def save_data(self):
for child in reversed(self.container.children):
if isinstance(child, TextInput):
self.data_list.append(child.text)
print(self.data_list)
class TestApp(App):
def build(self):
return MyScreen()
if __name__ == "__main__":
TestApp().run()
main.kv
#:kivy 1.11.0
<MyScreen>:
container: container
BoxLayout:
orientation: 'vertical'
GridLayout:
id: container
cols: 2
row_force_default: True
row_default_height: 30
col_force_default: True
col_default_width: dp(100)
Label:
text: "Last Name:"
TextInput:
id: last_name
Label:
text: "First Name:"
TextInput:
id: first_name
Label:
text: "Age:"
TextInput:
id: age
Label:
text: "City:"
TextInput:
id: city
Label:
text: "Country:"
TextInput:
id: country
Button:
text: "Save Data"
size_hint_y: None
height: '48dp'
on_release: root.save_data()
Output
You need to give your text inputs ids, then reference the id of them and get their text using .text. self.root in the TestApp class refers to the root widget of your kv file, which is the one that doesn't have brackets (< >) around it, in this case the GridLayout.
main.py
from kivy.app import App
class MainApp(App):
def get_text_inputs(self):
my_list = [self.root.ids.first_input_id.text, self.root.ids.second_input_id.text]
print(my_list)
pass
MainApp().run()
main.kv
GridLayout:
cols: 1
TextInput:
id: first_input_id
TextInput:
id: second_input_id
Button:
text: "Get the inputs"
on_release:
app.get_text_inputs()

kivy multiscreen can not find the id

all, i can not pass the goi into the function submit_goi in class VQCIA in my kivy code. i'd appreciate if some one can fix the code. print self.goi.text() sentence reports a problem, and it says AttributeError: 'NoneType' object has no attribute 'text'. that is really weird.
# ---------- vqcia.py ----------
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<HelloVQCIA>:
BoxLayout:
Button:
text: "Welcome to VQCIA"
on_press:
# You can define the duration of the change
# and the direction of the slide
root.manager.transition.direction = 'left'
root.manager.transition.duration = 1
root.manager.current = 'screen_two'
<VQCIA>:
BoxLayout:
orientation: "vertical"
goi: g_o_i
padding: 10
spacing: 10
size: 400, 200
pos: 200, 200
size_hint:None,None
BoxLayout:
Label:
text: "Enter gene of interest with TAIR ID:"
font_size: '25sp'
BoxLayout:
TextInput:
id: g_o_i
hint_text: 'AT3G20770'
multiline: False
font_size: '25sp'
BoxLayout:
Button:
text: "Submit"
size_hint_x: 15
on_press:
root.submit_goi()
""")
class HelloVQCIA(Screen):
pass
class VQCIA(Screen):
# Connects the value in the TextInput widget to these
# fields
goi = ObjectProperty()
def submit_goi(self):
print self.goi.text
# The ScreenManager controls moving between screens
screen_manager = ScreenManager()
# Add the screens to the manager and then supply a name
# that is used to switch screens
screen_manager.add_widget(HelloVQCIA(name="screen_one"))
screen_manager.add_widget(VQCIA(name="screen_two"))
class VQCIAApp(App):
def build(self):
return screen_manager
dbApp = VQCIAApp()
dbApp.run()
I want to make a multiscreen app and the first page is a welcome page. and the second page is the main page that user can submit a form. thank you.
In kv file, move goi: g_o_i to after class rule, <VQCIA>:
<VQCIA>:
goi: g_o_i
BoxLayout:
orientation: "vertical"

Kivy adding and removing widgets from GridLayout gives weak reference issue

I am attempting to dynamically add and remove widgets from a GridLayout with Python-Kivy, and I am running into an issue with weak references. When the Screen containing the GridLayout is initialized, I am placing a Label inside of the GridLayout that contains text notifying the user that the container has no items (technically, it has the Label in there so it is not necessarily empty). Then I have a Button that allows the user to add individual GridLayout widgets to the GridLayout that contain a Label, a TextInput and a CheckBox, as well as a Button that allows the user to remove those individual GridLayout widgets that they created. If all of these widgets (dynamically added by the user) are removed, then the original Label is added back to the GridLayout.
When I attempt to construct this logic in Python to pair with Kivy, I run into an issue where the original Label never seems to be fully removed from the stack.
I was under the impression that self.ids.widget_list.remove_widget(self.ids.empty) would remove the Label widget with id empty, however this is not the case. It is clear that the widget still exists, when I call print(self.ids):
{'widget_list': <WeakProxy to <kivy.uix.gridlayout.GridLayout object at 0x0451F030>>, 'empty': <WeakProxy to <kivy.uix.gridlayout.GridLayout object at 0x0451F9D0>>}
Any help is much appreciated.
EDIT
When checking for i in self.layouts: print(i.children) every time the remove() method is called, shows that the references to the added widgets are never being completely removed. This may be where my issues resides, but unsure how to resolve it.
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
#Load kv file
Builder.load_file('test.kv')
#First Screen
class Screen1(Screen):
layouts = []
def remove(self):
for i in self.layouts:
if i.children[0].active:
self.ids.widget_list.remove_widget(i)
if len(self.layouts)==0:
layout = GridLayout(rows=1, id=empty)
layout.add_widget(Label(text='Nothing Here'))
self.ids.widget_list.add_widget(layout)
else:
self.update_hints()
def add(self):
if len(self.ids.widget_list.children)<5:
print(self.ids)
self.ids.widget_list.remove_widget(self.ids.empty)
print(self.ids)
layout = GridLayout(cols=3)
layout.add_widget(Label(text='Test ' + str(len(self.ids.widget_list.children)+1)))
layout.add_widget(TextInput())
layout.add_widget(CheckBox())
self.ids.widget_list.add_widget(layout)
self.layouts.append(layout)
self.update_hints()
else:
layout = GridLayout(cols=1)
layout.add_widget(Label(text='Only five allowed at once.\nRemove at least one to add another.'))
button = Button(text='Acknowledge'); layout.add_widget(button)
popup = Popup(content=layout, title='Limit Reached', size_hint=(.5,.5), auto_dismiss=False)
button.bind(on_release=popup.dismiss)
popup.open()
def update_hints(self):
for i in self.layouts:
i.children[1].hint_text = 'Ex. ' + str(round(100/len(self.ids.widget_list.children),2)) + '%'
#Initialize Screens and Start App
class MyScreenManager(ScreenManager):
pass
#Main application
class SampleApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
SampleApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
<Screen1>:
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .1
Label:
text: 'List of Widgets'
underline: True
Label:
text: 'Percentage'
underline: True
Label:
text: 'Remove (Y/N)'
underline: True
ScrollView:
size_hint: 1, .5
do_scroll_x: False
padding: 20
spacing: 20
GridLayout:
id: widget_list
cols: 1
spacing: 5
GridLayout:
id: empty
rows: 1
Label:
text: 'Nothing Here'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .2
Button:
text: 'Add Widget'
on_release: root.add()
Label:
text: ''
Button:
text: 'Remove Widget'
on_release: root.remove()
Ok, I seem to have found a simpler means of implementation that avoids specifying any id for the initial Label (the same one that shows if self.layouts==[]):
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
#Load kv file
Builder.load_file('test.kv')
#First Screen
class Screen1(Screen):
count = 0
layouts = []
def remove(self):
for i in self.layouts:
if i.children[0].active:
self.ids.widget_list.remove_widget(i)
self.layouts = [i for i in self.layouts if not i.children[0].active]
if self.layouts!=[]:
self.update_hints()
else:
layout = GridLayout(rows=1)
layout.add_widget(Label(text='Nothing Here'))
self.ids.widget_list.add_widget(layout)
def add(self):
if self.layouts==[]:
self.ids.widget_list.clear_widgets()
if len(self.ids.widget_list.children)<5:
self.count+=1
layout = GridLayout(cols=3)
layout.add_widget(Label(text='Test ' + str(self.count)))
layout.add_widget(TextInput())
layout.add_widget(CheckBox())
self.ids.widget_list.add_widget(layout)
self.layouts.append(layout)
self.update_hints()
else:
layout = GridLayout(cols=1)
layout.add_widget(Label(text='Only five allowed at once.\nRemove at least one to add another.'))
button = Button(text='Acknowledge'); layout.add_widget(button)
popup = Popup(content=layout, title='Limit Reached', size_hint=(.5,.5), auto_dismiss=False)
button.bind(on_release=popup.dismiss)
popup.open()
def update_hints(self):
for i in self.layouts:
i.children[1].hint_text = 'Ex. ' + str(round(100/len(self.ids.widget_list.children),2)) + '%'
#Initialize Screens and Start App
class MyScreenManager(ScreenManager):
pass
#Main application
class SampleApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
SampleApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
<Screen1>:
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .1
Label:
text: 'List of Widgets'
underline: True
Label:
text: 'Percentage'
underline: True
Label:
text: 'Remove (Y/N)'
underline: True
ScrollView:
size_hint: 1, .5
do_scroll_x: False
padding: 20
spacing: 20
GridLayout:
id: widget_list
cols: 1
spacing: 5
GridLayout:
rows: 1
Label:
text: 'Nothing Here'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .2
Button:
text: 'Add Widget'
on_release: root.add()
Label:
text: ''
Button:
text: 'Remove Widget'
on_release: root.remove()

Kivy: Variable in a class?

I'm currently working to update a bunch of labels with a button press, which I was able to get an answer with through Kivy: How to refernce kv ID in Python?
However, now that I'm in my actual app, I need the functionality to be able to take a user input value and update existing labels. I've modified the example slightly where I'm just taking a user input for a starting number, adding one with each button click and displaying the running total to the right of all of this.
The code I have now is shown below, but the addition of 1 doesn't seem to be processing (it's just staying with the same total after a click). Any ideas why? Thanks very much!
This is the display before the button is clicked (running total is just the user input):
My Python file:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import random
class TestingWidget(BoxLayout):
# This is the kv id of the Label I would like to update
starting_number = StringProperty('Put your Starting Number here')
running_total = StringProperty(str(0))
#default text set
# This is the action I would like to happen when the button is pressed
def button_pressed(self):
self.running_total = str(int(self.running_total) + 1)
class TestButtonApp(App):
def build(self):
return TestingWidget()
if __name__ == '__main__':
TestButtonApp().run()
My kv file:
<TestingWidget>:
BoxLayout:
orientation: 'horizontal'
TextInput:
id: starting_number
hint_text: root.starting_number
Button:
id: add_one_button
text: 'Add 1 to Starting Number'
on_press: root.button_pressed()
Label:
id: running_total
text: starting_number.text
the problem is that Label is bound with TextInput, and if you change Label as you are doing it the TextInput updates it again giving the feeling that it does not change. What you should do is change the text with the on_text event as shown below:
*.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
class TestingWidget(BoxLayout):
starting_number = StringProperty("")
running_total = StringProperty(str(0))
def button_pressed(self):
if self.running_total != "":
self.running_total = str(int(self.running_total) + 1)
def text_changed(self, instance, value):
self.running_total = value
class TestButtonApp(App):
def build(self):
return TestingWidget()
if __name__ == '__main__':
TestButtonApp().run()
*.kv
<TestingWidget>:
BoxLayout:
orientation: 'horizontal'
TextInput:
id: starting_number
hint_text: "Put your Starting Number here"
on_text: root.text_changed(*args)
text: root.starting_number
Button:
id: add_one_button
text: 'Add 1 to Starting Number'
on_press: root.button_pressed()
Label:
id: running_total
text: root.running_total
The solution is to only change your kv File and no changes required to your Python Script. The changes are as follow:
kv File
1. TextInput: Add on_text Event
The textinput’s text is stored in its TextInput.text property. To run a callback when the text changes, do the following.
on_text:
root.running_total = self.text
2. Label: Reference StringProperty, root.running_total
Replace:
text: starting_number.text
with:
text: root.running_total
testbutton.kv
#:kivy 1.10.0
<TestingWidget>:
BoxLayout:
orientation: 'horizontal'
TextInput:
id: starting_number
hint_text: root.starting_number
on_text:
root.running_total = self.text
Button:
id: add_one_button
text: 'Add 1 to Starting Number'
on_press: root.button_pressed()
Label:
id: running_total
text: root.running_total
Output

Categories

Resources