JComboBox setSelectedItem Bug or Feature? - java

the Original code of set selected Item is:
public void setSelectedItem(Object anObject) {
Object oldSelection = selectedItemReminder;
Object objectToSelect = anObject;
if (oldSelection == null || !oldSelection.equals(anObject)) {
if (anObject != null && !isEditable()) {
// For non editable combo boxes, an invalid selection
// will be rejected.
boolean found = false;
for (int i = 0; i < dataModel.getSize(); i++) {
E element = dataModel.getElementAt(i);
if (anObject.equals(element)) {
found = true;
objectToSelect = element;
break;
}
}
if (!found) {
return;
}
}
in my opinion the line
if (anObject.equals(element)) {
should be
if (element.equals(anObject)) {
Consider a Combo box displaying eg. languages
then you hav a class like
class Language {
String code; // eg. "en"
String name; // eg. "English"
...
}
if you add Language items to your ComboBox the toString function is used to Display an Item. In the above class the toString function would return the name. A call setSelectedItem("en") fails because
String.equals(Language) will fail because Language.toString() will return "English"
the other way round Language.equals(String) would be helping because the class Language could override
boolean equals(String comp) {
return comp.equals(code)
}
Just for clarification, I know how to create a Combobox with the desired behaviour, my question is: is the comparison in the original code a bug or did I miss something fundamental?

The properly implemented Object.equals is symmetric, meaning that there should be no difference between anObject.equals(element) and element.equals(anObject).
You are describing a situation where the combobox model contains objects of type Item, but you want to select select an item by specifying an object of type Prop, where the value of Prop describes some property of Item.
Using technically incorrect implementation of equals() method you can select a combobox item by passing an instance of Prop instead of Item.
With the original code, you will have to provide broken equals() implementation in Prop class, and with your modification you will have to provide broken equals() implementation in Item class. If the Prop is some library class (as String in your example) then the former case is, of course, impossible, and i assume that the reason for your proposed modification is to allow the latter case.
I am not sure that library creators tried to prevent programmers from implementing broken equals() by choosing that specific anObject.equals(element) expression, but even if it was element.equals(anObject) it would still be bad practice to provide deliberately incorrect equals() implementation just for the sake of simplifying the combobox selection.
The proper way to perform selection by property would be to search the combobox data for the item with the required properties or to create a completely new instance of Item with the desired properties and then pass that item into the setSelectedItem.
If you are lucky to already use Java 8 then selecting the required item from a list is one-liner, and if not then you will have to write some boilerplate code with cycle, but at least you will have the proper equals implementation and clean conscience.

To override the inherited equals method, you should pass an object as parameter and not String
public boolean equals(Object obj){
//code goes here
}

Related

JavaFx ComboBox binding confusion

I have an I18N implementation that binds JavaFX UI elements through properties, for e.g.:
def translateLabel(l: Label, key: String, args: Any*): Unit =
l.textProperty().bind(createStringBinding(key, args))
Having a property binding is easy and works well. However I struggle with ComboBox as it takes an ObservableList (of Strings in my case) and I have no idea how to bind my translator functions to that. I am conflicted about the difference between ObservableValue, ObservableList and Property interfaces as they all sound the same.
It has itemsProperty() and valueProperty() however the documentation for these is lacking and vague so I am not sure where they can be used.
What I want to do is have a ComboBox where all elements (or at least the selected / visible one) changes the language dynamically (I18N) as if it was bound, just like a property.
EDIT:
Just to make it easier understand, my current implementation is:
private def setAggregatorComboBox(a: Any): Unit = {
val items: ObservableList[String] = FXCollections.observableArrayList(
noneOptionText.getValue,
"COUNT()",
"AVG()",
"SUM()"
)
measureAggregatorComboBox.getItems.clear()
measureAggregatorComboBox.getItems.addAll(items)
}
Where noneOptionText is a StringProperty that's already bound to a StringBinding that's translated upon class instantiation in this manner:
def translateString(sp: StringProperty, key: String, args: Any*): Unit =
sp.bind(createStringBinding(key, args))
The itemsProperty() is the list of items to show in the combo box popup; it's value is an ObservableList.
The valueProperty() is the selected item (or the value input by the user if the combo box is editable).
What I'd recommend is to have the data in the combo box be the list of keys, and use custom cells to bind the text in each cell to the translation of those keys. I don't speak scala, but in Java it looks like:
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll(getAllKeys());
class TranslationCell extends ListCell<String> {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (empty || item == null) {
setText("");
} else {
textProperty().bind(createStringBinding(item));
}
}
}
comboBox.setCellFactory(lv -> new TranslationCell());
comboBox.setButtonCell(new TranslationCell());
Note now that the valueProperty() contains the key for the selected value.
If you really want to bind the items to an ObservableValue<ObservableList<String>> you can do something like:
comboBox.itemsProperty().bind(Bindings.createObjectBinding(() ->
FXCollections.observableArrayList(...),
...));
where the first ... is a varargs of String values, and the second ... is an observable value, changes in which would prompt the list to be recomputed. (So in your case, I'm guessing you have an ObservableValue<Locale> somewhere representing the current locale; you would use that for the second argument.)
In your specific use case (where only the first element of the list is internationalizable), it might be easier simply to use a listener:
comboBox.getItems().setAll(
noneOptionTest.getValue(),
"COUNT()",
"AVG()",
"SUM");
noneOptionTest.addListener((obs, oldVal, newVal) ->
comboBox.getItems().set(0, newVal));
though I agree this is slightly less elegant.
For completeness:
I am conflicted about the difference between ObservableValue,
ObservableList and Property interfaces as they all sound the same.
ObservableValue<T>: represents a single value of type T which can be observed (meaning that code can be executed when it changes).
Property<T>: represents a writable ObservableValue<T>; the intention is that implementations would have an actual variable representing the value. It defines additional functionality allowing its value to be bound to other ObservableValue<T>.
So, for example:
DoubleProperty x = new SimpleDoubleProperty(6);
DoubleProperty y = new SimpleDoubleProperty(9);
ObservableValue<Number> product = x.multiply(y);
x and y are both Property<Number>; the implementation of SimpleDoubleProperty has an actual double variable representing this value, and you can do things like y.set(7); to change the value.
On the other hand, product is not a Property<Number>; you can't change its value (because doing so would violate the binding: the declared invariant that product.getValue() == x.getValue() * y.getValue()); however it is observable, so you can bind to it:
BooleanProperty answerCorrect = new SimpleBooleanProperty();
answerCorrect.bind(product.isEqualTo(42));
etc.
An ObservableList is somewhat different: it is a java.util.List (a collection of elements), and you can observe it to respond to operations on the list. I.e. if you add a listener to an ObservableList, the listener can determine if elements were added or removed, etc.

ComboBox not showing bound values

I have a comboBox cb and an ObservableList<StringProperty> data
I have bound the cb's Items to data as follows:
Bindings.bindContent(cb.getItems(), data);
Suppose data has the following items: str1, str2, str3, str4
When I change data, the combobox gets the new list without any problem.
But if str3 is selected in cb and I change the value of str3 to NewStr3 in data, that change is not getting displayed in cb. And sometimes the list displayed is also wrong (it shows str3 instead of NewStr3) eventhough underlying data it refers is correct.
How can I force combobox to display new values when the underlying model is changed?
The selected item in a combo box is not required to be an element of the combo box's items list. (For example, in an editable combo box, you can type in an item which is not in the list.) If you think about your example from this perspective, it's no surprise that it behaves as you describe.
If you want to force the selected value to be an element of the underlying list when that list may change, you need to define how the selected item should change if the list changes in a way in which it no longer contains the selected item (it is not obvious how you will do this, and probably depends on your application logic). Once you know what you want to do, you can implement it with a ListChangeListener:
cb.getItems().addListener((ListChangeListener.Change change) -> {
String newSelectedItem = ... ; // figure item that should be selected instead
cb.setValue(newSelectedItem);
});
The simplest implementation would be just cb.setValue(null);, which would mean no item was selected if the list changed so that it no longer contained the currently selected item.
Oops ... mis-read the comboBox for a choiceBox - while the basics of this answer apply to both combo- and choiceBox, I don't have a custom ComboBoxX - yet :-)
Basically, it's the responsibility of the SelectionModel to update itself on changes to the items. The intended behaviour implemented in core is to completely clear the selection - that is, null the selectedItem and set selectedIndex to -1 - if the old item was the selectedItem and is replaced or removed. The typical solution for custom behaviour is to implement a custom selection model and set it:
/**
* A SelectionModel that updates the selectedItem if it is contained in
* the data list and was replaced/updated.
*
* #author Jeanette Winzenburg, Berlin
*/
public static class MySelectionModel<T> extends ChoiceBoxSelectionModel<T> {
public MySelectionModel(ChoiceBoxX<T> cb) {
super(cb);
}
#Override
protected void itemsChanged(Change<? extends T> c) {
// selection is in list
if (getSelectedIndex() != -1) {
while (c.next()) {
if (c.wasReplaced() || c.wasUpdated()) {
if (getSelectedIndex() >= c.getFrom()
&& getSelectedIndex() < c.getTo()) {
setSelectedItem(getModelItem(getSelectedIndex()));
return;
}
}
}
}
// super expects a clean change
c.reset();
super.itemsChanged(c);
}
}
// usage
myChoiceBox.setSelectionModel(new MySelectionModel(myChoiceBox));
Unfortunately, core choiceBox doesn't play by the rule - it severely interferes with model's responsibilities (probably because the model implementation doesn't stand up to its duties) which requires a complete re-write of the whole collaborator-stack (choiceBox, -skin, copied -behaviour) such as ChoiceBoxX - which I did just to learn a bit, try remove some of its smells and fix some bugs.

Java Swing - DefaultListModel - Printing all object information, when i only want to print one field

I have this DefaultListModel
DefaultListModel listModel;
//constructor does the right hting... etc.. I skipped over a lot of code
JList jlist_available_items;
....
jlist_available_items= new JList(cartModel); //etc
Everything is working almost perfectly the issue is that
listModel.addElement(product);
if I change it to product.name it will look correctly, but behave wrongly [the object itself won't be accesisble, only the name]
Is adding the object to the view, and all I want to add is the object name.
When I change it to the name it causes all sorts of issues, because I store the objects in a hashmap, and the hashmap uses objects as keys, not the product.name string.
The reason is so that this method can search the hashmap for the right object.
for (Product p : store.database.keySet()) {
if (jlist_available_items.getSelectedValuesList().contains(
(Product) p)) { // if item is selected
cart.addItem(p);
}
}
How can I fix this?? I have been trying to fix it and related bugs for almsot two hours = ( !
Also sample output is
Product [description=descrion test, name=test]
That is what it is printing. I just want it to print the name. = (!
Also the objects are in a hashmap. I can just iterate through the hashmap until an object has the same name value and then use that, but I don't want to. I want a more proper and scalable solution, namely because I am having so much trouble thinking of one.
BY THE WAY! This is a GUI app in Swing! If you want images just ask = )!
EDIT: And now nmy list cell renderer is broken! It was working just a moment ago... = (
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
Product product = (Product) value;
return this;
}
}
By default, the toString() method of the objects in the model is called to display the list element. And your Product.toString() method returns Product [description=descrion test, name=test].
If you want to display something else, then use a ListCellRenderer, as explained in the swing tutorial about JList.
EDIT: your renderer has a bug: it doesn't set the text of the returned component (which is a JLabel). It should be:
Product product = (Product) value;
setText(product.getName());
return this;

get object from ComboBox

Here is my problem.
I have a generic class called FilteredComboBox that extends ComboBox. It is basically an editable combobox that filteres choices according to user input. This FilteredCombobox is feed by ObservableList of type Book, which is just a simple class with 2 fields, name and id (obviously it has getters, setters and toString).
After user makes his choice and clicks on book that he wants from the dropdown, I would like to get this book id by the method in book class called getBookId. Unfortunately when I say bookComboBox.getValue.getBookId I get cast exception, because getValue automatically calls toString method.
Is there a way around it? I would like to make getValue() method to return an object of type book and just a\call my getBookId() from there.
Any ideas?
For combo like
ComboBox<Book> combobox = new ComboBox<>(your_book_list);
get the selected book item on event (on button action event for instance)
Book selectedBook = combobox.getSelectionModel().getSelectedItem();
Integer id = selectedBook.getBookId();
Yes it does extend ComboBox
I solved my problem by doing his:
public T getChosenValue() {
int index = getSelectionModel().getSelectedIndex();
if(filter.size() != 0)
{
System.out.println("filter size is not 0");
return filter.get(index);
}
else
{
System.out.println("filter size is 0");
return items.get(index);
}
}
Since i have 2 observable lists, items and filtered I had to do this if, else. Works well, I will see if it doesnt give me any bugs when I test it.

How to disabled a combobox according to the selection in another combobox?

I have this block of code that is giving me results for a combo box, I would like it to ignore the combo box and disable it when the value "SDO/OD" is selected in the one above under the combo box for ROLE aka fcbRole. The following enables the box from the first part, but the second part does not fire off. And it gives me a warning: "This field is required"...Have you seen something like this before?
I have been tinkering with:
fcbRole.addSelectionChangedListener(new SelectionChangedListener<ModelData>()
{
#Override
public void selectionChanged(SelectionChangedEvent<ModelData> se)
{
if ("SDO/OD".equals(this.toString()))
{
fcbOfficeRegion.enable();
} else
{
fcbOfficeRegion.disable();
}
}
});
Don't use == and != to compare Strings, instead use:
if("SDO/OD".equals(this.getStringName()) // or make sure you override toString()
// enable
else
// disable
For String value equality, use equals() method and not operators. Operators does reference equality check.
So, change your code to:
if ("SDO/OD".equals(this.toString()))
{
fcbOfficeRegion.enable();
} else
{
fcbOfficeRegion.disable();
}

Categories