KivyMD icon indicating copy to clipboard operation
KivyMD copied to clipboard

MDDataTable - Checkbox for all rows returns data from current page only

Open flisboa999 opened this issue 3 years ago • 9 comments

Description of the Bug

I'm trying to implement a MDDataTable to visualize contacts from a csv file. The goal is to select specific names, pack them up inside a list, and then use this data to send automated messages via WhatsApp Web, using a Selenium Script.

I created an empty list inside my MainApp class and defined a function, using on_check_press, with a conditional: If the data is not inside the list, it appends, if it's already in, it removes.

If you select the checkboxes one by one it's working fine, returns the correct data.

But when you mark down the checkbox from the top-left, the one meant to select all the rows, it will select the checkboxes in all pages, but returns only the data from the current page.

Plus, If you click to select all rows in page 1, and then change to page 2, all the rows on page 2 will be already marked down, and if you click again to select all, it will unmark all of them,

Also found another issue, if you resize the window to half the size and click the checkbox for selecting all the rows, it will crash.

I really wanted to contribute by improving the source code directly, but I looked at the \kivymd\uix\datatables.py file and couldn't understand how it works and where the problems is, cause I'm a noob, lol.

Code and Logs

from kivymd.app import MDApp
from kivymd.uix.screen import Screen
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp

class MainApp(MDApp):

    global contacts
    contacts = []
            
    def build(self):

        screen = Screen()

        table = MDDataTable(

            check = True, 
            use_pagination = True,
            
            column_data = [

                ("name", dp(60)),
                ("number", dp(60)),

            ],

            row_data = [

                ("splinter", "123"),
                ("raphael", "321"),
                ("donatelo", "439"),
                ("leonardo", "892"),
                ("michelangello", "829"),

                ("trevor", "341"),               
                ("michael", "453"),
                ("franklin", "129"),
                ("lester", "880"),
                ("lamar", "192"),

            ]
        )

        table.bind(on_check_press=self.checked)
        screen.add_widget(table)

        return screen

    def checked(self, instance_table, current_row):

        if current_row[0] not in contacts:
            contacts.append(current_row[0])
        else:
            contacts.remove(current_row[0])

        print(contacts)

MainApp().run()

Screenshots

  1. Selecting one by one : https://i.imgur.com/e99sWbs.png

  2. Selecting all checkboxes: https://i.imgur.com/PUgI9qb.png

  3. Moving to the next page and unmarking all the checkboxes: https://i.imgur.com/MQCv6rT.png

Versions

  • OS: W7 Home Premium SP1 64 bits
  • Python: 3.8.10
  • Kivy: 2.0.0
  • KivyMD: 0.104.2

flisboa999 avatar Nov 09 '21 13:11 flisboa999

In the meantime you can use row data.

   self.table.row_data

from MDDataTable class.

sollarp avatar Nov 20 '21 16:11 sollarp

Here is a workaround that I've used to get a list of ids from the table selections:

from kivy.clock import Clock
from kivy.metrics import dp
from kivy.properties import ListProperty
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.screen import MDScreen


