Python Forum
[Kivy] Need to render updated recycler view
Thread Rating:
  • 1 Vote(s) - 3 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Kivy] Need to render updated recycler view
#1
hello, i'm trying to learn kivy. I need to update one part of the screen based on interaction in another part.
Specifically, i need to get on_press input from a recycleView on left half of a boxLayout and update the recycleView on the right side of the boxLayout. It isnt working.

So far my code:
import openpyxl
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
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.boxlayout import BoxLayout

Builder.load_string('''


<SelectableLabel>:
    # Draw a background to indicate selection
    canvas.before:
        Color:
            rgba: (.0, 0.5, .1, .3) if self.selected else (0, 0, 0, 1)
        Rectangle:
            pos: self.pos
            size: self.size

<SelectableLabel2>:
    # Draw a background to indicate selection
    canvas.before:
        Color:
            rgba: (.0, 0.1, .5, .3) if self.selected else (0, 0, 0, 1)
        Rectangle:
            pos: self.pos
            size: self.size

<RootWid>:
    orientation: 'vertical'
    ActionBar:
        pos_hint: {'top':1}
        ActionView:
            use_separator: True
            ActionPrevious:
                title: 'StuCheck'
                with_previous: False
            ActionOverflow:
            ActionButton:
                text: 'Populate list'
                on_press: QuestionsList().populate()
            ActionButton:
                text: 'Sort list'
                on_press: root.sort()
            ActionButton:
                text: 'Clear list'
                on_press: root.clear()
            ActionGroup:
                text: 'More'
                ActionButton:
                    text: 'Insert new item'
                    on_press: root.insert(new_item_input.text)
                ActionButton:
                    text: 'Update first item'
                    on_press: root.update(update_item_input.text)
                ActionButton:
                    text: 'Remove first item'
                    on_press: root.remove()
    BoxLayout:
        orientation: 'horizontal'
        RV:
        QuestionsList:

<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: False
        touch_multiselect: True

<QuestionsList>:
    viewclass: 'SelectableLabel2'
    SelectableRecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
        multiselect: False
        touch_multiselect: True
''')


class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    ''' Adds selection and focus behaviour to the view. '''

class RootWid(BoxLayout):
    pass

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. '''
        self.selected = is_selected
        if is_selected:
            QuestionsList().populate(rv.data[index]['text'])
            print("selection changed to {0}".format(rv.data[index]))
        else:
            print("selection removed for {0}".format(rv.data[index]))

class SelectableLabel2(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
        print (data, 'refreshed')
        return super(SelectableLabel2, 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 is_selected:
            print("selection changed to {0}".format(rv.data[index]))
        else:
            print("selection removed for {0}".format(rv.data[index]))


class QuestionsList(RecycleView):
    def __init__(self, **kwargs):
        super(QuestionsList, self).__init__(**kwargs)
    
    def clear(self):
        self.data = []

    def populate(self, subject):
        wb = openpyxl.load_workbook('sWL.xlsx')
        sheet = wb[subject]
        self.data = []
        for row in range (2, sheet.max_row+1):
            question = sheet.cell(row=row, column=1).value
            self.data.append({'text' : question})
        RootWid()    
        

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        subjects = [some list]
        self.data = []
        for subject in subjects:
            self.data.append({'text':subject})


class TestApp(App):
    def build(self):
        return RootWid()

if __name__ == '__main__':
    TestApp().run()
Please advice. Thanks in advance :)
Reply
#2
if there is a better way of doing this, kindly let me know :)
Reply
#3
hi! refined the code a little, although output is the same. Tried to make the second RV a dynamic class in a widget and then clear the widget and update it everytime a button is clicked on the left. Kindly advice.

import openpyxl
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
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.factory import Factory

Builder.load_string('''


<SelectableLabel>:
	# Draw a background to indicate selection
	canvas.before:
		Color:
			rgba: (.0, 0.5, .1, .3) if self.selected else (0, 0, 0, 1)
		Rectangle:
			pos: self.pos
			size: self.size

<RootWid>:
	orientation: 'vertical'
	ActionBar:
		pos_hint: {'top':1}
		ActionView:
			use_separator: True
			ActionPrevious:
				title: 'StuCheck'
				with_previous: False
			ActionOverflow:
			ActionButton:
				text: 'Populate list'
				on_press: QuestionsList().populate()
			ActionButton:
				text: 'Sort list'
				on_press: root.sort()
			ActionButton:
				text: 'Clear list'
				on_press: root.clear()
			ActionGroup:
				text: 'More'
				ActionButton:
					text: 'Insert new item'
					on_press: root.insert(new_item_input.text)
				ActionButton:
					text: 'Update first item'
					on_press: root.update(update_item_input.text)
				ActionButton:
					text: 'Remove first item'
					on_press: root.remove()
	BoxLayout:
		orientation: 'horizontal'
		RV:
		Middle_c:
			Questions_list:

