ats75
ats75

Reputation: 21

Difficulties selecting/unselecting all Viewclasses in a RecycleView

I am trying to build a function that helps the user select/unselect all viewclass-instances in a RecycleView.

The Problem is that while i can change the "selected" attribute of every viewclass in the RecycleView to True or False.

like this for example:


for viewclass in recycle_view.data:
    viewclass['selected'] = False 

-the "selected" color of the viewclasses do not update themselves, meaning it retains the "selected" color, eventhough it's technically unselected. I wonder how i can refresh the recycleview in a way, that it also the takes the color of the viewclasses into account.

This is the code that handles the creation of the vieclasses "UserCard", with all it's selecting methods:



class UserCard(RecycleDataViewBehavior, MDBoxLayout):

    selected_cards = False

    name = StringProperty()
    price = StringProperty()
    date = StringProperty()
    category = StringProperty()
    callback = ObjectProperty(lambda x: x)
    rview = ObjectProperty()

    index = None
    current_card_index = None

    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)
    selected_list_cards = []
    selected_list_index = []
    num_selected_cards = 0

    color_normal = (18/250, 18/250, 18/250, 1)
    color_select = (25/250, 25/250, 25/250, 1)

    def refresh_view_attrs(self, rv, index, data):
        self.index = index

        self.selected = data["selected"]
        return super().refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):

        if super().on_touch_down(touch):
            return True

        if self.collide_point(*touch.pos) and self.selectable:
            Clock.schedule_once(self.callback)
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        self.selected = is_selected
        rv.data[index]["selected"] = is_selected
        screen_products = MDApp.get_running_app().root.get_screen('float_layout_products')

        if is_selected:
            rv.data[index]['md_bg_color'] = (25/250, 25/250, 25/250, 1)
            if index not in UserCard.selected_list_index:
                UserCard.selected_list_index.append(index)
                UserCard.current_card_index = index
            if index not in UserCard.selected_list_cards:
                UserCard.selected_list_cards.append(self)
        else:
            rv.data[index]['md_bg_color'] = (18/250, 18/250, 18/250, 1)
            if index in UserCard.selected_list_index:
                UserCard.selected_list_index.remove(index)
            if index in UserCard.selected_list_cards:
                UserCard.selected_list_cards.remove(self)

        screen_products.ids.items_selected.text = str(
            len(UserCard.selected_list_index))

    def on_tap_card(self, *args):

        screen_products = MDApp.get_running_app().root.get_screen('float_layout_products')
        recycle_view = screen_products.ids.recycle_view

        datas = [data["selected"] for data in recycle_view.data]
        if True in datas and not UserCard.selected_cards:
            screen_products.switch_topbar_select()
            UserCard.selected_cards = True
        else:
            if len(list(set(datas))) == 1 and not list(set(datas))[0]:
                UserCard.selected_cards = False

        for index, is_selected in enumerate(datas):
            num_selected_cards = datas.count(True)

            if num_selected_cards == 1 and is_selected:
                UserCard.selected_cards = True
                screen_products.ids.action_button_bottom.icon = 'pencil'
                screen_products.on_edit_true()

            elif num_selected_cards == 0 and not is_selected:
                UserCard.selected_cards = False

            elif num_selected_cards > 1 and is_selected:
                # UserCard.selected_cards = False
                screen_products.ids.action_button_bottom.icon = 'plus'
                screen_products.on_edit_false()

        if not UserCard.selected_cards:
            screen_products.ids.action_button_bottom.icon = 'plus'
            screen_products.on_edit_false()
            screen_products.switch_topbar_search()
            num_selected_cards = 0


def load_usercards_products(self):
    screen_products = MDApp.get_running_app().root.get_screen('float_layout_products')
    recycle_view = screen_products.ids.recycle_view

    visual_path = fr'C:\Python\Projects\MyApp\saved_products\visuals\saved_visuals'
    input_dir = 'C:/Python/Projects/MyApp/saved_products/inputs/'
    input_files = [f for f in os.listdir(
        input_dir) if os.path.isfile(os.path.join(input_dir, f))]
    num_inputs = len(input_files)

    creation_date_list = []
    title_list = []
    price_list = []
    category_list = []

    screen_products.ids.action_button_bottom.icon = 'plus'

    async def generate_card():

        recycle_view.data = []

        for filename in os.listdir(visual_path):
            if filename.endswith('.json'):
                file_path = os.path.join(visual_path, filename)

                with open(file_path, 'r') as f:
                    data = json.load(f)
                    creation_date = data['creation_date']
                    creation_date_list.append(creation_date)
                    title = data['title_text']
                    title_list.append(title)
                    price = data['price_value']
                    price_list.append(price)
                    category = data['category_value']
                    category_list.append(category)

        for i in range(num_inputs):
            await asynckivy.sleep(0)
            recycle_view.data.append(
                {
                    "name": title_list[i],
                    "price": price_list[i],
                    "date": creation_date_list[i],
                    "category": category_list[i],
                    "selected": False,
                    "callback": UserCard.on_tap_card,
                }
            )

    Clock.schedule_once(lambda x: asynckivy.start(generate_card()))

These are the UserCard- and recycle_view classes.


