Why is my RecycleView Kivy table not refreshing? - python

So whenever I try to delete an existing item from the recycleview, I have to close the application and open it again to see the deletion. I want to make it so it will automatically refresh the recycleview and show the deletion.
To navigate the application, start by going to Record new Automation, then type a name at the top and press Save in the bottom right hand corner. Then you must close the application and reopen it before pressing Open old Automation. Then click on the item you made and press Delete at the bottom. As you can see, the data is deleted, but the visual has no updated.
kek.py
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import BooleanProperty
from kivy.properties import NumericProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.label import Label
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.storage.jsonstore import JsonStore
store = JsonStore('automations.json')
class CustomScreen(Screen):
hue = NumericProperty(0)
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
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. '''
global selected
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
selected = '{0}'.format(rv.data[index])[10:-2]
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in store]
class RVScreen(Screen):
def storedelete(self):
try:
store.delete(selected)
refresh_view_attrs(self, rv, index, data)
except Exception:
print("Select Something")
class NewAutoScreen(Screen):
def recordname(self, inputname):
if inputname:
try:
store.put(inputname, )
except Exception:
print("error")
class AutrumApp(App):
def build(self):
root = ScreenManager()
root.add_widget(CustomScreen(name='Autrum'))
root.add_widget(RVScreen(name='RVScreen'))
root.add_widget(NewAutoScreen(name='NewAutoScreen'))
return root
if __name__ == '__main__':
AutrumApp().run()
autrumapp.kv
#:kivy 2.0
<CustomScreen>:
hue: 4
canvas:
Color:
hsv: self.hue, .5, .3
Rectangle:
size: self.size
Label:
font_size: 42
text: root.name
Button:
text: 'Record new Automation'
size_hint: None, None
pos_hint: {'right': 1}
size: 200, 75
on_release:
root.manager.current = "NewAutoScreen"
root.manager.transition.direction = 'left'
root.manager.transition.duration = .25
Button:
text: 'Open old Automation'
size_hint: None, None
pos_hint: {'left': 1}
size: 200, 75
on_release:
root.manager.current = "RVScreen"
root.manager.transition.direction = 'right'
root.manager.transition.duration = .25
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
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
<RVScreen>:
id: old
FloatLayout:
orientation: "vertical"
RV:
Button:
text: 'Back'
pos_hint: {'right': 1}
size_hint: .1, .1
on_release:
root.manager.current = "Autrum"
root.manager.transition.direction = 'left'
root.manager.transition.duration = .25
Button:
text: 'Open'
pos_hint: {'center_x': .05}
size_hint: .1, .1
Button:
text: 'Edit'
pos_hint: {'center_x': .15}
size_hint: .1, .1
Button:
text: 'Delete'
pos_hint: {'center_x': .25}
size_hint: .1, .1
on_press:
old.storedelete()
<NewAutoScreen>:
id: recorder
FloatLayout:
Button:
text: 'Back'
size_hint: .1, .1
pos_hint: {'left': 1, 'bottom': 1}
on_release:
root.manager.current = "Autrum"
root.manager.transition.direction = 'right'
root.manager.transition.duration = .25
TextInput:
id: entry
size_hint: .5, .05
pos_hint: {'center_x': .5, 'top': 1}
multiline: False
text: ""
Button:
size_hint: .1, .1
pos_hint: {'right': 1, 'bottom': 1}
text: "Save"
on_press: recorder.recordname(entry.text)
Button:
size_hint: .2, .1
pos_hint: {'center_x': .4, 'bottom': 1}
text: "Start Recording"
Button:
size_hint: .2, .1
pos_hint: {'center_x': .6, 'bottom': 1}
text: "Stop Recording"

You must also update the data attribute of the RecycleView to reflect the deletion. You can do that by first modifying the kv to provide easy access to the RecycleView like this:
<RVScreen>:
rv: rv # handy reference to the RV
id: old
FloatLayout:
orientation: "vertical"
RV:
id: rv # the id referenced above
And using the reference to the RecycleView in your storedelete() method:
class RVScreen(Screen):
def storedelete(self):
try:
store.delete(selected)
self.rv.data = [{'text': str(x)} for x in store] # Update RecycleView
except Exception:
print("Select Something")

Related

Using Kivy, how do I have a checkbox's active (or inactive) status rotate (or not rotate) an image on the second screen?

On the first screen, I have a checkbox that asks the user if they want their image in landscape mode. On the second screen, my code currently uses a canvas and rotates the image 90 degrees. If that box is not checked, that means the user wants the image to be portrait mode, and I need that canvas to be cleared.
my.kv
WindowManager:
MainWindow:
SecondWindow:
<MainWindow>:
id: main_window
name: "main"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
padding: 50
Label:
text: "Email"
color: 0,0,0,1
font_size: 32
BoxLayout:
orientation: "horizontal"
Label:
text: "Email Address:"
color: 0,0,0,1
TextInput:
size_hint_y: None
pos_hint: {'center_y': .5}
height: 38
multiline: True
padding: 10
BoxLayout:
orientation: "horizontal"
Label:
text: "Display Landscape Mode?"
color: 0,0,0,1
CheckBox:
id: checkbox_confirm_mode
on_active:
root.checkbox_click_mode(self, self.active)
pos_hint: {'center_x': .5}
BoxLayout:
orientation: "horizontal"
Label:
text: "Stretch image to fill screen?"
color: 0,0,0,1
CheckBox:
id: checkbox_confirm_stretch
on_active:
root.checkbox_click_stretch(self, self.active)
pos_hint: {'center_x': .5}
BoxLayout:
orientation: "horizontal"
Label:
text: "I double-checked that my email is typed correctly:"
color: 0,0,0,1
CheckBox:
id: checkbox_confirm_email
on_active:
root.checkbox_click(self, self.active)
root.disable_button()
pos_hint: {'center_x': .5}
BoxLayout
orientation: "vertical"
Button:
id:submit_button
text: "Submit"
disabled: True
size_hint: (0.2, None)
pos_hint: {'center_x': .5}
height: 50
on_release:
app.root.current = "second"
root.manager.transition.direction = "left"
<SecondWindow>:
id: second_window
name: "second"
canvas:
Rotate:
angle: 90
origin: self.center
Image:
source: 'Troy.png'
keep_ratio: True
allow_stretch: False
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
class MainWindow(Screen):
def on_pre_enter(self):
Window.size = (750,400)
Window.clearcolor = (1, 1, 1, 1)
def checkbox_click(self, instance, value):
return value
def checkbox_click_mode(self, instance, value):
return value
def checkbox_click_stretch(self, instance, value):
return value
def clear_canvas(self):
self.canvas.clear()
return
def disable_button(self):
if self.ids.checkbox_confirm_email.active == False:
self.ids.submit_button.disabled = True
else:
self.ids.submit_button.disabled = False
class SecondWindow(Screen):
def on_pre_enter(self):
Window.size = (500,500)
Window.clearcolor = (0,0,0,0)
pass
class WindowManager(ScreenManager):
pass
class MyMainApp(App):
def build(self):
return kv
kv = Builder.load_file("my.kv")
if __name__ == "__main__":
MyMainApp().run()
For your current design you can control it from outside as,
First set properties in SecondWindow for setting the angle, stretch instruction etc. and then control them depending on the state of those (weak reference of the check boxes) checkbox_confirm_mode etc. from the MainWindow as,
class SecondWindow(Screen):
angle = NumericProperty(0)
allow_strech = BooleanProperty(False)
def on_pre_enter(self):
screen = self.manager.get_screen("main")
angle_value = screen.ids.checkbox_confirm_mode.active
stretch_value = screen.ids.checkbox_confirm_stretch.active
self.angle = 90*angle_value # Less calculation.
self.allow_strech = stretch_value # Less calculation.
Window.size = (500,500)
...
Now in the kvlang of SecondWindow,
<SecondWindow>:
id: second_window
name: "second"
canvas:
Rotate:
angle: root.angle
origin: self.center
Image:
source: 'Troy.png'
keep_ratio: True
allow_stretch: root.allow_strech

Kivy widget generation very slow

I'm making a kivy app to find the rhyming words for a word entered by the user. It displays all the rhyming words as OneLineListItems in an MDList which is inside a kivy RecycleView. On clicking on one of these OneLineListItems it displays the definition of the word on the right-hand side of the screen. However, when I click on a OneLineListItem its definition takes very long to appear and sometimes it lags so badly that the app closes. Am I doing something wrong or is it just my computer? Code below:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.label import MDLabel
from kivymd.uix.list import OneLineListItem
import pronouncing
import enchant
from PyDictionary import PyDictionary
dictionary = PyDictionary()
d = enchant.Dict("en_US")
kv = """
Screen:
input:input
scroll:scroll
word:word
defs:defs
MDGridLayout:
rows: 1
cols: 2
MDGridLayout:
rows: 2
cols: 1
MDFloatLayout:
MDTextField:
id:input
size_hint: (.4, None)
height: 26
multiline: False
on_text_validate: app.rhyme()
hint_text: "Search"
mode: "rectangle"
pos_hint: {"center_x": .25, "center_y": .85}
font_name: "DejaVuSans.ttf"
text_size: self.size
MDFloatLayout:
RecycleView:
size_hint: 0.85,1.5
bar_width: dp(15)
bar_height: dp(40)
scroll_type: ["content"]
pos_hint: {"center_x": 0.45, "center_y": 0.93}
MDList:
id: scroll
MDBoxLayout:
id:defs
orientation: "vertical"
md_bg_color: 0, 1, 0.2, 1
MDLabel:
id: word
text: ""
text_size: self.size
"""
class RhymeApp(MDApp):
played = []
x_turn = True
def build(self):
self.screen = Builder.load_string(kv)
return self.screen
def rhyme(self):
raw_rhymes = pronouncing.rhymes(self.screen.input.text)
rhymes = []
[rhymes.append(x) for x in raw_rhymes if x not in rhymes and x[-1] != "." and d.check(x)]
self.screen.scroll.clear_widgets()
for i in rhymes:
self.screen.scroll.add_widget(
OneLineListItem(text=f"{i}".capitalize(), on_release=self.dictionary)
)
def dictionary(self, btn):
nl = '\n'
self.screen.defs.clear_widgets()
self.screen.word.text = btn.text.capitalize()
meaning = dictionary.meaning(btn.text, disable_errors=True)
if meaning is None:
self.screen.defs.add_widget(
MDLabel(text=f"Sorry, no meaning for that word.",
text_size="self.size")
)
else:
for key in meaning:
self.screen.defs.add_widget(
MDLabel(text=f"Part of speech: {key} {nl}Meaning: {nl}{nl}{meaning[key][0].capitalize()}.",
text_size="self.size")
)
if __name__ == "__main__":
RhymeApp().run()
Can someone please help?
First create a custom class for the data-class like following:
class ListItem(OneLineListItem):
# Here define all the necessary attrs., methods apart from the defaults (if you need any).
Now in your .kv initialize RecycleView as,
RecycleView:
id: scroll
#size_hint: 0.85,1.5
bar_width: dp(15)
bar_height: dp(40)
scroll_type: ["content"]
#pos_hint: {"center_x": 0.45, "center_y": 0.93}
viewclass: "ListItem"
RecycleBoxLayout:
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Now you are ready to feed RecycleView with you data as,
def rhyme(self):
...
self.screen.ids.scroll.data = [
{"text" : f"{i}".capitalize()}
for i in rhymes]

MDLabels stacked in one place (one above another)

I've some problems with multiple MDLabels in BoxLayout (that is contains by AnchorLayout), so all the MDLabel objects are stacked in one place on the screen!
I dont know how to make them centered and grouped like a list (with spacing and e.g.)
Please, help me with solving that problem!
Thanks a lot and sorry for bad english.
There is my main.py
from kivy.app import App
from kivymd.theming import ThemeManager
from kivymd.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.metrics import dp, sp, pt
def toast(text):
from kivymd.toast.kivytoast import toast
toast(text)
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu_items = [
{
"viewclass": "MDMenuItem",
"text": "text%d" % i,
"callback": self.callback,
}
for i in range(1, 3)
]
self.menu_button = None
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def callback(self, *args):
toast(args[0])
class MainApp(App):
title = "KivyMD MDDropdownMenu Demo"
theme_cls = ThemeManager()
def build(self):
return MyScreen()
if __name__ == "__main__":
MainApp().run()
And there is my main.kv file contains:
#:import MDDropdownMenu kivymd.menus.MDDropdownMenu
#:import MDRaisedButton kivymd.button.MDRaisedButton
#:import MDLabel kivymd.label.MDLabel
<MDMenuItem>:
on_release:
app.root.change_variable(self.text)
app.root.menu_button.text = self.text
<MyScreen>:
name: 'myscrn'
AnchorLayout:
anchor_y: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.1, 0.5
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
spacing: dp(10)
MDRaisedButton:
id: mainbutton
size_hint: None, None
size: 3 * dp(48), dp(48)
text: 'MDButton1'
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
opposite_colors: True
on_release:
root.menu_button = mainbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
MDRaisedButton:
id: secondbutton
size_hint: None, None
size: 3 * dp(48), dp(48)
text: 'MDButton2'
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
opposite_colors: True
on_release:
root.menu_button = secondbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
AnchorLayout:
anchor_y: 'top'
BoxLayout:
orientation: 'vertical'
size_hint: 0.95, 0.5
padding: [0, 0, 0, 0]
spacing: dp(5)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
MDLabel:
font_size: dp(12)
text: '123'
MDLabel:
font_size: dp(22)
text: '456'
Woops, looks like a simple mistake. Your indentation on KV Lang is incorrect. You didn't nest your labels into BoxLayout correctly.
AnchorLayout:
anchor_y: 'top'
BoxLayout:
orientation: 'vertical'
size_hint: 0.95, 0.5
padding: [0, 0, 0, 0]
spacing: dp(5)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
MDLabel:
font_size: dp(12)
text: '123'
MDLabel:
font_size: dp(22)
text: '456'"""

kivy Error when trying to add a button on gridlayot in a different class

My app wants to make a list from the strings typed in the TextInput field and show them in the gridlayout in the middle of the window after pressing the button "Buscar". I'm sharing variables and functions between classes but when i try to add a new button with the TextInput.text inside the gridlayout shows the error message:
"AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'add_widget'"
Thank you
The interface looks like this
This is my .py file
from kivy.app import App
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, NumericProperty, StringProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
class Lista(ScrollView):
lista_repuestos = ObjectProperty()
class CustomWidget(FloatLayout):
campo_de_busqueda_text_input = ObjectProperty()
repuesto = StringProperty('')
def submit_repuesto(self):
self.repuesto = self.campo_de_busqueda_text_input.text
Lista.lista_repuestos.add_widget(Button(text=self.repuesto))
class CustomWidgetApp(App):
def build(self):
return CustomWidget()
if __name__ == "__main__":
CustomWidgetApp().run()
this is my .kv file
CustomWidget:
<CustomWidget>:
campo_de_busqueda_text_input: campodebusqueda
TextInput:
id: campodebusqueda
size_hint: .7, .1
pos_hint: {"x": .15, "y": .85}
Button:
on_release: root.submit_repuesto()
size_hint: .1, .1
pos_hint: {"x": .85, "y": .85}
text: "Buscar"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .15}
text: "DescripciĆ³n"
text_size: self.size
halign: "left"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .10}
text: "Referencia"
text_size: self.size
halign: "left"
Label:
size_hint: .15, .05
pos_hint: {"x": .05, "y": .05}
text: "Cantidad"
text_size: self.size
halign: "left"
<Lista>:
lista_repuestos: listarepuestos
GridLayout:
id: listarepuestos
size_hint: .7, .6
pos_hint: {"x": .15, "y": .25}
cols: 1
row_default_height: 50
row_force_default: True
padding: 5
height: self.minimum_height
size_hint_y: None
Your line in the CustomWidget:
Lista.lista_repuestos.add_widget(Button(text=self.repuesto))
should reference an instance of Lista, not the class itself.

kivy dynamically add and remove dropdown entries

my app should dynamically add and remove items in the dropdown menu. The adding of buttons works, but I didn't get it working to remove the added buttons.
py:
import kivy
kivy.require('1.7.2') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
class HomeScreen(Screen):
addButton = ObjectProperty(None)
removeButton = ObjectProperty(None)
top_layout = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(HomeScreen, self).__init__(*args, **kwargs)
def resetBoxes(self):
self.ids.btn_release.text = "Release"
self.ids.btn_version.text = "Version"
self.ids.btn_device.text = "Device"
return
def removeButtonPressed(self):
self.dropdown.remove_widget(self)
return
def addButtonPressed(self):
self.dropdown = DropDown()
notes = ['Features', 'Suggestions', 'Abreviations', 'Miscellaneous']
for note in notes:
btn = Button(text=note, size_hint_y=None, height=20)
btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(btn)
self.ids.btn_release.bind(on_release=self.dropdown.open)
self.dropdown.bind(on_select=lambda instance, x: setattr(self.ids.btn_release, 'text', x))
return
def btn_releaseClicked(self):
self.ids.btn_release.text="clicked"
def btn_versionClicked(self):
self.ids.btn_version.text="clicked"
def btn_deviceClicked(self):
self.ids.btn_device.text="clicked"
class dropdApp(App):
def build(self):
return HomeScreen()
if __name__ == '__main__':
dropdApp().run()
kv:
<HomeScreen>:
id: home_screen
addButton: addButtonID
removeButton: removeButtonID
top_layout: topLayoutID
orientation: 'vertical'
FloatLayout:
size_hint: 1, 1
Button:
id: addButtonID
text: 'Add'
pos_hint: {'x': .35, 'y': .70}
size_hint: .3, .08
valign: 'middle'
halign: 'center'
text_size: self.size
on_release: root.addButtonPressed()
Button:
id: removeButtonID
text: 'Remove'
pos_hint: {'x': .35, 'y': .60}
size_hint: .3, .08
valign: 'middle'
halign: 'center'
text_size: self.size
on_release: root.removeButtonPressed()
BoxLayout:
id: topLayoutID
size_hint: 1, .05
pos_hint: {'x': 0, 'y': .90}
Button:
id: btn_release
text: 'Release'
on_press: root.btn_releaseClicked()
Button:
id: btn_version
text: 'Version'
on_press: root.btn_versionClicked()
Button:
id: btn_device
text: 'Device'
on_press: root.btn_deviceClicked()
Use clear_widgets() to get the result you want - removing all buttons from dropdown - or store some reference to those buttons in dropdown somewhere and remove only desired ones.
def removeButtonPressed(self):
self.dropdown.clear_widgets()

Categories