Home > Net >  How to get a kivymd MDList and ScrollView to scroll until the end of the list
How to get a kivymd MDList and ScrollView to scroll until the end of the list

Time:08-15

I have the following kivymd code:

main.py

import itertools
from ctypes import windll, c_int64
windll.user32.SetProcessDpiAwarenessContext(c_int64(-4))    # Has to be here before kivy import. 4k fix
from kivy.core.window import Window
from kivy.metrics import sp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivymd.app import MDApp
from kivymd.uix.list import OneLineListItem
from skimage.io import imread
import os
import struct


# C:\WPy64-31040\python-3.10.4.amd64\python.exe main.py
import kivy; print(kivy.__version__)


def clickme(list_item):
    print(list_item.text)


class MyLayout(BoxLayout):
    pass


class MyTextInput(TextInput):
    def __init__(self, **kwargs):
        super(MyTextInput, self).__init__(**kwargs)
        self.size_hint = (None, None)
        self.foreground_color = (0, 0, 0, 1)    # Text is black.
        self.background_color = (0.6, 0.6, 0.6, 0.3)    # Background is transparent and therefore not visible.
        self.cursor_color = (0, 0, 0, 0)    # Hide the cursor by making it transparent.
        self.font_size = sp(24)
        # Left, right center.
        self.halign = 'center'
        # self.padding_x = [self.center[0] - self._get_text_width(max(self._lines, key=len), self.tab_width, self._label_cached) / 2.0,
        #     0] if self.text else [self.center[0], 0]
        # Top, bottom center. Moved to kv file.
        # self.padding_y = [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]    # Deprecated. Use padding instead.
        # self.padding = [self.padding[0], self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), self.padding[2], 0]

    def keyboard_on_textinput(self, window, text):
        """Upper case and always one letter only."""
        self.text = text.upper()


class CrosswordApp(MDApp):
    def build(self):
        return MyLayout()

    def on_start(self):
        # List of possible crossword candidates.
        for i in range(20):
            self.root.ids.lst_matches.add_widget(
                OneLineListItem(text=f"Single-line item {i} very very very very very long string that hopefully is long enough", on_release=clickme,
                                divider="Full", font_style="Caption"))

        # Get black and white blocks.
        # tex = self.root.ids.img_crossword.texture
        # for y in range(0, tex.size[1], 5):
        #     for x in range(0, tex.size[0], 5):
        #         print(self.get_color_value(x, y, tex))

        # Read crossword image using path relative to this file's path.
        img = imread(os.path.dirname(os.path.realpath(__file__))   r"\images\Huisgenoot20220605P.png")
        dcol = {}
        ysize = int(img.shape[0] / 23)    # Also len(img)
        xsize = int(img.shape[1] / 18)    # Also len(img[0])
        for y, x in itertools.product(range(23), range(18)):
            cnt = r = g = b = 0
            for y2, x2 in itertools.product(range(y * ysize, y * ysize   ysize - 1), range(x * xsize, x * xsize   xsize - 1)):
                cnt = cnt   1
                r = r   img[y2][x2][0]
                g = g   img[y2][x2][1]
                b = b   img[y2][x2][2]
            dcol[(x, y)] = [int(r / cnt), int(g / cnt), int(b / cnt)]
        for row in range(23):
            for col in range(18):
                v = dcol[col, row]
                if v[0] > 230 and v[1] > 230 and v[2] > 230:
                    print(" ", end="")
                elif 167 <= v[0] <= 187 and 203 <= v[1] <= 223 and 203 <= v[2] <= 223:
                    print("B", end="")
                else:
                    print("#", end="")
            print("")

        print(dcol[0, 0])
        print(dcol[1, 0])
        print(dcol[2, 0])
        print(dcol[3, 0])
        print(dcol[6, 1])
        print(dcol[17, 0])

    def get_color_value(self, x, y, texture):
        """Get the rgba (0..255) value at x and y of an image texture."""
        pixel = texture.get_region(x, y, 1, 1)
        bp = pixel.pixels
        return struct.unpack('4B', bp)     # tuple [c for c in struct.unpack('4B', bp)]    # / 255.0


if __name__ == '__main__':
    Window.maximize()
    CrosswordApp().run()

crossword.kv

#:kivy 2.1.0

<MyTextInput>

<MyLayout>
    BoxLayout:
        orientation: 'horizontal'
        size: root.width, root.height
        padding: 0
        spacing: 0
        Image:
            id: img_crossword
            source: 'images/Huisgenoot20220605P.png'
            size_hint: None, None
            size: img_crossword.image_ratio * root.height, root.height
            allow_stretch: True
            keep_ratio: True
        BoxLayout:
            orientation: 'vertical'
            size_hint: None, None
            size: root.width - (img_crossword.image_ratio * root.height), root.height
            padding: 0
            spacing: 0
            ScrollView:
                # size_hint: None, 1
                width: root.width - (img_crossword.image_ratio * root.height)
                do_scroll_x: False
                do_scroll_y: True
                bar_color: 0, 0, 255, 1
                bar_width: 12
                MDList:
                    id: lst_matches
                    size_hint: None, 0.8
                    width: root.width - (img_crossword.image_ratio * root.height)
                    height: self.height
                    spacing: dp(0)
                    padding: dp(0)
            BoxLayout:
                orientation: 'horizontal'
                size_hint: None, 0.2
                width: root.width - (img_crossword.image_ratio * root.height)
                padding: 0
                spacing: 0
                TextInput:
                    id: txt_clue
                    size_hint: 0.4, 0.2
                    text: 'Clue'
                Label:
                    id: lbl_clue
                    size_hint: 0.4, 0.2
                    color: 0, 0, 0, 1
                    text: 'Clue'
                TextInput:
                    id: txt_word
                    size_hint: 0.2, 0.2
                    text: 'Word'

    FloatLayout:
        MyTextInput:
            text: 'A'
            pos: img_crossword.pos[0]   img_crossword.width / 18 * 16, img_crossword.pos[1]   img_crossword.height / 23 * 5
            size: img_crossword.width / 18, img_crossword.height / 23
            padding: [self.padding[0], self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), self.padding[2], 0]

The MDList and ScrollView worked fine when I did not have widgets below it, but since adding the BoxLayout and the TextInput and Label below it; the list does not scroll all the way to the end and instead scrolls a little bit to the first invisible line and then bounces back to show only the first lines. How can I make it scroll to the end again?

CodePudding user response:

The problem is that you are setting a size_hint_y for the MDList, but it must be None in order for the scrolling to work correctly. I suggest the following change:

    BoxLayout:
        orientation: 'vertical'
        size_hint: None, None
        size: root.width - (img_crossword.image_ratio * root.height), root.height
        padding: 0
        spacing: 0
        ScrollView:
            size_hint: None, 0.8  # set size_hint_y
            width: root.width - (img_crossword.image_ratio * root.height)
            do_scroll_x: False
            do_scroll_y: True
            bar_color: 0, 0, 255, 1
            bar_width: 12
            MDList:
                id: lst_matches
                size_hint: None, None  # set size_hint_y to None
                width: root.width - (img_crossword.image_ratio * root.height)
                height: self.minimum_height
                spacing: dp(0)
                padding: dp(0)
        BoxLayout:
            orientation: 'horizontal'
            size_hint: None, 0.2
  • Related