Add a delete button to rows in a ListView

I was playing around a bit with the simple To-Do app here.

First, in the setup_tasks() method for the window, I added a sorter to show the tasks ordered by their content label:

    fn setup_tasks(&self) {
        // Create new model
        let model = gio::ListStore::new::<TaskObject>();

        // Get state and set model
        self.imp().tasks.replace(Some(model));

        let sorter = CustomSorter::new(move |obj1, obj2| {
            let task_object_1 = obj1
                .downcast_ref::<TaskObject>()
                .expect("The object needs to be of type TaskObject");
            let task_object_2 = obj2
                .downcast_ref::<TaskObject>()
                .expect("The object needs to be of type TaskObject");

            let content_1 = task_object_1.content();
            let content_2 = task_object_2.content();

            content_1.cmp(&content_2).into()
        });
        let sort_model = SortListModel::new(Some(self.tasks()), Some(sorter.clone()));
        let selection_model = NoSelection::new(Some(sort_model));

        self.imp().tasks_list.set_model(Some(&selection_model));
    }

I also added a remove_button to each task row to remove an item from the tasks list.

I also modified the call to the factory.connect_bind() method to bind an action to the remove_button:

        // Tell factory how to bind `TaskRow` to a `TaskObject`
        factory.connect_bind(clone!(
            #[weak(rename_to = window)]
            self,
            move |_, list_item| {
                // Get `TaskObject` from `ListItem`
                let task_object = list_item
                    .downcast_ref::<ListItem>()
                    .expect("Needs to be ListItem")
                    .item()
                    .and_downcast::<TaskObject>()
                    .expect("The item has to be an `TaskObject`.");

                // Get `TaskRow` from `ListItem`
                let task_row = list_item
                    .downcast_ref::<ListItem>()
                    .expect("Needs to be ListItem")
                    .child()
                    .and_downcast::<TaskRow>()
                    .expect("The child has to be a `TaskRow`.");

                task_row.bind(&task_object);

                let list_item = list_item
                    .downcast_ref::<ListItem>()
                    .expect("Needs to be ListItem");

                task_row.imp().remove_button.connect_clicked(clone!(
                    #[weak]
                    list_item,
                    move |_| {
                        let position = list_item.position();
                        window.remove_task(position);
                    }
                ));

The remove_task(position) method is defined as follows:

impl Window {

    // ...

    fn remove_task(&self, position: u32) {
        println!("remove task at position {}", position);

        let tasks = self.tasks();
        tasks.remove(position);
    }

    // ...

The problem is that the position I define in the line

let position = list_item.position();

is the position of the item as it is shown on screen, whereas

tasks.remove(position)

removes the item at position in the list store, which is different.

For example, if I add tasks labeled 1 and 0 (in that order) and then I try to delete the uppermost item (labeled 0 because they are ordered by their labels), it removes the item I added first instead.

I understand why that is happening, but I’m not sure how I get access the correct index in the list store, or if it is possible to remove an item “directly”, not via it’s index. I couldn’t solve it by searching the docs. I’m grateful for any kind of help.

I’m not entirely sure what the ideal way is to solve this, but one approach is getting the item from the top most model with .get_item(), then finding that item in your original model with .find() which returns the position on which you can run .remove().

1 Like

Hey Jamie, thanks for your reply! Unfortunately I don’t understand which methods you’re referring to. Searching for get_item lead me to the .item() method, is this the one you meant? Searching for find didn’t return anything that seems related. Could you clarify a bit?

I believe they mean the find method on the gio::ListStore object.
So this one: ListStore in gio - Rust

1 Like

Amazing, thanks to both of you, @monster and @HighKingofMelons! So with the code below, it seems to work as expected :slight_smile:

                let list_item = list_item
                    .downcast_ref::<ListItem>()
                    .expect("Needs to be ListItem")
                    .item()
                    .expect("Needs to be bound");

                task_row.imp().remove_button.connect_clicked(move |_| {
                    let tasks = window.tasks();
                    let position = tasks.find(&list_item).expect("Did not find item");
                    tasks.remove(position);
                });
1 Like