Setting current-index on ComboBox does not work in some situations
Bug Description
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
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.
@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?
@FloVanGH the changed callback are run in the next event loop iteration when both model and the index have been changed.
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);
}
?
You can use slint::invoke_from_event_loop that does that. (or slint::spawn_local)
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)
}
}
}