0

I've based this section of my code on the documentation and information from other posts here such as:

How to fetch data from database and show in table in kivy+python

The goal is to have a scrolling word list with just the words as selectable buttons and when you click on the button the word, pronunciation, and translation should be presented in a popup. All of this is pulled from rows in a sqlite database that is constantly updating.

I have it mostly working but the one thing I cannot figure out how to do is to separate the data, which is in the form of a dictionary of strings, to display just the word (column 1) in the list and all of the information (columns 1, 2, and 3) in the popup. I also do not want to have to iterate through the entire dictionary because although the sample db here only has 8 items in it, the real one has thousands. I would like the information to be pulled from the db when I click on the selectable button.

Is there away to either pull the text from the individual rows from the following code-

data: [{'text': f'{entry[0], entry[1], entry[2]}'} for entry in root.rows]

or set the data separately as variables and use those-

data: [{'text': f'{entry[0]}'} for entry in root.rows]

data2: [{'text': f'{entry[1]}'} for entry in root.rows]

data3: [{'text': f'{entry[2]}'} for entry in root.rows]

Is there some other way of doing this that I am missing?

Another issue I am having is that the text on the selectable buttons are being shown twice - on the button and also underneath (you can see it when you shrink the window) and I don't know why or how to fix it.

Any help is appreciated, thanks.

minimum code with databases: https://github.com/nitro9a/word_a_day_minimum

app.py:

import csv
import sqlite3
import random
import textwrap
from utils import database, scalelabel, scrollablelabel, recycleselect
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import BooleanProperty, ListProperty, ObjectProperty, StringProperty

word_dict = {}

class MessageBox(Popup):
    def popup_dismiss(self):
        self.dismiss()

    obj = ObjectProperty(None)
    obj_text = StringProperty('')

    def __init__(self, obj, **kwargs):
        super(MessageBox, self).__init__(**kwargs)
        self.obj = obj
        self.obj_text = obj.text #what is in the message box, will display same on click

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

class SelectableButton(RecycleDataViewBehavior, Button):
    """ Add selection support to the Label """
    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
        print(type(data))
        #print(f'Data: {data.items()}, Index: {index},rv: {rv}, Type: {type(data)}')
        return super(SelectableButton, self).refresh_view_attrs(rv, index, data)

    def apply_selection(self, rv, index, is_selected):
        self.selected = is_selected

    def on_press(self):
        popup = MessageBox(self)
        popup.open()

    def update_changes(self, txt):
        self.text = txt

class RV(RecycleView):
    #data_items = ListProperty([])
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)

class WindowManager(ScreenManager):
    pass

class UnreadWords(Screen):

    unread_table = ObjectProperty(None)
    rows = ListProperty([("Word", "Pronunciation", "English")])

    def __init__(self, **kwargs):
        super(UnreadWords, self).__init__(**kwargs)

    def display_database(self):
        con = sqlite3.connect('italian_unread.db')
        cursor = con.cursor()
        cursor.execute("SELECT Word, Pronunciation, English from Italian_a")
        self.rows = cursor.fetchall()

kv = Builder.load_file("layout.kv")

class WordApp(App):
    def build(self):
        return kv

if __name__=="__main__":
    WordApp().run() 

layout.kv:

#: import NoTransition kivy.uix.screenmanager.NoTransition
#: include italian_a

<RV>:
    viewclass: 'SelectableButton'
    RecycleBoxLayout:
        bcolor: 1,1,1,1
        padding: "15dp", "5dp", "15dp", "15dp"
        default_size: None, dp(25)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'

<SelectableButton>:
    state_image: self.background_normal if self.state == 'normal' else self.background_down
    disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
    _scale: 1. if self.texture_size[0] < self.width else float(self.width) / self.texture_size[0]
    orientation: 'horizontal'
    canvas:
        Color:
            rgba: self.background_color
        BorderImage:
            border: self.border
            pos: self.pos
            size: self.size
            source: self.disabled_image if self.disabled else self.state_image
        PushMatrix
        Scale:
            origin: self.center
            x: self._scale or 1.
            y: self._scale or 1.
        Color:
            rgba: self.disabled_color if self.disabled else self.color
        Rectangle:
            texture: self.texture
            size: self.texture_size
            pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
        PopMatrix

<MessageBox>:
    name: 'mbox'
    lbl: lbl
    title: ''
    size_hint: None, None
    size: 400, 400
    on_open:
        root.obj.update_changes(lbl.text)

    BoxLayout:
        orientation: 'vertical'
        Label:
            id: lbl
            text: root.obj_text
        Button:
            size_hint: 1, 0.2
            text: 'OK'
            on_press:
                root.dismiss()

