slint icon indicating copy to clipboard operation
slint copied to clipboard

Setting current-index on ComboBox does not work in some situations

Open npwoods opened this issue 10 months ago • 6 comments

Bug Description

bug-demo.zip

In the attached example, main.slint looks like this:

import { ComboBox } from "std-widgets.slint";

export component AppWindow inherits Window {
    in property <[string]> combobox-model;
    in-out property <int> combobox-index <=> my-combobox.current-index;
    my-combobox := ComboBox {
        model: root.combobox-model;
    }
}

src/main.rs:

fn main() {
    let app_window = AppWindow::new().unwrap();

    let items = [
        "not this1",
        "not this2",
        "!!!THIS!!!",
        "not this3",
        "not this4",
    ];
    let model = VecModel::from_iter(items.iter().copied().map(SharedString::from));
    let model = ModelRc::new(model);
    app_window.set_combobox_model(model);
    app_window.set_combobox_index(2);
    app_window.run().unwrap();
}

I would expect this to result in a ComboBox with !!!THIS!!! selected. But it seems to not stick. Curiously, if we don't specify the model and put the ComboBox items directly in main.slint, this works

Reproducible Code (if applicable)


Environment Details

  • Slint Version: 1.9.2
  • Platform/OS: Windows
  • Programming Language: Rust

Product Impact

No response

npwoods avatar Feb 13 '25 23:02 npwoods

Thank you for reporting your problem.

This might be related to #6619, which is about the ComboBox showing the wrong item. I do not think it is the same issue though.

hunger avatar Feb 14 '25 10:02 hunger

@ogoffart

I have investigate this a little. Looks like the problem is that first the changed callback of current-index is triggered and after that model changed callback. Even if the order in the Rust code is:

  app_window.set_combobox_model(model);
   app_window.set_combobox_index(2);

Internal the callbacks are used to prevent current-index from being reset after the complete model is changed.

I have also tried a workaround in the ComboboxBase to use a timer to update the current-index but it changes nothing, the order of updating is still switched:

 reset-timer := Timer {
        interval: 250ms;
        running: false;

        triggered => {
            root.current-index = 0;
            self.running = false;
        }
    }

Do you have an explanation for this behavior and an idea how to fix it?

ghost avatar Apr 07 '25 09:04 ghost

@FloVanGH the changed callback are run in the next event loop iteration when both model and the index have been changed.

ogoffart avatar Apr 09 '25 06:04 ogoffart

Is there any workaround, similar to

app_window.set_combobox_model(model);
app_window.run_in_next_iteration_loop(move |app_window| {
     app_window.set_combobox_index(2);
}

?

qarmin avatar Apr 26 '25 17:04 qarmin

You can use slint::invoke_from_event_loop that does that. (or slint::spawn_local)

ogoffart avatar May 02 '25 11:05 ogoffart

You can assing target index to a buffer with logic value that allows to extract buffer once and only in "changed" event loop. This example with nested ComboBox models works correct:

component Example inherits VerticalLayout {
    element := ComboBox {
        model: [
            "MODEL0",
            "MODEL1",
        ];
        changed current-index => {
            dependent-element.current-value = dependent-element.model[0]
        }
    }

    property <[[string]]> MODELS: [
        [
            "ITEM0-0",
            "ITEM0-1",
        ],
        [
            "ITEM1-0",
            "ITEM1-1",
            "ITEM1-2",
        ],
    ];
    property <{active: bool, index: int}> buf;
    function set-both-elements-indexes(element-new-index: int, dependent-element-new-index: int) {
        if element.current-index == element-new-index {
            dependent-element.current-index = dependent-element-new-index;
        } else {
            element.current-index = element-new-index;
            buf = { active: true, index: dependent-element-new-index };
        }
    }
    dependent-element := ComboBox {
        model: MODELS[element.current-index];
        changed model => {
            self.current-index = 0; // implicit
            if buf.active {
                self.current-index = buf.index;
                buf.active = false;
            }
        }
    }

    Button {
        text: "Set indexes to (1, 2)";
        clicked => {
            set-both-elements-indexes(1, 2)
        }
    }
}

Stevasha avatar Oct 04 '25 11:10 Stevasha