Spinner at bottom of recyleview in kivy and kivymd - python

I want to user to see the spinner when they reach the bottom of the Recycleview..
In this bellow code the spinner is visible in the viewport this is the issue so there is some code below..
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.scrollview import ScrollView
from kivy.uix.screenmanager import Screen
Builder.load_string('''
<MyScreen>:
ScrollView:
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:root.height
RV:
size_hint_y:None
height:root.height
viewclass: 'Button'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
size_hint_y:None
height:self.minimum_height
MDSpinner:
size_hint:None,None
width:dp(40)
height:dp(20)
pos_hint:{"center_x":.5}
''')
class MyScreen(Screen):
pass
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(25)]
class TestApp(MDApp):
def build(self):
return MyScreen()
if __name__ == '__main__':
TestApp().run()
Thanks in advance..

If you want the last item in the RecycleView to be a spinner, you cannot do that with a viewclass of Button (unless you can make a Button look like a Spinner). However, you can design your own viewclass that can look like either a Button or a Spinner. So defining a custom viewclass like this:
class MyViewClass(RelativeLayout):
text = StringProperty('') # text for the Button
spinner = BooleanProperty(False) # if True, show a Spinner instead
Then add a rule for it in your kv:
<MyViewClass>:
Button:
text: root.text
opacity: 0 if root.spinner else 1
MDSpinner:
pos_hint:{"center_x":.5}
opacity: 1 if root.spinner else 0
active: True if root.spinner else False
size_hint: None, None
height: root.height
width: root.height
The above rule uses opacity to determine whether the Button or the Spinner is visible. And the active attribute of the MDSpinner is used to avoid having a spinning MDSpinner running in the items where it is not visible.
Then just specify MyViewClass as the viewclass:
viewclass: 'MyViewClass'
And remove the old MDSpinner from your kv.
The last thing to do is to adjust your data:
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'spinner': False} for x in range(25)]
self.data.append({'text': '', 'spinner': True})
The data now sets spinner to False for all the entries, and adds an extra entry with spinner set to True.

Related

Inaccurate hover behavior with KivyMD

I am using Kivy and KivyMD and running into an issue when combining a Kivy RecycleView with a KivyMD HoverBehavior. What happens is that the bottom element in my list (inside the recycle view) behaves the way I expect it to with the hover behavior, correctly detecting when the mouse enters and exits the bounding box of the list element. However, as I move up higher on the list, the element that is being detected as hovered gets further and further from where my mouse pointer actually is.
For example, when hovering over element 10 of 10, the on_enter and on_leave methods of the hover behavior correctly show that, but I would have to hover over element 8 to get element 9 to be detected, 6 to get 8 to be detected, and so on.
Here is the code I have so far, pared down as far as I can to be able to reproduce the issue. You can install the dependencies (Kivy and KivyMD) and run the code. From there, just try to hover over various elements in the list and notice how the wrong checkboxes appear checked and the wrong element numbers output to the console.
What might be causing this type of behavior?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, StringProperty
from kivymd.uix.behaviors import HoverBehavior
from kivy.uix.relativelayout import RelativeLayout
Builder.load_string('''
<RV>:
viewclass: 'TestItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<TestItem>:
canvas:
Color:
rgba: .8, .8, .8, 1
Line:
points: 0,0,self.width,0
width:1
CheckBox:
id: "checkbox"
active: True if root.status == 'completed' else False
Label:
size_hint_x: .86
text: root.title
''')
class TestItem(RecycleDataViewBehavior, RelativeLayout, HoverBehavior):
''' Add selection support to the Label '''
status = StringProperty()
title = StringProperty()
def on_enter(self, *args):
self.children[1].background_checkbox_normal ="atlas://data/images/defaulttheme/checkbox_on"
print("Entering", self.title)
def on_leave(self, *args):
self.children[1].background_checkbox_normal ="atlas://data/images/defaulttheme/checkbox_off"
print("Leaving", self.title)
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'title': str(x), 'status': "not started"} for x in range(10)]
self.refresh_from_data()
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
The HoverBehavior does not appear to work correctly for RelayiveLayout. Try changing
class TestItem(RecycleDataViewBehavior, RelativeLayout, HoverBehavior):
to
class TestItem(RecycleDataViewBehavior, HoverBehavior, FloatLayout):
That will also require refactoring the <TestItem> rule in your kv. Something like:
<TestItem>:
canvas:
Color:
rgba: .8, .8, .8, 1
Line:
points: self.x, self.y,self.width,self.y
width:1
CheckBox:
id: "checkbox"
size_hint_x: .14
pos_hint: {'x': 0.86}
center_y: root.center_y
active: True if root.status == 'completed' else False
Label:
size_hint_x: .86
pos_hint: {'x': 0}
center_y: root.center_y
text: root.title

