Python:如何在RecycleView中添加垂直滚动

Mar*_*tin 5 python kivy

我正在使用Python-2.7和kivy。
我运行test.py然后显示一个菜单Test。当我单击它时显示list数据。有人可以告诉我如何scrollbar在列表中添加垂直方向。

测试.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, ObjectProperty, NumericProperty, DictProperty

from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
from kivy.core.window import Window
from kivy.clock import Clock

Window.size = (600, 325)

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

    selected_row = NumericProperty(0)

    def get_nodes(self):
        nodes = self.get_selectable_nodes()
        if self.nodes_order_reversed:
            nodes = nodes[::-1]
        if not nodes:
            return None, None

        selected = self.selected_nodes
        if not selected:    # nothing selected, select the first
            self.select_node(nodes[0])
            self.selected_row = 0
            return None, None

        if len(nodes) == 1:     # the only selectable node is selected already
            return None, None

        last = nodes.index(selected[-1])
        self.clear_selection()
        return last, nodes

    def select_next(self):
        ''' Select next row '''
        last, nodes = self.get_nodes()
        if not nodes:
            return

        if last == len(nodes) - 1:
            self.select_node(nodes[0])
            self.selected_row = nodes[0]
        else:
            self.select_node(nodes[last + 1])
            self.selected_row = nodes[last + 1]

    def select_previous(self):
        ''' Select previous row '''
        last, nodes = self.get_nodes()
        if not nodes:
            return

        if not last:
            self.select_node(nodes[-1])
            self.selected_row = nodes[-1]
        else:
            self.select_node(nodes[last - 1])
            self.selected_row = nodes[last - 1]

    def select_current(self):
        ''' Select current row '''
        last, nodes = self.get_nodes()
        if not nodes:
            return

        self.select_node(nodes[self.selected_row])


class SelectableButton(RecycleDataViewBehavior, Button):
    ''' Add selection support to the Button '''
    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(SelectableButton, self).refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableButton, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            print("on_touch_down: self=", self)
            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