WindowManager:
    transition: NoTransition()
    UnreadWords:

<UnreadWords>:
    name: "unread"
    unread_table: unread_table

    BoxLayout:
        orientation: "vertical"
        rows: 4
        cols: 1

        GridLayout:
            cols: 2
            rows: 1
            size_hint_y: 5

            ScaleButton:
                id: page2
                text: "Page 2"
                on_release:
                    app.root.current = "unread"
                    root.display_database()

        GridLayout:
            cols: 1
            rows: 1
            size_hint_y: 5

            ScaleLabel:
                text: "Unread Words"
                size_hint_y: 5
                color: (0/255., 0/255., 0/255., 1)
                background_normal: ''
                bcolor: (155/255., 155/255., 155/255., 1)

        GridLayout:
            cols: 1
            rows: 1
            size_hint_y: 80

            BoxLayout:
                id: unread_table
                RV:
                    id: dat
                    viewclass: 'SelectableButton'
                    size_hint_y: 1
                    font_size: self.height * 0.5

                    data: [{'text': f'{entry[0], entry[1], entry[2]}'} for entry in root.rows]
                    #data: [{'text': f'{entry[0]}'} for entry in root.rows]

Image of label being displayed twice

mpierce
  • 1
  • 3

1 Answers1

0

I think the easiest way to handle this would be to save the word data in a Dictionary, and use that Dictionary to feed the RecycleView and the MessageBox. To do that, I removed the global word_dict from your app.py, and added it to your UnreadWords Screen as:

class UnreadWords(Screen):

    unread_table = ObjectProperty(None)
    rows = ListProperty([("Word", "Pronunciation", "English")])

    def __init__(self, **kwargs):
        super(UnreadWords, self).__init__(**kwargs)
        self.word_dict = {}

    def display_database(self):
        con = sqlite3.connect('italian_unread.db')
        cursor = con.cursor()
        cursor.execute("SELECT Word, Pronunciation, English from Italian_a")
        self.rows = cursor.fetchall()

        # populate the self.word_dict dictionary
        for row in self.rows:
            self.word_dict[row[0]] = [row[1], row[2]]

        # create the `data` for the RecycleView
        self.ids.dat.data = [{'text': key} for key in self.word_dict.keys()]

Since the RecycleView data is being created in the above code, the data: property is not needed in the kv file. so the RV entry looks like:

            RV:
                id: dat
                viewclass: 'SelectableButton'
                size_hint_y: 1
                font_size: self.height * 0.5

And the MessageBox class becomes:

class MessageBox(Popup):
    def popup_dismiss(self):
        self.dismiss()

    obj = ObjectProperty(None)
    obj_text = StringProperty('')

    def __init__(self, obj, **kwargs):
        super(MessageBox, self).__init__(**kwargs)
        self.obj = obj

        # set the Popup text to the pronunciation and translation
        # from the word_dict
        word_data = kv.get_screen('unread').word_dict[obj.text]
        self.obj_text = word_data[0] + '\n' + word_data[1]

There is code in the MessageBox that modifies the SelectableButton. I'm not sure of its purpose, but that would probably need to be modified.

John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • Thanks, that seems to work perfectly. I tested it in the full program and made a few adjustments. word_dict was leftover from the code I removed to post it here. The first page has a button you press to get a random word which is then removed from the "unread" words db and added to the "read" words db. I thought I could leave it as a global because both the RandomWords class and the UnreadWords class page pull from the same db but it was throwing key errors on getting a random word. So I removed word_dict as a global, added it to RandomWords, and added an unread_dict to the UnreadWords class. – mpierce Jul 17 '19 at 18:47
  • I still have the issue of the labels being printed twice but you have solved my main issue, thank you. I have added an image of the issue. – mpierce Jul 17 '19 at 18:54
  • I can't find the text that shows up in your image anywhere in your code and I don't see that effect. Does this show up when you click on a `SelectableButton`? If so, it may be related to the `on_open` property of the `MessageBox`. Try commenting that out. – John Anderson Jul 17 '19 at 22:46
  • Sorry, the text from that example are from a db file which I didn't know how to include here. I had already had removed the on_open property at your previous suggestion. I found the issue though- I needed to add a dash before : in the kv file: <-SelectableButton>. With this widget, I was combining the attributes of a scaling button and a selectable button. I'm not sure but I think that without the dash it was using both the custom canvas instructions plus the instructions inherited from the widget. Thanks again for helping me to clear these issues up. – mpierce Jul 18 '19 at 16:01