When I click a button different button flashes in kivy recycleview

So, I know there have been couple of similar questions but I haven't found a solution on any of those questions.
When I click one button in my RecycleView screen in my kivy app different button flashes. I haven't changed anything about buttons so I don't see any mistake in my code but there might be something that I have not seen.
Here is the code:
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.recycleview import RecycleView
from kivy.app import App
class Kupci_sve(RecycleView, Screen):
def __init__(self, **kwargs):
super(Kupci_sve, self).__init__(**kwargs)
self.data = [{"text": str(i)} for i in range(20)]
self.bar_width = 8
self.scroll_type = ['bars']
kv = Builder.load_string('''
<Kupci_sve>:
name: 'kupci_sve'
viewclass: 'Button'
RecycleBoxLayout:
default_size: (None, 100)
default_size_hint: (1, None)
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
''')
class app(App):
def build(self):
return Kupci_sve()
if __name__ == '__main__':
app().run()
I import this screen to my main.py file and run it from there but I didn't paste that code because I think it has nothing to do with this problem. If you need any other info, just tell me. Thank you.
Without a minimal, complete, reproducible example, I suspect your problem may be in making your Kupci_sve class extend both Screen and RecycleView. A better approach would be to just extend Screen and simply include a RecycleView in the kv rule for <Kupci_sve>.
Here is what I mean:
<Kupci_sve>:
name: 'kupci_sve'
RecycleView:
id: rv # added id
bar_width: 8
scroll_type: ['bars']
viewclass: 'Button'
RecycleBoxLayout:
default_size: (None, 100)
default_size_hint: (1, None)
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
And the Kupci_sve class becomes:
class Kupci_sve(Screen):
def __init__(self, **kwargs):
super(Kupci_sve, self).__init__(**kwargs)
self.ids.rv.data = [{"text": str(i), "on_release":partial(self.butt_release, i)} for i in range(20)]
def butt_release(self, index):
print('butt_release:', index)

How to replace deprecated ListView in kivy?