<Middle_c>:
	orientation: 'horizontal'

<Questions_list@RecycleView>:
	data: [{'text':'123'}]
	viewclass: 'SelectableLabel'
	SelectableRecycleBoxLayout:
		default_size: None, dp(56)
		default_size_hint: 1, None
		size_hint_y: None
		height: self.minimum_height
		orientation: 'vertical'
		multiselect: False
		touch_multiselect: True

<RV>:
	data: self.data
	viewclass: 'SelectableLabel'
	SelectableRecycleBoxLayout:
		default_size: None, dp(56)
		default_size_hint: 1, None
		size_hint_y: None
		height: self.minimum_height
		orientation: 'vertical'
		multiselect: False
		touch_multiselect: True

''')

class RootWid(BoxLayout):
	pass
	

class Middle_c(BoxLayout):

	uq = Factory.Questions_list()

	def update_questions(self, subject):
		uq = Factory.Questions_list()
		self.clear_widgets()
		uq.data = self.populate(subject)
		
	def populate(self, subject):
		wb = openpyxl.load_workbook('sWL.xlsx')
		sheet = wb[subject]
		self.data = []
		for row in range (2, sheet.max_row+1):
			question = sheet.cell(row=row, column=1).value
			self.data.append({'text' : question})
		return self.data	


class RV(RecycleView):
	def __init__(self, **kwargs):
		super(RV, self).__init__(**kwargs)
		subjects = [some list here]
		self.data = []
		for subject in subjects:
			self.data.append({'text':subject})


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):
			Middle_c().update_questions(RV().data[self.index]['text'])
			return True
		if self.collide_point(*touch.pos) and self.selectable:
			Middle_c().update_questions(RV().data[self.index]['text'])
			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 is_selected:
			Middle_c().update_questions(RV().data[self.index]['text'])
			print("selection changed to {0}".format(rv.data[index]))
		else:
			print("selection removed for {0}".format(rv.data[index]))


class TestApp(App):
	def build(self):
		return RootWid()

if __name__ == '__main__':
	TestApp().run()
Reply
#4
realised i could achieve the same effect using a combination of scroll view and generating button widgets on the go. here's the mod code. But it still doesnt work. I dont know why. There is some error with the binding of the on_release call. I'm attaching the traceback as well. Someone please give me a hint, this problem has been bugging me for a long time now..
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import openpyxl

root = Builder.load_string('''
<RootWid>:
	orientation: 'vertical'
	ActionBar:
		pos_hint: {'top':1}
		ActionView:
			use_separator: True
			ActionPrevious:
				title: 'StuCheck'
				with_previous: False
			ActionOverflow:
			ActionButton:
				text: 'Populate list'
			ActionButton:
				text: 'Sort list'
			ActionButton:
				text: 'Clear list'
	BoxLayout:
		orientation: 'horizontal'
		Widget1:
		Widget2:

<Widget1>:
	orientation: 'vertical'
<Widget2>:
	orientation: 'vertical'
