La situación: el siguiente código usa la clase RecycleView de Kivy para generar una GUI que muestra una tabla de datos. Cada fila de esta tabla comienza con un botón rojo "Ignorar" cuyo color se supone que el usuario puede alternar entre rojo y verde haciendo clic.

El problema: cuando hago clic en el botón "Ignorar" de una fila, no solo se vuelve verde el botón en el que se hizo clic, sino que también se vuelven verdes muchos otros botones más abajo en la lista. La selección no deseada ocurre cíclicamente, es decir, parece que cada botón n -ésimo se vuelve verde.

¿Por qué pasó esto? ¿Cómo puedo hacer que Kivy deje de hacer esto y, en su lugar, seleccione solo el botón en el que se hizo clic?

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import BooleanProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
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


Builder.load_string('''
<Row@BoxLayout>:
    orientation: 'horizontal'
    data: [None, None, None, None]
    IgnoreButton:
        text: 'Ignored'  if self.selected else 'Ignore'
        background_normal: ''
        # color green if ignored, otherwise red
        background_color: [0.2, 0.5, 0.1, 1] if self.selected else [0.5, 0, 0.1, 1]
    Label:
        text: str(root.data[1])
    Label:
        text: str(root.data[2])
    Label:
        text: str(root.data[3])
<RV>:
    viewclass: 'Row'
    data: [{'data':[0, x, x+1, x+2]} for x in range(100)]
    SelectableRecycleBoxLayout:
        orientation: 'vertical'
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        multiselect: True
''')

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

class IgnoreButton(Button, RecycleDataViewBehavior):
    # Add selection support to the Button.
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def on_touch_down(self, touch):
        # Add selection on touch down.
        if super().on_touch_down(touch):
            self.selected = not self.selected

class RV(RecycleView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

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

if __name__ == '__main__':
    TestApp().run()
0
Asker 24 ago. 2020 a las 04:16

1 respuesta

La mejor respuesta

Con una vista de reciclaje, solo hay tantos widgets como puede ver ... se reciclan. Por lo tanto, no puede confiar en el widget para almacenar el estado. En Row, creé un NumericProperty, idx, y creé un índice en la lista rvdata. Esto corresponde al índice del "widget virtual".
Creé una lista para almacenar el estado del ToggleButton.

Estabas tratando un Button, como un ToggleButton, así que lo reemplacé con un ToggleButton. Puede volver a agregar los cambios de color.

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView


Builder.load_string('''
<Row>:
    orientation: 'horizontal'
    data: [None, None, None, None]
    ToggleButton:
        text: 'Ignored'  if app.root.selected[root.idx] == 'normal' else 'Ignore'
        #background_normal: ''
        # color green if ignored, otherwise red
        #background_color: [0.2, 0.5, 0.1, 1] if app.root.selected[root.idx] == 'normal' else [0.5, 0, 0.1, 1]
        state: app.root.selected[root.idx]
        on_state: app.root.selected[root.idx] = self.state
    Label:
        text: str(root.data[1])
    Label:
        text: str(root.data[2])
    Label:
        text: str(root.data[3])
<RV>:
    viewclass: 'Row'
    data: [{'data':[0, x, x+1, x+2], 'idx': x} for x in range(100)]
    RecycleBoxLayout:
        orientation: 'vertical'
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
''')


class Row(BoxLayout):
    idx = NumericProperty()


class RV(RecycleView):
    selected = ListProperty(['normal'] * 100)


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


if __name__ == '__main__':
    TestApp().run()
1
EGarbus 24 ago. 2020 a las 03:20