gtk-rs / examples

DEPRECATED, use https://github.com/gtk-rs/gtk-rs repository instead!
MIT License
284 stars 75 forks source link

How to display TreeView backed by a Vec of Rust objects #304

Closed kornelski closed 4 years ago

kornelski commented 4 years ago

Examples show how to bind a ListStore or TreeStore to a TreeView, but it seems like this relies on the store being the source of truth for this data, and the data to be stored as separate Values in separate columns.

What I'm missing is how to use Rust-native type as the data source for a TreeView, especially if the TreeView is sortable.

My data lives in a Vec<RustStruct>, and I'd like to display some of the fields of that struct in TreeView's columns.

I can't find a way reliably connect the Vec to the ListStore or use it as the data source for the TreeView. My naive approach of mapping vec[n] to n-th row of the ListStore worked only for non-sortable view, but as soon as the view is sorted, Vec indices don't correspond to ListStore rows any more, so I can't update them (without searching the whole list store for a matching row).

I've tried wrapping my RustStruct to be a GObject subclass:

    let store = ListStore::new(&[RustStructGObjectWrapper::static_type()]);
    tree.set_model(Some(&store)); 

    // creates 3 columns here

but it seems like ListStore wants 1:1 mapping between number of columns in the ListStore:

'assertion failed: columns.len() <= n_columns as usize'

I hoped that TreeViewColumnExt::set_cell_data_func would let me have one object per row, and copy its properties into separate columns of the TreeView, but apparently that's not the way to do it.


So it would be helpful for me to see an example of how to go from struct Model {column1:RefCell<String>, column2:RefCell<String>}; Vec<Model> to a sortable TreeView that displays model's fields, and can update them in the UI when they change.

GuillaumeGomez commented 4 years ago

I used sortable trees in process-viewer and bound the sorting to "hidden" columns. Here are the lines that might be interesting for you: https://github.com/GuillaumeGomez/process-viewer/blob/15e9252f096f831adda747cb693f0d5c27c1ecab/src/display_procs.rs#L66-L75 and https://github.com/GuillaumeGomez/process-viewer/blob/15e9252f096f831adda747cb693f0d5c27c1ecab/src/display_procs.rs#L177-L180

kornelski commented 4 years ago

But this is the thing I'm struggling with: you're storing the data in the ListStore. I have my data in a Vec.

You're filling the store once, fully, with insert_with_values, and don't seem to update later.

But if vec[7] changed in my program, how do I know where vec[7]'s data is in the TreeView? which cell do I update?

GuillaumeGomez commented 4 years ago

I actually do here: https://github.com/GuillaumeGomez/process-viewer/blob/15e9252f096f831adda747cb693f0d5c27c1ecab/src/process_viewer.rs#L100-L116

kornelski commented 4 years ago

So that's a linear search. It's going to be O(n^2) for me, because in my program I emit an event "7th row of the Vec changed, 3rd row of the Vec changed, 11th row of the Vec changed". If I use a treeiter to scan through entire store every time to find where my Vec's data ended up in, it'll be super slow.

I have my expectations set by Cocoa, where I can say "treeview.column1.text binds to mymodel.field1" and vec[7].field1.set() sends a message to treeview.column1 to update itself.

but here I have column.add_attribute(&cell, "text", 0); where 0 is the column number, not name of the field in the model.

GuillaumeGomez commented 4 years ago

You can try the other way around: using a vec of references to the liststore. Then no need to have the data twice.

kornelski commented 4 years ago

Does the TreeIter survive sorting? i.e. if it was iter of 0th row, and user reverses sorting, is it going start pointing to the last row?

GuillaumeGomez commented 4 years ago

Good question... I'm pretty sure you can have access to the data location directly, never looked how however. If you find out, please tell me. :3

kornelski commented 4 years ago

gtk_list_store_set_valuesv: assertion 'iter_is_valid (iter, list_store)' failed

Nope, I can't store iters.

kornelski commented 4 years ago

I think I've got a solution:

  1. Use an unsorted ListStore for 1:1 relationship with Vec, and copy data back and forth using index<>row mapping
  2. Prevent ListStore from being mixed up by wrapping it TreeModelSort https://stackoverflow.com/a/19063670/27009