''')

class RootWid(BoxLayout):
	pass

class Widget1(BoxLayout):
	def __init__(self, **kwargs):
		super(Widget1, self).__init__(**kwargs)
		subjects = [Subjects go here]
		for subject in subjects:
			btn = Button(text=subject, on_release=self.populate(subject))
			Widget1.add_widget(self, btn)

	def populate(self, subject):
		wb = openpyxl.load_workbook('sWL.xlsx')
		sheet = wb[subject]
		data = []
		for row in range (2, sheet.max_row+1):
			question = sheet.cell(row=row, column=1).value
			data.append(question)
		return self.make_buttons(data)

	def make_buttons(self, data_list):
		for question in data_list:
			btn = Button(text=question)
			Widget2.add_widget(self, btn)


class Widget2(BoxLayout):
	pass
class TestApp(App):
	def build(self):
		return RootWid()

if __name__ == '__main__':
	TestApp().run()			
Error:
Traceback (most recent call last): File "/home/bunny/Desktop/App/new.py", line 69, in <module> TestApp().run() File "/usr/lib/python3/dist-packages/kivy/app.py", line 800, in run root = self.build() File "/home/bunny/Desktop/App/new.py", line 66, in build return RootWid() File "/usr/lib/python3/dist-packages/kivy/uix/boxlayout.py", line 131, in __init__ super(BoxLayout, self).__init__(**kwargs) File "/usr/lib/python3/dist-packages/kivy/uix/layout.py", line 76, in __init__ super(Layout, self).__init__(**kwargs) File "/usr/lib/python3/dist-packages/kivy/uix/widget.py", line 348, in __init__ Builder.apply(self, ignored_consts=self._kwargs_applied_init) File "/usr/lib/python3/dist-packages/kivy/lang/builder.py", line 469, in apply self._apply_rule(widget, rule, rule, ignored_consts=ignored_consts) File "/usr/lib/python3/dist-packages/kivy/lang/builder.py", line 585, in _apply_rule self._apply_rule(child, crule, rootrule) File "/usr/lib/python3/dist-packages/kivy/lang/builder.py", line 582, in _apply_rule child = cls(__no_builder=True) File "/home/bunny/Desktop/App/new.py", line 44, in __init__ btn = Button(text=subject, on_release=self.populate(subject)) File "/usr/lib/python3/dist-packages/kivy/uix/behaviors/button.py", line 121, in __init__ super(ButtonBehavior, self).__init__(**kwargs) File "/usr/lib/python3/dist-packages/kivy/uix/label.py", line 277, in __init__ super(Label, self).__init__(**kwargs) File "/usr/lib/python3/dist-packages/kivy/uix/widget.py", line 352, in __init__ self.bind(**on_args) File "kivy/_event.pyx", line 419, in kivy._event.EventDispatcher.bind AssertionError: None is not callable
It works if i remove the on_release call, but it makes the whole app useless...
Kindle advice.
Reply
#5
You already found where the error happens, when code runs after the mentioned line is removed.
Whose "populate()" method are you attempting to call on line #44?
Reply
#6
i tried moving the populate method around. first i kept it in widget2, cand called it as:
on_release=Widget2().populate()
It didnt work. Then i moved it to Widget1 and tried:
on_release=self.populate()
It behaves the same way in terms of output. Am i making the call the wrong way? Kindly advice.
Reply
#7
What I asked was intended more in the way of "does your widget class even provide the populate method?"
You'll need to do research on that. But I suspect the class does not have the populate method and that's why error message says "None is not callable".
Reply
#8
^_^

The call needed a lambda function, and i was passing it a normal function!
That was the error.
Thank you sir :D

But then running my code puts everything into the same side. I'll attach picture of the output. I want it to come on the right side and i want it to be scrollable.

Here is my semi-fiexed code and its output.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import openpyxl

root = Builder.load_string('''
<RootWid>:
	orientation: 'vertical'
	ActionBar:
		pos_hint: {'top':1}
		ActionView:
			use_separator: True
			ActionPrevious:
				title: 'StuCheck'
				with_previous: False
			ActionOverflow:
			ActionButton:
				text: 'Populate list'
			ActionButton:
				text: 'Sort list'
			ActionButton:
				text: 'Clear list'
	BoxLayout:
		orientation: 'horizontal'
		ScrollView:	
			Widget1:
		ScrollView:
			Widget2:

<Widget1>:
	orientation: 'vertical'
<Widget2>:
	orientation: 'vertical'
''')

class RootWid(BoxLayout):
	pass

class Widget1(BoxLayout):
	def __init__(self, **kwargs):
		super(Widget1, self).__init__(**kwargs)
		subjects = ['Anatomy', 'Biochemistry', 'Physiology', 'Pharmacology', 'Pathology', 'Microbiology', 'FMT', 'Ophthalmology', 'ENT', 'PSM', 'OG', 'Surgery', 'Internal_Medicine', 'Paediatrics', 'Anaesthesiology', 'Radiology', 'Dermatology', 'Orthopaedics', 'Psychiatry']
		for subject in subjects:
			btn = Button(text=subject, on_release= lambda x: self.populate(subject))
			Widget1.add_widget(self, btn)

	def populate(self, subject):
		wb = openpyxl.load_workbook('sWL.xlsx')
		sheet = wb[subject]
		data = []
		for row in range (2, sheet.max_row+1):
			question = sheet.cell(row=row, column=1).value
			data.append(question)
		return self.make_buttons(data)

	def make_buttons(self, data_list):
		self.clear_widgets()
		for question in data_list:
			btn = Button(text=question)
			Widget2().add_widget(self, btn)


class Widget2(BoxLayout):
	pass

class TestApp(App):
	def build(self):
		return RootWid()

if __name__ == '__main__':
	TestApp().run()
İmage

İmage
Reply
#9
Glad you found the source of error!
I get an error (no file found) when following the ling to images. Can you check whether the uploading was successful and retry if necessary?
Reply
#10
İmage


İmage


These are files on my computer. i hope this works..

https://drive.google.com/open?id=1s_5nzF...wVy6gafyWK
https://drive.google.com/open?id=1QpOjd1...aKp4PiDxrt
i uploaded them to drive.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020