I am studying kivy for now and reading Creating Apps with kivy. Author use follwing code:
.kv
AddLocationForm:
<AddLocationForm#BoxLayout>:
orientation : 'vertical'
BoxLayout:
pos : 100, 100
height : '40dp'
size_hint_y : None
TextInput:
size_hint_x : 50
Button:
text : 'search'
size_hint_x : 25
Button:
text : 'current location'
size_hint_x : 25
ListView:
item_strings: ["Palo Alto, MX", "Palo Alto, US"]
and .py
from kivy.app import App
class FirstKivyApp(App):
pass
FApp = FirstKivyApp()
FApp.run()
But as much as I understand ListView is deprecated now. It is supposed to be changed on RecycleView now. I've checked for some solutions but they don't make sense for me because use things I've not accomplished yet. I tried to use
RecycleView:
data : ["Palo Alto, MX", "Palo Alto, US"]
instead of ListView but it isn't shown whereas I can access the data through id and ObjectProperty. Is there a way to display data in simplier way, than using ScreenManager, constructing classes and referring to build method? For example something like in author's or my example, but working. Adding RecycleBoxLayout didn't work too.
When calling your Recycleview in kivy, make sure it has an appropriate id, this can then be called in your python code with data applied with the following:
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
The kivy docs site has a great example of how to implement RecycleView, found here: https://kivy.org/doc/stable/api-kivy.uix.recycleview.html
Try this for a basic example showing how to use ScreenManager with RecycleView:
import kivy
# import main libraries, import object types and layouts
from kivy.config import Config
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.properties import (StringProperty, ObjectProperty,
OptionProperty, NumericProperty, ListProperty, BooleanProperty)
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.behaviors import FocusBehavior
# import screen features
from kivy.uix.label import Label
from kivy.uix.button import Button
# load in kv file, deals with cosmetics of each screen
kv = """
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if root.selected else (0, 0, 0, .1)
Rectangle:
pos: self.pos
size: self.size
<SelectScreen>:
BoxLayout:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
orientation: 'vertical'
GridLayout:
cols: 2
rows: 1
size_hint_y: .25
height: dp(54)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Select all'
font_size: 24
on_release:
controller.select_all(len(rv.data))
Button:
text: 'Deselect all'
font_size: 24
on_release:
controller.clear_all()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
id: controller
key_selection: 'selectable'
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
spacing: dp(2)
"""
Builder.load_string(kv)
# Adds selectable labels to lists (recycleview)
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
def get_nodes(self):
nodes = self.get_selectable_nodes()
if self.nodes_order_reversed:
nodes = nodes[::-1]
if not nodes:
return None, None
selected = self.selected_nodes
if not selected: # nothing selected, select the first
self.select_node(nodes[0])
return None, None
if len(nodes) == 1: # the only selectable node is selected already
return None, None
last = nodes.index(selected[-1])
self.clear_selection()
return last, nodes
def select_all(self, num):
print(num)
#print(len(self.ids.rv.data))
last, nodes = self.get_nodes()
print(nodes)
for x in range(num):
print(x)
self.select_node(x)
def clear_all(self):
self.clear_selection()
# Create action on selectable list, for example apply selection remembers previous selection and saves to sqlite db
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
index = int
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
if self.selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
# Screen for selecting from recycleview
class SelectScreen(Screen):
interfacename = StringProperty()
def __init__(self, **kwargs):
super(SelectScreen, self).__init__(**kwargs)
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
def select(self): # selects participants from list new value to database
print (self.ids.rv.data[val[0]]['text'])
def selectall(self):
for num in range(len(self.ids.rv.data)):
SelectableLabel.apply_selection(self, self.ids.rv, num, True)
# screen manager
sm = ScreenManager()
sm.add_widget(SelectScreen(name='select')) # main starting menu
# Build the app return screenmanager
class RecycleviewApp(App):
def build(self):
return sm
if __name__ == '__main__':
RecycleviewApp().run()
Example image of application and terminal printing application and removal of RecycleView selection:
I founded out the issue.
Firstly, format of data must be suitable. As twicejiggled showed it must be list of dicts.
Secondly, as much as I understood, some layout is neede for showing. For example this code in .kv will work:
RecycleView:
data : [{'text':'text'} for x in range(50)]
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'

Passing Kivy Widget to another class

