Search code examples
pythonkivykivy-languagekivymdqgridlayout

How to refresh GridLayout in Kivy with kv file


I need help.

I created a small mobile application with Kivy.

I have two screens: ScreenList and ScreenDetail.

However the screen(ScreenList) containing GridLayout does not refresh

ScreenList: contains a list of items

ScreenDetail: Contains the details of a single item.

How the app works:

  1. When I click on the first item on button 1
  2. I go to the details of the item.
  3. I modify the second field. I replace the text: Firt element for First and update data
  4. After recording, I redirect the application to the screens which contain (ScreenList) the list of elements.
  5. But the list of elements remains unchanged then the data has been modified in the database. 6.And when I return to the screen (ScreenDetail) which contains the details, there I see that the data is updated.

How can I refresh the item list in ScreenList?

Here are the pictures as an example

List before update

enter image description here

before update

enter image description here

after update

enter image description here

List after update

enter image description here

Here is the python code:

import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, StringProperty
from kivy.lang import Builder
from kivymd.uix.picker import MDTimePicker
from kivymd.uix.picker import MDDatePicker
from kivymd.app import MDApp
import sqlite3
import os.path

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(BASE_DIR, "donnee/ProjetMaison.db")


def donnee_dic(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d

cur_id = None

class ScreenList(Screen):
    data_grid = ObjectProperty()
    
    def go_to_detail(self, instance):
        global cur_id
        cur_id = int(instance.text)
        self.manager.current = 'screen_detail'
    
    def __init__(self, **kwargs):
        super(ScreenList, self).__init__(**kwargs)
        
        con_list_course = sqlite3.connect(db_path)
        con_list_course.row_factory = donnee_dic
        curss_list_course = con_list_course.cursor()
        data_list_course = curss_list_course.execute("select  * FROM courses")
        self.data_grid.bind(minimum_height=self.data_grid.setter('height'))
        for row in data_list_course:
            template_screen = GridLayout(cols=2, size_hint_y=None, height=40)           
            template_screen.add_widget(Label(text=row['nom']))
            template_screen.add_widget(Button(text=str(row['id']), on_press=self.go_to_detail))
            
            self.data_grid.add_widget(template_screen)

        con_list_course.close()


def Maj_colonne(id, nom):
    try:
        sqliteConnection = sqlite3.connect(db_path)
        cursor = sqliteConnection.cursor()
        sqlite_update_query = """Update courses set nom = ?  where id = ?"""
        columnValues = (nom, id)
        cursor.execute(sqlite_update_query, columnValues)
        sqliteConnection.commit()
        sqliteConnection.commit()
        cursor.close()

    except sqlite3.Error as error:
        print("Erreur de connexion", error)
    finally:
        if sqliteConnection:
            sqliteConnection.close()

class ScreenDetail(Screen):
    label_id = ObjectProperty()
    label_nom = ObjectProperty()
    
    def __init__(self, **kwargs):
        super(ScreenDetail, self).__init__(**kwargs)
        
    def on_enter(self):
        global cur_id
        conn = sqlite3.connect(db_path)

        cursor = conn.execute("select  * FROM courses where id=?",str(cur_id))
        for row in cursor:      
            self.label_id.text = str(row[0])
            self.label_nom.text = str(row[1])

        cursor.close()


    def update_course(self):
        id_pk = self.ids['label_id'].text
        nom = self.ids['label_nom'].text        
        if id_pk:
            Maj_colonne(str(id_pk), str(nom))
            self.ids['label_id'].text = ''
            self.ids['label_nom'].text = ''
            self.manager.current = 'screen_list'        

class Listapp(MDApp):
    def build(self):
        screenmanager = ScreenManager()
        screenmanager.add_widget(ScreenList(name='screen_list'))
        screenmanager.add_widget(ScreenDetail(name='screen_detail'))
        
        return screenmanager

if __name__ == '__main__':
    Listapp().run()

Here is the kv code:

#:import utils kivy.utils                    
<ScreenList>:
    data_grid: data_grid
    MDBoxLayout:
        orientation: 'vertical'
        md_bg_color: app.theme_cls.primary_color
        radius: [25, 0, 0, 0]   
        ScrollView:
            MDGridLayout:
                id: data_grid
                cols: 1
                spacing:10
                size_hint_y:None
                
<ScreenDetail>:
    label_id: label_id
    label_nom: label_nom
    MDBoxLayout:
        orientation: 'vertical'
        md_bg_color: app.theme_cls.primary_color
        ScrollView:
            GridLayout:
                id: detail_grid
                cols:2          
                Label:
                    text: 'Numéro:'
                    bold: True
                TextInput:
                    id: label_id
                    text: ''                
                Label:
                    text: 'Nom:'
                    bold: True
                TextInput:
                    id: label_nom
                    text: ''
                Button:
                    text: 'OK'
                    size_hint_y: None
                    on_press: root.update_course() 

Thank you


Solution

  • Few notes to take, in general, when working with Kivy

    • When you're trying to share data in between screens, it's often useful to use app methods instead of specific methods of screens.

    • And when you need to create lots of buttons, maybe inside a loop, and bind methods on its events( on_press, on_release), it's often bad to create button instances on the fly and bind methods on its events because you'll need to do extra work to make sure that those bound methods are called with right parameters when events are fired. Rather create a custom class template and use that instead.

    Working solution for your problem (only showing sections that has been added/updated

    Created custom GridLayout:

    class MyGrid(GridLayout):
        pass
    
    

    Updated __init__ method inside ScreenList:

       def __init__(self, **kwargs):
            #...
            app = MDApp.get_running_app()
            for row in data_list_course:
                template_screen = MyGrid()
                template_screen.ids.lbl.text = row['nom']
                template_screen.ids.btn.text = str(row['id'])
                
                self.data_grid.add_widget(template_screen)
    

    Added methods inside app class:

    class Listapp(MDApp):
        def build(self):
            self.screenmanager = ScreenManager()
            self.screenmanager.add_widget(ScreenList(name='screen_list'))
            self.screenmanager.add_widget(ScreenDetail(name='screen_detail'))
            
            return self.screenmanager
        
        def go_to_detail_(self, inst):
            self.inst = inst
            self.screenmanager.current = 'screen_detail'
        
        def update_course_(self, label_id, label_nom):
            c = self.inst.children[::-1]
            c[0].text = label_nom.text
            c[1].text = label_id.text
            print(label_id.text, label_nom.text)        
            if label_id.text:
                Maj_colonne(str(id_pk), str(nom))
                label_nom.text = ''
                label_id.text = ''
                self.screenmanager.current = 'screen_list'
    

    Here's the updated kv code:

    #:import utils kivy.utils                    
    <ScreenList>:
        data_grid: data_grid
        MDBoxLayout:
            orientation: 'vertical'
            md_bg_color: app.theme_cls.primary_color
            radius: [25, 0, 0, 0]   
            ScrollView:
                MDGridLayout:
                    id: data_grid
                    cols: 1
                    spacing:10
                    size_hint_y:None
                    
    <ScreenDetail>:
        label_id: label_id
        label_nom: label_nom
        MDBoxLayout:
            orientation: 'vertical'
            md_bg_color: app.theme_cls.primary_color
            ScrollView:
                GridLayout:
                    id: detail_grid
                    cols:2          
                    Label:
                        text: 'Numéro:'
                        bold: True
                    TextInput:
                        id: label_id
                        text: ''                
                    Label:
                        text: 'Nom:'
                        bold: True
                    TextInput:
                        id: label_nom
                        text: ''
                    Button:
                        text: 'OK'
                        size_hint_y: None
                        on_press: app.update_course_(label_id, label_nom)
    
    <MyGrid>:
        cols:2
        size_hint_y:None
        height:40
        
        Label:
            id: lbl
            text: ''
        
        Button:
            id: btn
            text: ''
            on_press:
                app.go_to_detail_(root)