<UserCard>
    orientation: "vertical"
    adaptive_height: False
    md_bg_color: (25/250, 25/250, 25/250, 1) if self.selected else (18/250, 18/250, 18/250, 1) 
    radius: 16
    padding: 0, 0, 0, "16dp"

    MDRelativeLayout:

        MDLabel:
            id: name_label
            text: root.name
            adaptive_size: False
            color: (51/250, 54/250, 63/250, 1)
            pos: "12dp", "12dp"
            pos_hint: {"top": 1.25, "left": 1} 
            bold: True

        MDLabel:
            id: price_label
            text: root.price
            adaptive_size: True
            color: (51/250, 54/250, 63/250, 1)
            pos: "12dp", "12dp"
            pos_hint: {"top": 0.9, "center_x": 0.9}
            opacity: 0

        MDLabel:
            id: date_label
            text: root.date
            adaptive_size: True
            color: (51/250, 54/250, 63/250, 1)
            pos: "12dp", "12dp"
            pos_hint: {"center_y": 0.1, "left": 1}
            bold: False
            font_size: 13

        MDFillRoundFlatButton:
            id: category_ellipse
            size_hint: (0, 0)
            text: root.category
            color: (51/250, 54/250, 63/250, 1)
            font_size: 15
            bold: True
            icon_size: 15
            icon: "gift-outline"
            text_color: (51/250, 54/250, 63/250, 1)
            md_bg_color: (23/250, 23/250, 23/250, 1)
            pos: "12dp", "12dp"
            pos_hint: {"right": 0.95, "center_y": 0.2}
            _no_ripple_effect: True
            radius: [20]
            opacity: 0

<RView@RecycleView>
    viewclass: 'UserCard'
    size_hint: (0.95, 0.88)
    pos_hint: {'center_x': 0.5, 'y': 0.11}
    bar_color: 0,0,0,0
    SelectableRecycleGridLayout:
        orientation: 'vertical'
        spacing: "16dp"
        padding: "16dp"
        default_size: None, dp(100)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        multiselect: True
        touch_multiselect: True

I have tried changing the md_bg_color of each viewclass, but that didn't help either, because it only changed the color of the viewclasses that were loaded. I've tried refreshing the recycleview via

recycle_view.refresh_from_data()

Also the color of the viewclasses did not go back to the "normal" color sometimes, instead those viewclasses were recycled and made other viewclasses, that were't selected, look like they were selected.

Upvotes: 0

Views: 66

Answers (1)

BlackHaikal
BlackHaikal

Reputation: 35

You should provide minimal code so that people that want to help you out, can just copy paste and run the code (instead writing the code back and match the code that you gave - unless they have time / professional programmers where they can solve just by reading the short code given)

Below some other way to do what you want, or maybe not:

(if you have any questions regarding to this code, call me out on Kivy Discord)

from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty, BooleanProperty, ListProperty
from kivy.uix.behaviors import ButtonBehavior
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.utils import asynckivy


Builder.load_string('''
<Home>
    app: app
    RecycleView:
        id: recycle_view
        viewclass: 'UserCard'
        bar_color: 0,0,0,0
        RecycleBoxLayout:
            orientation: 'vertical'
            spacing: "16dp"
            padding: "16dp"
            default_size: None, dp(70)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height

    Button:
        size_hint_y: .1
        text: 'CLEAR'
        on_press: root.clear_selected_user_card()

<UserCard>
    orientation: "vertical"
    size_hint: 1, None
    md_bg_color: 'orange'
    on_press: root.selected_node()
    MDLabel:
        id: name_label
        text: root.name
        bold: True
        halign: 'center'
''')


class UserCard(ButtonBehavior, MDBoxLayout):
    name = StringProperty()
    selected = BooleanProperty(False)

    def selected_node(self):
        if self.selected:
            self.selected = False
        else:
            self.selected = True

    def on_selected(self, grid, selected):
        if selected:
            self.md_bg_color = 'red'
            self.main_screen.update_selected_list(self, 'append')
        else:
            self.md_bg_color = 'orange'
            self.main_screen.update_selected_list(self, 'remove')


class Home(MDScreen):
    selected_data_list = ListProperty()

    def update_selected_list(self, instance, task):
        if task == 'append':
            self.selected_data_list.append(instance)
        else:
            self.selected_data_list.remove(instance)

    def clear_selected_user_card(self):
        self.app.load_usercards_products()


class TestApp(MDApp):
    def build(self):
        return Home(name='home_screen')

    def on_start(self):
        Clock.schedule_once(self.load_usercards_products, 1)

    def load_usercards_products(self, dt=None):
        recycle_view = self.root.ids.recycle_view
        title_list = ['title1', 'title2', 'title3', 'title4', 'title5']

        async def generate_card():
            recycle_view.data = []
            num_inputs = 5
            for i in range(num_inputs):
                await asynckivy.sleep(0)
                recycle_view.data.append(
                    {
                        "name": title_list[i],
                        "selected": False,
                        "main_screen": self.root
                    }
                )

        Clock.schedule_once(lambda x: asynckivy.start(generate_card()))


TestApp().run()

Upvotes: 0

Related Questions