问题
I have an ObservableList<MyElement> list = FXCollections.observableArrayList();
public class MyElement
{
private IntegerProperty position;//with getter
//[...]
//somewhere in the constructor taking the list as argument
position.bind(list.indexOf(this));
}
Now I'd like to bind MyElement.position to the actual position in the list i.e. if the position changes in the list (for instance drag and drop in the GUI or anything else) I want the position property to be updated automatically.
Is this possible? Can I make a bidirectional binding between these values?
回答1:
I don't know if I correctly understood your question, but I will try to answer it :-).
The thing is, once a ObservableList (javafx.collections) object does not store some kind of "selected" index state, why do we should bind another integer to it?
I think, in this case, your code should be responsible for store the "selected" index state and expose it to a client code. If this is what you are looking for, I suggest you have three attributes to deal with it:
public class ListSelection<T> {
private ObservableList<T> items = FXCollections.observableArrayList(new ArrayList<T>());
private ObjectProperty<T> selectedItem = new SimpleObjectProperty<>();
private IntegerProperty selectedIndex = new SimpleIntegerProperty(0);
}
The selected element can be controlled using the selectedIndex
attribute.
Then, create a bind to the selectedItem
, to "automatically" update it when selectedIndex
change:
selectedItem.bind(
when(isEmpty(items))
.then(new SimpleObjectProperty<T>())
.otherwise(valueAt(items, selectedIndex))
);
Bindings
should have been imported statically:
import static javafx.beans.binding.Bindings.*;
Notice the use of method Bindings.valueAt(ObservableList<E> list, ObservableIntegerValue index)
. It creates a bind to the list.get(index.getValue())
element.
Finally, you can use it like this:
ListSelection<String> selection = new ListSelection<>();
Label label = new Label();
List<String> weekDays = Arrays.asList("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday");
selection.getItems().addAll(weekDays);
label.textProperty().bind(selection.selectedItemProperty());
I also suggest you to take a look to javafx.scene.control.SelectionModel
class and its subclasses (eg. javafx.scene.control.SingleSelectionModel
). Maybe, it could be easier to extend some of them.
回答2:
I don't have a bidirectional binding but if you only want the property to be updated when the position changes in the list, you can use this code:
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
/**
* An integer property that's bound to the index of an item in an observable list. For example, if your list
* is ["car", "train", "ship"], then new PositionInListProperty("train", list) will be 1. The binding is unidirectional.
* Changing this property doesn't affect the list.
*
* @param <T> Type of elements in the list.
*/
public class PositionInListProperty<T> extends SimpleIntegerProperty implements ListChangeListener<T> {
private final T item;
private final ObservableList<T> list;
/**
* Creates the property and binds it to an observable list.
* @param item The position of this item in the list is tracked by this property.
* @param list Whenever this list changes, the value of this property is updated.
*/
public PositionInListProperty(T item, ObservableList<T> list) {
this.item = item;
this.list = list;
this.setValue(list.indexOf(item));
list.addListener(this);
}
@Override
public void onChanged(Change<? extends T> c) {
this.setValue(list.indexOf(item));
}
It adds itself as a listener to the list and reacts on all events. You can then use the property (read-only!) as normal. The time complexity of indexOf
is O(n), though, so if you have a long list, you'll want to somehow optimize this.
来源:https://stackoverflow.com/questions/16217814/how-to-bind-the-position-in-a-list-to-an-attribute-of-that-element