I am building a dynamic user interface using Python and Kivy. Because I need to add and remove widgets dynamically I want to use a separate class to handle adding and removing widgets from a GridLayout. I called this class LayoutManager.
The GridLayout is defined in my kv-File (id: "config_box_layout"). Inside my root widget class in my python code I am referencing the GridLayout via id. This is working properly. This reference is passed to the LayoutManager in the constructor. I tried passing it via ObjectProperty or GridLayout.
The problem is that I always end up with this kind of error if I try to remove widgets from the Layout:
'kivy.properties.ObjectProperty' object has no attribute 'remove_widget'
If I try to remove a widget in the save-method inside my Tab class using config_box_layout.remove_widget(some newly created Label) it's working properly.
I think the problem is that Kivy and all the kv-widgets are weakly-referenced and handling those references over to other classes seem to be not the intended use case.
I try to seperate classes to avoid doing all the coding stuff in one big fat main Layout class.
Looking forward to any help! :)
main.py
import kivy
from util.layoutManager import LayoutManager
from kivy.app import App
kivy.require('1.11.1')
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from functools import partial
class ChooseDirectoryDialog(FloatLayout):
add = ObjectProperty(None)
cancel = ObjectProperty(None)
save = ObjectProperty(None)
class Tab(TabbedPanel):
config_add_src = ObjectProperty()
ingest_layout = ObjectProperty()
configManager = ConfigManager()
config_box_layout = ObjectProperty()
layoutManager = LayoutManager(config_box_layout)
def add_src(self):
content = ChooseDirectoryDialog(cancel=self.cancel, save=self.save)
self._popup = Popup(title="Add Source", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def cancel(self):
self._popup.dismiss()
def delete_source(self, description, widget):
self.configManager.delete_source(description)
self.remove_source_from_ui(description)
def remove_source_from_ui(self, description):
self.layoutManager.remove_configuration(description)
def save(self, srcpath, destpath, description):
desc_label = Label(text=description)
desc_label.size_hint_y = None
desc_label.height = 30
self.config_box_layout.add_widget(desc_label)
srcpath_label = Label(text=srcpath)
srcpath_label.size_hint_y = None
srcpath_label.height = 30
self.config_box_layout.add_widget(srcpath_label)
destpath_label = Label(text=destpath)
destpath_label.size_hint_y = None
destpath_label.height = 30
self.config_box_layout.add_widget(destpath_label)
deleteButton = Button(text="Quelle löschen")
deleteButton.size_hint_y = None
deleteButton.height = 30
deleteButton.bind(on_press=partial(self.delete_source, description))
self.config_box_layout.add_widget(deleteButton)
self.layoutManager.add_configuration(description,
desc_label, srcpath_label, destpath_label, deleteButton)
self.configManager.add_source(description, srcpath, destpath)
self._popup.dismiss()
def copyToDestination(self, srcpath, destpath):
pass
class AutoIngest(App):
def build(self):
return Tab()
if __name__ == '__main__':
#Builder.load_file('autoingest.kv')
AutoIngest().run()
autoingest.kv
#:kivy 1.11.1
<Tab>:
do_default_tab: False
config_add_button: add_button
config_box_layout: config_box
TabbedPanelItem:
text: 'Konfiguration'
TabbedPanel:
tab_width: 200
do_default_tab: False
TabbedPanelItem:
text: 'Quellen verwalten'
StackLayout:
orientation: "lr-tb"
padding: 10
Button:
size_hint: .2, .1
id: add_button
text: 'Quelle hinzufügen'
on_press: root.add_src()
GridLayout:
id: config_box
cols: 4
Label:
size_hint_y: None
height: 30
text: "Bezeichnung"
Label:
size_hint_y: None
height: 30
text: "Quell-Pfad"
Label:
size_hint_y: None
height: 30
text: "Ziel-Pfad"
Label:
size_hint_y: None
height: 30
text: "Aktionen"
<ChooseDirectoryDialog>:
text_input: text_input
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
Label:
size_hint_y: None
height: 30
text: "Bezeichnung"
TextInput:
id: text_input
size_hint_y: None
height: 30
multiline: False
Label:
size_hint_y: None
height: 30
text: "Quellverzeichnis auswählen"
FileChooserListView:
id: source_chooser
Label:
size_hint_y: None
height: 30
text: "Zielverzeichnis auswählen"
FileChooserListView:
id: destination_chooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Add"
on_release: root.save(source_chooser.path, destination_chooser.path, text_input.text)
layoutManager.py
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
class LayoutManager:
#I alrey tried to pass the GridLayout itself. This didn't work either.
def __init__(self, configlayout: ObjectProperty):
self.configurations = {}
self.configlayout = configlayout
def remove_configuration(self, description):
widgets = self.configurations.get(description)
for x in widgets:
self.configlayout.remove_widget(x)
def add_configuration(self, description, *widgets):
self.configurations[description] = {'widgets': widgets}
I believe the problem is that the Tab instance ids have not been setup when your lines:
config_box_layout = ObjectProperty()
layoutManager = LayoutManager(config_box_layout)
are executed, so config_box_layout is not yet set.
You can force the creation of the LayoutManager to delay until the ids are created using kivy.clock in the build() method of the App:
class AutoIngest(App):
def build(self):
Clock.schedule_once(self.setup_layoutManager)
return Tab()
def setup_layoutManager(self, dt):
self.root.layoutManager = LayoutManager(self.root.config_box_layout)
Also, the line:
layoutManager = LayoutManager(config_box_layout)
can be removed from the Tab class.
Once that is working you will discover issues in your remove_configuration() method of LayoutManager.

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()

Categories

Resources