class MyScreen(MDScreen):

    my_selections = ListProperty()

    def load_data(self, my_data):
        # create kivy MD table
        row_data = [(obj.identification, obj.latitude, obj.longitude
                    for obj in my_data.values()]
        self.my_table = MDDataTable(use_pagination=True,
                                    check=True,
                                    column_data=[('ID', dp(45)), ('Latitude', dp(25)), ('Longitude', dp(25))],
                                    row_data=row_data)
        self.my_table.bind(on_check_press=self.on_check)
        self.my_table.header.ids.check.bind(on_release=self.on_checkbox_active)
        self.add_widget(self.my_table)
        # reset selections
        self.my_selections = []

    def on_checkbox_active(self, cb):
        if cb.state == 'normal':
            Clock.schedule_once(self.update_checks, 0)

    def on_check(self, instance, row_data):
        Clock.schedule_once(self.update_checks, 0)

    def update_checks(self, _):
        # WARNING: get_row_checks not reliable
        #  see https://github.com/kivymd/KivyMD/issues/924
        #  see https://github.com/kivymd/KivyMD/issues/1123
        self.my_selections = []
        table_data = self.my_table.table_data
        for page, selected_cells in table_data.current_selection_check.items():
            for column_index in selected_cells:
                data_index = int(page * table_data.rows_num + column_index / table_data.total_col_headings)
                self.my_selections.append(table_data.row_data[data_index][0])

not ideal but it does the job.

julien6387 avatar Dec 02 '22 19:12 julien6387

Here is a workaround that I've used to get a list of ids from the table selections:

from kivy.clock import Clock
from kivy.metrics import dp
from kivy.properties import ListProperty
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.screen import MDScreen


class MyScreen(MDScreen):

    my_selections = ListProperty()

    def load_data(self, my_data):
        # create kivy MD table
        row_data = [(obj.identification, obj.latitude, obj.longitude
                    for obj in my_data.values()]
        self.my_table = MDDataTable(use_pagination=True,
                                    check=True,
                                    column_data=[('ID', dp(45)), ('Latitude', dp(25)), ('Longitude', dp(25))],
                                    row_data=row_data)
        self.my_table.bind(on_check_press=self.on_check)
        self.my_table.header.ids.check.bind(on_release=self.on_checkbox_active)
        self.add_widget(self.my_table)
        # reset selections
        self.my_selections = []

    def on_checkbox_active(self, cb):
        if cb.state == 'normal':
            Clock.schedule_once(self.update_checks, 0)

    def on_check(self, instance, row_data):
        Clock.schedule_once(self.update_checks, 0)

    def update_checks(self, _):
        # WARNING: get_row_checks not reliable
        #  see https://github.com/kivymd/KivyMD/issues/924
        #  see https://github.com/kivymd/KivyMD/issues/1123
        self.my_selections = []
        table_data = self.my_table.table_data
        for page, selected_cells in table_data.current_selection_check.items():
            for column_index in selected_cells:
                data_index = int(page * table_data.rows_num + column_index / table_data.total_col_headings)
                self.my_selections.append(table_data.row_data[data_index][0])

not ideal but it does the job.

Thank you this works great! My issue was that the checks-states weren't properly updated after using the update_row() of the datatable. I'm now using self.my_selection instead of get_row_checks() and it does the job perfectly.

LeisAlAyoubi avatar Jun 12 '23 13:06 LeisAlAyoubi

Hi, I tried your code. It is working fine except one condition, when i select the header checkbox ,it is appending all the elements in the selection list but when i am trying to print the list outside the loop, it is printing 10 times. Can you please help to figure out this thing ?

27Vaishali avatar Nov 06 '23 20:11 27Vaishali

Hi, I tried your code. It is working fine except one condition, when i select the header checkbox ,it is appending all the elements in the selection list but when i am trying to print the list outside the loop, it is printing 10 times. Can you please help to figure out this thing ?

Can you please provide an image and the code snippet where you try to make a print(). When you said you have a print statement out side of the loop but executed 10 times there must be an other loop behind that or something calling the function to do that. I have not looked this code for a long time... but might be able to help..

sollarp avatar Nov 07 '23 13:11 sollarp

Thank you for replying . This is my code class TableScreen(Screen): data_table = None # Define data_table as an instance variable my_selections = ListProperty() def on_enter(self): self.create_custom_table() def create_custom_table(self): df = pd.read_csv("Token_Id_File.csv") result_data = [(str(index + 1), value[0]) for index, value in enumerate(df.values)]

    self.data_table = MDDataTable(  # Use self.data_table
        use_pagination=False,
        size_hint=(0.9, 0.9),
        check=True,
        rows_num=100,
        background_color_header="#008000",
        column_data=[
            ("No.", dp(30)),
            ("Ticker Name", dp(30)),
        ],
        row_data=result_data,
    )

    self.data_table.bind(on_check_press=self.on_check)  # Bind the on_check_press function
    self.data_table.header.ids.check.bind(on_release=self.on_checkbox_active)
    layout = BoxLayout(orientation="vertical")

    # Add the "Select All" button before the "Go Back" button
    layout.add_widget(self.data_table)
    print(self.my_selections)
    Submit = MDRaisedButton(
        text="Submit",
        pos_hint={"center_x": 0.2},
    )

    Submit.bind(on_release=self.Submit)
    layout.add_widget(Submit)
    self.add_widget(layout)

def on_checkbox_active(self, cb):
    if cb.state == 'normal':
        Clock.schedule_once(self.update_checks, 0)

def on_check(self, instance, row_data):
    Clock.schedule_once(self.update_checks, 0)

def update_checks(self, _):

    self.my_selections = []
    data_table = self.data_table.table_data
    for page, selected_cells in data_table.current_selection_check.items():
        for column_index in selected_cells:
            data_index = int(page * data_table.rows_num + column_index / data_table.total_col_headings)
            self.my_selections.append(data_table.row_data[data_index][1])
    print(self.my_selections)

def Submit(self, instance):
    self.manager.current = "login"
   

27Vaishali avatar Nov 07 '23 14:11 27Vaishali

Thank you for replying . This is my code class TableScreen(Screen): data_table = None # Define data_table as an instance variable my_selections = ListProperty() def on_enter(self): self.create_custom_table() def create_custom_table(self): df = pd.read_csv("Token_Id_File.csv") result_data = [(str(index + 1), value[0]) for index, value in enumerate(df.values)]

    self.data_table = MDDataTable(  # Use self.data_table
        use_pagination=False,
        size_hint=(0.9, 0.9),
        check=True,
        rows_num=100,
        background_color_header="#008000",
        column_data=[
            ("No.", dp(30)),
            ("Ticker Name", dp(30)),
        ],
        row_data=result_data,
    )

    self.data_table.bind(on_check_press=self.on_check)  # Bind the on_check_press function
    self.data_table.header.ids.check.bind(on_release=self.on_checkbox_active)
    layout = BoxLayout(orientation="vertical")

    # Add the "Select All" button before the "Go Back" button
    layout.add_widget(self.data_table)
    print(self.my_selections)
    Submit = MDRaisedButton(
        text="Submit",
        pos_hint={"center_x": 0.2},
    )

    Submit.bind(on_release=self.Submit)
    layout.add_widget(Submit)
    self.add_widget(layout)

def on_checkbox_active(self, cb):
    if cb.state == 'normal':
        Clock.schedule_once(self.update_checks, 0)

def on_check(self, instance, row_data):
    Clock.schedule_once(self.update_checks, 0)

def update_checks(self, _):

    self.my_selections = []
    data_table = self.data_table.table_data
    for page, selected_cells in data_table.current_selection_check.items():
        for column_index in selected_cells:
            data_index = int(page * data_table.rows_num + column_index / data_table.total_col_headings)
            self.my_selections.append(data_table.row_data[data_index][1])
    print(self.my_selections)

def Submit(self, instance):
    self.manager.current = "login"

The method update_checks is being called 10 times... It makes me think that is the default table size maybe??

sollarp avatar Nov 07 '23 21:11 sollarp

I don't think so

27Vaishali avatar Nov 09 '23 00:11 27Vaishali

Hi. The update_checks is called each time a row is graphically checked. 10 is the default rows_num, i.e. the number of elements displayed in a data table page. when you use the header checkbox to select all items, update_checks is called for each of the 10 items of the page. As I said, not ideal. It's just a workaround until this issue gets fixed by the KivyMD developers.

julien6387 avatar Dec 07 '23 14:12 julien6387