Mox
Mox

Reputation: 421

Kivy RecycleView, RecycleGridLayout, Scrollable Label problems

As the title suggests, I'm trying to make a data table with a fixed header and a cell size of w400 x h60. If the text inside the label exceeds a width of 400, the label should scroll horizontally. The code posted below is the closest I've come.

Inspecting the tree, ScrollCell (BoxLayout) has dimensions of 100x100 and does not fill respect it's given size or fill the deafult col/row width/height of the RecycleGridLayout.

Here's how it looks:

Screen Shot

Any help in fixing this is appreciated.

rtable.py:

from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.recycleview import RecycleView
from kivy.properties import BooleanProperty
from kivy.properties import ObjectProperty
from kivy.properties import NumericProperty, StringProperty
import kivy
kivy.require('1.10.0')


class HeaderCell(Label):
    pass


class TableHeader(ScrollView):
    """Fixed table header that scrolls x with the data table"""
    header = ObjectProperty(None)

    def __init__(self, list_dicts=None, *args, **kwargs):
        super(TableHeader, self).__init__(*args, **kwargs)

        titles = list_dicts[0].keys()

        for title in titles:
            self.header.add_widget(HeaderCell(text=title))


class ScrollCell(BoxLayout):
    text = StringProperty(None)
    is_even = BooleanProperty(None)


class TableData(RecycleView):
    nrows = NumericProperty(None)
    ncols = NumericProperty(None)
    rgrid = ObjectProperty(None)

    def __init__(self, list_dicts=[], *args, **kwargs):
        self.nrows = len(list_dicts)
        self.ncols = len(list_dicts[0])

        super(TableData, self).__init__(*args, **kwargs)

        self.data = []
        for i, ord_dict in enumerate(list_dicts):
            is_even = i % 2 == 0
            row_vals = ord_dict.values()
            for text in row_vals:
                self.data.append({'text': text, 'is_even': is_even})


class Table(BoxLayout):

    def __init__(self, list_dicts=[], *args, **kwargs):

        super(Table, self).__init__(*args, **kwargs)
        self.orientation = "vertical"

        self.header = TableHeader(list_dicts=list_dicts)
        self.table_data = TableData(list_dicts=list_dicts)

        self.table_data.fbind('scroll_x', self.scroll_with_header)

        self.add_widget(self.header)
        self.add_widget(self.table_data)

    def scroll_with_header(self, obj, value):
        self.header.scroll_x = value


if __name__ == '__main__':
    from kivy.app import App
    from collections import OrderedDict

    class RtableApp(App):
        def build(self):
            data = []

            keys = ["Title Col: {}".format(i + 1) for i in range(15)]

            for nrow in range(30):
                row = OrderedDict.fromkeys(keys)
                for i, key in enumerate(keys):
                    row[key] = "Data col: {}, row: {}".format(i + 1, nrow + 1)
                    if i % 3 == 0:
                        row[key] = row[key] + ". Extra long label. " * 3
                data.append(row)

            return Table(list_dicts=data)

    RtableApp().run()

rtable.kv:

<HeaderCell>
    size_hint: (None, None)
    height: 60
    width: 400
    text_size: self.size
    halign: "left"
    valign: "middle"
    background_disabled_normal: '' 
    disabled_color: (1, 1, 1, 1)
    canvas.before:
        Color:
            rgba: 0.165, 0.165, 0.165, 1
        Rectangle:
            pos: self.pos
            size: self.size

<TableHeader>:
    header: header
    bar_width: 0
    do_scroll: False
    size_hint: (1, None)
    effect_cls: "ScrollEffect"
    height: 60
    GridLayout:
        id: header
        rows: 1
        size_hint: (None, None)
        width: self.minimum_width
        height: self.minimum_height

<ScrollCell>:
    orientation: 'horizontal'
    label: label
    size_hint: (None, None)
    size: (400, 60)  
    text: ''
    canvas.before:
        Color:
            rgba: [0.23, 0.23, 0.23, 1] if self.is_even else [0.2, 0.2, 0.2, 1]
        Rectangle:
            pos: self.pos
            size: self.size     
    ScrollView:
        scroll_type: ['bars', 'content']
        do_scroll_x: True
        do_scroll_y: False
        effect_cls: "ScrollEffect"
        Label:
            id: label
            text_size: None, self.height
            size_hint: (None, 1)
            width: self.texture_size[0]
            text: root.text
            padding_x: 10



<TableData>:
    rgrid: rgrid
    bar_width: 25
    scroll_type: ['bars']
    bar_color: [0.2, 0.7, 0.9, 1]
    bar_inactive_color: [0.2, 0.7, 0.9, .5]
    do_scroll_x: True
    do_scroll_y: True
    effect_cls: "ScrollEffect"
    viewclass: "ScrollCell"
    RecycleGridLayout:
        id: rgrid
        rows: root.nrows
        cols: root.ncols
        size_hint: (None, None)
        width: self.minimum_width
        height: self.minimum_height
        col_default_width: 400
        row_default_height: 60

Upvotes: 2

Views: 4167

Answers (1)

Mox
Mox

Reputation: 421

I was able to make this work where upon I discovered a flaw in my intended design. If the labels are scrollable and take up the entire area (400x60) of the cell within the RecycleGridLayout then this will break the scroll behavior of the RecycleView by not allowing the table to scroll y and x. This is caused by, presumably, the nested ScrollViews.

Nested Scroll-able Labels

There is mention in the docs about being able to have nested ScrollViews but I was not able to figure that out. I also saw in the #kivy irc logs that nested ScrollViews are not recommended. I have decided to go a different route by using a 'tooltip' type widget to display labels when they are wider than the cell.

Upvotes: 3

Related Questions