class RV(BoxLayout):
    data_items = ListProperty([])
    row_data = DictProperty({})
    col1_data = ListProperty([])
    col2_data = ListProperty([])
    col1_row_controller = ObjectProperty(None)
    col2_row_controller = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.get_states()
        Clock.schedule_once(self.set_default_first_row, .0005)
        self._request_keyboard()

    def _request_keyboard(self):
        self._keyboard = Window.request_keyboard(
            self._keyboard_closed, self, 'text'
        )
        if self._keyboard.widget:
            # If it exists, this widget is a VKeyboard object which you can use
            # to change the keyboard layout.
            pass
        self._keyboard.bind(on_key_down=self._on_keyboard_down)

    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        self._keyboard = None

    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'down':    # keycode[274, 'down'] pressed
            # Respond to keyboard down arrow pressed
            self.display_keystrokes(keyboard, keycode, text, modifiers)
            self.col1_row_controller.select_next()
            self.col2_row_controller.select_next()

        elif keycode[1] == 'up':    # keycode[273, 'up] pressed
            # Respond to keyboard up arrow pressed
            self.display_keystrokes(keyboard, keycode, text, modifiers)
            self.col1_row_controller.select_previous()
            self.col2_row_controller.select_previous()

        # Keycode is composed of an integer + a string
        # If we hit escape, release the keyboard
        if keycode[1] == 'escape':
            keyboard.release()

        # Return True to accept the key. Otherwise, it will be used by
        # the system.
        return True

    def display_keystrokes(self, keyboard, keycode, text, modifiers):
        print("\nThe key", keycode, "have been pressed")
        print(" - text is %r" % text)
        print(" - modifiers are %r" % modifiers)

    def on_keyboard_select(self):
        ''' Respond to keyboard event to call Popup '''

        # setup row data for Popup
        self.row_data = self.col1_data[self.col1_row_controller.selected_row]

        # call Popup
        self.popup_callback()

    def on_mouse_select(self, instance):
        ''' Respond to mouse event to call Popup '''

        if (self.col1_row_controller.selected_row != instance.index
                or self.col2_row_controller.selected_row != instance.index):
            # Mouse clicked on row is not equal to current selected row
            self.col1_row_controller.selected_row = instance.index
            self.col2_row_controller.selected_row = instance.index

            # Hightlight mouse clicked/selected row
            self.col1_row_controller.select_current()
            self.col2_row_controller.select_current()

        # setup row data for Popup
        # we can use either col1_data or col2_data because they are duplicate and each stores the same info
        self.row_data = self.col1_data[instance.index]

        # call Popup
        self.popup_callback()

    def popup_callback(self):

        # enable keyboard request
        self._request_keyboard()

    def set_default_first_row(self, dt):
        ''' Set default first row as selected '''
        self.col1_row_controller.select_next()
        self.col2_row_controller.select_next()

    def update(self):
        self.col1_data = [{'text': str(x[0]), 'Id': str(x[0]), 'Name': x[1], 'key': 'Id', 'selectable': True}
                          for x in self.data_items]

        self.col2_data = [{'text': x[1], 'Id': str(x[0]), 'Name': x[1], 'key': 'Name', 'selectable': True}
                          for x in self.data_items]

    def get_states(self):
        rows = [(1, 'abc'), (1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc'),(1, 'abc')]

        i = 0
        for row in rows:
            self.data_items.append([row[0], row[1], i])
            i += 1
        print(self.data_items)
        self.update()


class MainMenu(BoxLayout):
    states_cities_or_areas = ObjectProperty(None)
    rv = ObjectProperty(None)

    def display_states(self):
        self.remove_widgets()
        self.rv = RV()
        self.states_cities_or_areas.add_widget(self.rv)

    def remove_widgets(self):
        self.states_cities_or_areas.clear_widgets()


class TestApp(App):
    title = "test"

    def build(self):
        return MainMenu()


if __name__ == '__main__':
    TestApp().run()
Run Code Online (Sandbox Code Playgroud)

测试.kv

#:kivy 1.10.0

<SelectableButton>:
    canvas.before:
        Color:
            rgba: (0, 0.517, 0.705, 1) if self.selected else (0, 0.517, 0.705, 1)
        Rectangle:
            pos: self.pos
            size: self.size
    background_color: [1, 0, 0, 1]  if self.selected else [1, 1, 1, 1]  # dark red else dark grey
    on_press: app.root.rv.on_mouse_select(self)


<RV>:
    col1_row_controller: col1_row_controller
    col2_row_controller: col2_row_controller

    orientation: "vertical"

    GridLayout:
        size_hint: 1, None
        size_hint_y: None
        height: 25
        cols: 3

        Label:
            size_hint_x: .1
            text: "Id"
        Label:
            size_hint_x: .5
            text: "Name"


    ScrollView:
        id: kr_scroll
        do_scroll_x: False
        height: 2
        BoxLayout:
            RecycleView:
                size_hint_x: .1
                data: root.col1_data
                viewclass: 'SelectableButton'
                SelectableRecycleGridLayout:
                    id: col1_row_controller
                    key_selection: 'selectable'
                    cols: 1
                    default_size: None, dp(26)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    multiselect: True
                    touch_multiselect: True

            RecycleView:
                size_hint_x: .5
                data: root.col2_data
                viewclass: 'SelectableButton'
                SelectableRecycleGridLayout:
                    id: col2_row_controller
                    key_selection: 'selectable'
                    cols: 1
                    default_size: None, dp(26)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    multiselect: True
                    touch_multiselect: True


<MenuButton@Button>:
    text_size: self.size
    valign: "middle"
    padding_x: 5
    size : (80,30)
    size_hint : (None, None)
    background_color: 90 , 90, 90, 90
    background_normal: ''
    color: 0, 0.517, 0.705, 1
    border: (0, 10, 0, 0)


<MainMenu>:
    states_cities_or_areas: states_cities_or_areas

    BoxLayout:
        orientation: 'vertical'
        #spacing : 10

        BoxLayout:
            canvas.before:
                Rectangle:
                    pos: self.pos
                    size: self.size

            size_hint_y: 1

            MenuButton:
                id: btn
                text: 'Test'
                size : (60,30)
                on_release: root.display_states()


        BoxLayout:
            canvas.before:
                Rectangle:
                    pos: self.pos
                    size: self.size

                Color:
                    rgb: (1,1,1)

            Label:
                size_hint_x: 45

        BoxLayout:
            id: states_cities_or_areas
            size_hint_y: 10

        Label:
            size_hint_y: 1
Run Code Online (Sandbox Code Playgroud)

iko*_*lim 6

问题

\n\n
\n

如何仅在 customerId 列中设置文本右对齐,而其他列\n 应左对齐?

\n
\n\n

解决方案

\n\n

片段

\n\n
def apply_selection(self, rv, index, is_selected):\n    \'\'\' Respond to the selection of items in the view. \'\'\'\n    self.selected = is_selected\n    self.text_size = self.size\n    if index == rv.data[index][\'range\'][0]:\n        self.halign = \'right\'\n    else:\n        self.halign = \'left\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出

\n\n

图像8

\n\n

标签 - 文本垂直生长并以一定宽度换行

\n\n

可以垂直增长但以一定宽度包裹文本的标签:

\n\n
Label:\n    size_hint_y: None\n    text_size: self.width, None\n    height: self.texture_size[1]\n
Run Code Online (Sandbox Code Playgroud)\n\n

要在其上滚动 GridLayout\xe2\x80\x99s X 轴/水平和 Y 轴/垂直,请将 size_hint 属性设置为 (None, None)。

\n\n

ScrollView - ScrollEffect、scroll_type、bar_width 等

\n\n
\n

默认情况下,ScrollView 允许沿 X 轴和 Y 轴滚动。您可以通过将 do_scroll_x 或 do_scroll_y 属性设置为 False 来显式禁用轴上的滚动。

\n\n

要在其上\xe2\x80\x99s Y 轴/垂直滚动 GridLayout,请将子\xe2\x80\x99s\n 宽度设置为 ScrollView 的宽度 (size_hint_x=1),并将 size_hint_y 属性\n 设置为 None :

\n\n

当滚动超出 ScrollView 的边界时,它使用 ScrollEffect 来处理过度滚动。

\n\n

滚动类型

\n\n

设置用于滚动视图内容的滚动类型。\n 可用选项有: [\xe2\x80\x98content\xe2\x80\x99]、[\xe2\x80\x98bars\xe2\x80\x99]、 [\xe2\x80\x98bars\xe2\x80\x99,\xe2\x80\x98内容\xe2\x80\x99]

\n\n

[\xe2\x80\x98bars\xe2\x80\x99] 通过拖动或滑动滚动条来滚动内容。

\n
\n\n

网格布局

\n\n
\n

列数最小值

\n\n

每列的最小宽度的字典。字典键是\n列号,例如 0, 1, 2\xe2\x80\xa6

\n\n

cols_minimum 是一个 DictProperty,默认为 {}。

\n
\n\n

片段

\n\n
<RV>:\n    row_controller: row_controller\n\n    bar_width: 10\n    bar_color: 1, 0, 0, 1   # red\n    bar_inactive_color: 0, 0, 1, 1   # blue\n    effect_cls: "ScrollEffect"\n    scroll_type: [\'bars\']\n\n    data: root.rv_data\n    viewclass: \'SelectableButton\'\n\n    SelectableRecycleGridLayout:\n        id: row_controller\n        key_selection: \'selectable\'\n        cols: root.total_col_headings\n        cols_minimum: root.cols_minimum\n        default_size: None, dp(26)\n        default_size_hint: 1, None\n        size_hint: None, None\n        height: self.minimum_height\n        width: self.minimum_width\n        orientation: \'vertical\'\n        multiselect: True\n        touch_multiselect: True\n
Run Code Online (Sandbox Code Playgroud)\n\n

下面的例子演示了:

\n\n
    \n
  1. 表格标题在 X 轴上滚动。
  2. \n
  3. 支持X轴和Y轴滚动。
  4. \n
  5. 支持在弹出窗口中 Y 轴滚动。
  6. \n
  7. 适用于数据库表中的任何列。
  8. \n
  9. 使用 GridLayout cols_minimum改变列宽。
  10. \n
  11. 将两个 RecycleView 合二为一,因为不需要仅用于 ID 的 RecycleView。
  12. \n
  13. 可选按钮。
  14. \n
\n\n

SQLite 示例数据库

\n\n

chinook SQLite 示例数据库

\n\n

例子

\n\n

主要.py

\n\n
from kivy.app import App\nfrom kivy.uix.boxlayout import BoxLayout\nfrom kivy.properties import BooleanProperty, ListProperty, ObjectProperty, NumericProperty, DictProperty\n\nfrom kivy.uix.recycleview.views import RecycleDataViewBehavior\nfrom kivy.uix.button import Button\nfrom kivy.uix.recyclegridlayout import RecycleGridLayout\nfrom kivy.uix.behaviors import FocusBehavior\nfrom kivy.uix.recycleview.layout import LayoutSelectionBehavior\nfrom kivy.uix.popup import Popup\nfrom kivy.core.window import Window\nfrom kivy.clock import Clock\nfrom kivy.uix.scrollview import ScrollView\nfrom kivy.uix.label import Label\nfrom kivy.uix.textinput import TextInput\nfrom kivy.uix.recycleview import RecycleView\nimport sqlite3 as lite\nimport re\n\nWindow.size = (600, 325)\n\n\nclass PopupLabelCell(Label):\n    pass\n\n\nclass EditStatePopup(Popup):\n\n    def __init__(self, obj, **kwargs):\n        super(EditStatePopup, self).__init__(**kwargs)\n        self.populate_content(obj)\n\n    def populate_content(self, obj):\n        for x in range(len(obj.table_header.col_headings)):\n            self.container.add_widget(PopupLabelCell(text=obj.table_header.col_headings[x]))\n            textinput = TextInput(text=str(obj.row_data[x]))\n            if x == 0:\n                textinput.readonly = True\n            self.container.add_widget(textinput)\n\n\nclass SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,\n                                  RecycleGridLayout):\n    \'\'\' Adds selection and focus behaviour to the view. \'\'\'\n\n    selected_row = NumericProperty(0)\n    obj = ObjectProperty(None)\n\n    def get_nodes(self):\n        nodes = self.get_selectable_nodes()\n        if self.nodes_order_reversed:\n            nodes = nodes[::-1]\n        if not nodes:\n            return None, None\n\n        selected = self.selected_nodes\n        if not selected:    # nothing selected, select the first\n            self.selected_row = 0\n            self.select_row(nodes)\n            return None, None\n\n        if len(nodes) == 1:     # the only selectable node is selected already\n            return None, None\n\n        last = nodes.index(selected[-1])\n        self.clear_selection()\n        return last, nodes\n\n    def select_next(self, obj):\n        \'\'\' Select next row \'\'\'\n        self.obj = obj\n        last, nodes = self.get_nodes()\n        if not nodes:\n            return\n\n        if last == len(nodes) - 1:\n            self.selected_row = nodes[0]\n        else:\n            self.selected_row = nodes[last + 1]\n\n        self.selected_row += self.obj.total_col_headings\n        self.select_row(nodes)\n\n    def select_previous(self, obj):\n        \'\'\' Select previous row \'\'\'\n        self.obj = obj\n        last, nodes = self.get_nodes()\n        if not nodes:\n            return\n\n        if not last:\n            self.selected_row = nodes[-1]\n        else:\n            self.selected_row = nodes[last - 1]\n\n        self.selected_row -= self.obj.total_col_headings\n        self.select_row(nodes)\n\n    def select_current(self, obj):\n        \'\'\' Select current row \'\'\'\n        self.obj = obj\n        last, nodes = self.get_nodes()\n        if not nodes:\n            return\n\n        self.select_row(nodes)\n\n    def select_row(self, nodes):\n        col = self.obj.rv_data[self.selected_row][\'range\']\n        for x in range(col[0], col[1] + 1):\n            self.select_node(nodes[x])\n\n\nclass SelectableButton(RecycleDataViewBehavior, Button):\n    \'\'\' Add selection support to the Button \'\'\'\n    index = None\n    selected = BooleanProperty(False)\n    selectable = BooleanProperty(True)\n\n    def refresh_view_attrs(self, rv, index, data):\n        \'\'\' Catch and handle the view changes \'\'\'\n\n        self.index = index\n        return super(SelectableButton, self).refresh_view_attrs(rv, index, data)\n\n    def on_touch_down(self, touch):\n        \'\'\' Add selection on touch down \'\'\'\n        if super(SelectableButton, self).on_touch_down(touch):\n            return True\n        if self.collide_point(*touch.pos) and self.selectable:\n            print("on_touch_down: self=", self)\n            return self.parent.select_with_touch(self.index, touch)\n\n    def apply_selection(self, rv, index, is_selected):\n        \'\'\' Respond to the selection of items in the view. \'\'\'\n        self.selected = is_selected\n        self.text_size = self.size\n        if index == rv.data[index][\'range\'][0]:\n            self.halign = \'right\'\n        else:\n            self.halign = \'left\'\n\n\nclass HeaderCell(Label):\n    pass\n\n\nclass TableHeader(ScrollView):\n    """Fixed table header that scrolls x with the data table"""\n    header = ObjectProperty(None)\n    col_headings = ListProperty([])\n    cols_minimum = DictProperty({})\n\n    def __init__(self, **kwargs):\n        super(TableHeader, self).__init__(**kwargs)\n        self.db = lite.connect(\'chinook.db\')\n        self.db_cursor = self.db.cursor()\n        self.get_table_column_headings()\n\n    def get_table_column_headings(self):\n        self.db_cursor.execute("PRAGMA table_info(customers)")\n        col_headings = self.db_cursor.fetchall()\n\n        for col_heading in col_headings:\n            data_type = col_heading[2]\n            if data_type == "INTEGER":\n                self.cols_minimum[col_heading[0]] = 100\n            else:\n                self.cols_minimum[col_heading[0]] = int(re.findall(r\'\\d+\', data_type).pop(0)) * 5\n            self.col_headings.append(col_heading[1])\n            self.header.add_widget(HeaderCell(text=col_heading[1], width=self.cols_minimum[col_heading[0]]))\n\n\nclass RV(RecycleView):\n    row_data = ()\n    rv_data = ListProperty([])\n    row_controller = ObjectProperty(None)\n    total_col_headings = NumericProperty(0)\n    cols_minimum = DictProperty({})\n    table_header = ObjectProperty(None)\n\n    def __init__(self, table_header, **kwargs):\n        super(RV, self).__init__(**kwargs)\n        self.table_header = table_header\n        self.total_col_headings = len(table_header.col_headings)\n        self.cols_minimum = table_header.cols_minimum\n        self.database_connection()\n        self.get_states()\n        Clock.schedule_once(self.set_default_first_row, .0005)\n        self._request_keyboard()\n\n    def database_connection(self):\n        self.db = lite.connect(\'chinook.db\')\n        self.db_cursor = self.db.cursor()\n\n    def _request_keyboard(self):\n        self._keyboard = Window.request_keyboard(\n            self._keyboard_closed, self, \'text\'\n        )\n        if self._keyboard.widget:\n            # If it exists, this widget is a VKeyboard object which you can use\n            # to change the keyboard layout.\n            pass\n        self._keyboard.bind(on_key_down=self._on_keyboard_down)\n\n    def _keyboard_closed(self):\n        self._keyboard.unbind(on_key_down=self._on_keyboard_down)\n        self._keyboard = None\n\n    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):\n        if keycode[1] == \'down\':    # keycode[274, \'down\'] pressed\n            # Respond to keyboard down arrow pressed\n            self.display_keystrokes(keyboard, keycode, text, modifiers)\n            self.row_controller.select_next(self)\n\n        elif keycode[1] == \'up\':    # keycode[273, \'up] pressed\n            # Respond to keyboard up arrow pressed\n            self.display_keystrokes(keyboard, keycode, text, modifiers)\n            self.row_controller.select_previous(self)\n\n        elif len(modifiers) > 0 and modifiers[0] == \'ctrl\' and text == \'e\':     # ctrl + e pressed\n            # Respond to keyboard ctrl + e pressed, and call Popup\n            self.display_keystrokes(keyboard, keycode, text, modifiers)\n            self.on_keyboard_select()\n\n        # Keycode is composed of an integer + a string\n        # If we hit escape, release the keyboard\n        if keycode[1] == \'escape\':\n            keyboard.release()\n\n        # Return True to accept the key. Otherwise, it will be used by\n        # the system.\n        return True\n\n    def display_keystrokes(self, keyboard, keycode, text, modifiers):\n        print("\\nThe key", keycode, "have been pressed")\n        print(" - text is %r" % text)\n        print(" - modifiers are %r" % modifiers)\n\n    def on_keyboard_select(self):\n        \'\'\' Respond to keyboard event to call Popup \'\'\'\n\n        # setup row data for Popup\n        self.setup_row_data(self.rv_data[self.row_controller.selected_row][\'Index\'])\n\n        # call Popup\n        self.popup_callback()\n\n    def on_mouse_select(self, instance):\n        \'\'\' Respond to mouse event to call Popup \'\'\'\n\n        if self.row_controller.selected_row != instance.index:\n            # Mouse clicked on row is not equal to current selected row\n            self.row_controller.selected_row = instance.index\n\n            # Hightlight mouse clicked/selected row\n            self.row_controller.select_current(self)\n\n        # setup row data for Popup\n        self.setup_row_data(self.rv_data[instance.index][\'Index\'])\n\n        # call Popup\n        self.popup_callback()\n\n        # enable keyboard request\n        self._request_keyboard()\n\n    def setup_row_data(self, value):\n        self.db_cursor.execute("SELECT * FROM customers WHERE CustomerId=?", value)\n        self.row_data = self.db_cursor.fetchone()\n\n    def popup_callback(self):\n        \'\'\' Instantiate and Open Popup \'\'\'\n        popup = EditStatePopup(self)\n        popup.open()\n\n    def set_default_first_row(self, dt):\n        \'\'\' Set default first row as selected \'\'\'\n        self.row_controller.select_next(self)\n\n    def get_states(self):\n        self.db_cursor.execute("SELECT * FROM customers ORDER BY CustomerId ASC")\n        rows = self.db_cursor.fetchall()\n\n        data = []\n        low = 0\n        high = self.total_col_headings - 1\n        for row in rows:\n            for i in range(len(row)):\n                data.append([row[i], row[0], [low, high]])\n            low += self.total_col_headings\n            high += self.total_col_headings\n\n        self.rv_data = [{\'text\': str(x[0]), \'Index\': str(x[1]), \'range\': x[2], \'selectable\': True} for x in data]\n\n\nclass Table(BoxLayout):\n    rv = ObjectProperty(None)\n\n    def __init__(self, **kwargs):\n        super(Table, self).__init__(**kwargs)\n        self.orientation = "vertical"\n        self.header = TableHeader()\n        self.rv = RV(self.header)\n\n        self.rv.fbind(\'scroll_x\', self.scroll_with_header)\n\n        self.add_widget(self.header)\n        self.add_widget(self.rv)\n\n    def scroll_with_header(self, obj, value):\n        self.header.scroll_x = value\n\n\nclass MainMenu(BoxLayout):\n    states_cities_or_areas = ObjectProperty(None)\n    table = ObjectProperty(None)\n\n    def display_states(self):\n        self.remove_widgets()\n        self.table = Table()\n        self.states_cities_or_areas.add_widget(self.table)\n\n    def remove_widgets(self):\n        self.states_cities_or_areas.clear_widgets()\n\n\nclass TestApp(App):\n    title = "test"\n\n    def build(self):\n        return MainMenu()\n\n\nif __name__ == \'__main__\':\n    TestApp().run()\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试.kv

\n\n
#:kivy 1.10.0\n\n<PopupLabelCell>\n    size_hint: (None, None)\n    height: 30\n    text_size: self.size\n    halign: "left"\n    valign: "middle"\n\n<EditStatePopup>:\n    container: container\n    size_hint: None, None\n    size: 400, 275\n    title_size: 20\n    # title_font: "Verdana"\n    auto_dismiss: False\n\n    BoxLayout:\n        orientation: "vertical"\n        ScrollView:\n            bar_width: 10\n            bar_color: 1, 0, 0, 1   # red\n            bar_inactive_color: 0, 0, 1, 1   # blue\n            effect_cls: "ScrollEffect"\n            scroll_type: [\'bars\']\n            size_hint: (1, None)\n\n            GridLayout:\n                id: container\n                cols: 2\n                row_default_height: 30\n                cols_minimum: {0: 100, 1: 300}\n                # spacing: 10, 10\n                # padding: 20, 20\n                size_hint: (None, None)\n                height: self.minimum_height\n\n        BoxLayout:\n            Button:\n                size_hint: 1, 0.2\n                text: "Save Changes"\n                on_release:\n                    root.dismiss()\n            Button:\n                size_hint: 1, 0.2\n                text: "Cancel Changes"\n                on_release: root.dismiss()\n\n<SelectableButton>:\n    canvas.before:\n        Color:\n            rgba: (0, 0.517, 0.705, 1) if self.selected else (0, 0.517, 0.705, 1)\n        Rectangle:\n            pos: self.pos\n            size: self.size\n    background_color: [1, 0, 0, 1]  if self.selected else [1, 1, 1, 1]  # dark red else dark grey\n    on_press: app.root.table.rv.on_mouse_select(self)\n\n<HeaderCell>\n    size_hint: (None, None)\n    height: 25\n    text_size: self.size\n    halign: "left"\n    valign: "middle"\n    background_disabled_normal: \'\'\n    disabled_color: (1, 1, 1, 1)\n\n    canvas.before:\n        Color:\n            rgba: 1, 0.502, 0, 1\n        Rectangle:\n            pos: self.pos\n            size: self.size\n\n<TableHeader>:\n    header: header\n    bar_width: 0\n    do_scroll: False\n    size_hint: (1, None)\n    height: 25\n    effect_cls: "ScrollEffect"\n\n    GridLayout:\n        id: header\n        rows: 1\n        cols_minimum: root.cols_minimum\n        size_hint: (None, None)\n        width: self.minimum_width\n        height: self.minimum_height\n\n<RV>:\n    row_controller: row_controller\n\n    bar_width: 10\n    bar_color: 1, 0, 0, 1   # red\n    bar_inactive_color: 0, 0, 1, 1   # blue\n    effect_cls: "ScrollEffect"\n    scroll_type: [\'bars\']\n\n    data: root.rv_data\n    viewclass: \'SelectableButton\'\n\n    SelectableRecycleGridLayout:\n        id: row_controller\n        key_selection: \'selectable\'\n        cols: root.total_col_headings\n        cols_minimum: root.cols_minimum\n        default_size: None, dp(26)\n        default_size_hint: 1, None\n        size_hint: None, None\n        height: self.minimum_height\n        width: self.minimum_width\n        orientation: \'vertical\'\n        multiselect: True\n        touch_multiselect: True\n\n\n<MenuButton@Button>:\n    text_size: self.size\n    valign: "middle"\n    padding_x: 5\n    size : (80,30)\n    size_hint : (None, None)\n    background_color: 90 , 90, 90, 90\n    background_normal: \'\'\n    color: 0, 0.517, 0.705, 1\n    border: (0, 10, 0, 0)\n\n\n<MainMenu>:\n    states_cities_or_areas: states_cities_or_areas\n\n    BoxLayout:\n        orientation: \'vertical\'\n\n        BoxLayout:\n            canvas.before:\n                Rectangle:\n                    pos: self.pos\n                    size: self.size\n\n            size_hint_y: 1\n\n            MenuButton:\n                id: btn\n                text: \'Test\'\n                size : (60,30)\n                on_release: root.display_states()\n\n\n        BoxLayout:\n            canvas.before:\n                Rectangle:\n                    pos: self.pos\n                    size: self.size\n\n                Color:\n                    rgb: (1,1,1)\n\n            Label:\n                size_hint_x: 45\n\n        BoxLayout:\n            id: states_cities_or_areas\n            size_hint_y: 10\n\n        Label:\n            size_hint_y: 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出

\n\n

图像01\n图像02\n图像03\n在此输入图像描述

\n