I am currently working on a game with Java and JavaFX. I am using a JavaFX ComboBox.
The following example should explain my problem.
Let's say I have a class "Animal" with the attributes "name", "age" and "color".
First file:
public class Animal {
private String name;
private int age;
private String color;
public Animal(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
}
Now I want to create a ComboBox with each animal I create.
Second file:
ComboBox<Animal> comboBoxAnimal = new ComboBox();
ObservableList<Animal> comboBoxItems = FXCollections.observableArrayList();
Animal dog = new Animal("Liam", 2, "Brown");
Animal cat = new Animal("Emily", 5, "Gray");
Animal bird = new Animal("Kian", 3, "Green");
comboBoxItems.addAll(dog, cat, bird);
comboBoxAnimal.setItems(comboBoxItems);
Currently I get only "Animal#xxxxxxxx" which is understandable because I have a ComboBox of Animals but want only the names (Strings) to be presented.
Just simply creating a ComboBox<String> won't solve the problem as I need a Combobox<Animal>.
How can I get a Combobox<Animal> but as elements show only the names of each Animal?
Thanks for your feedback :)
Two options:
Use a cell factory.
Use a string converter.
The cell factory and string converter examples used in this answer produce the identical output:
Cell Factory Implementation
Use a cell factory, like in this answer:
How can I Populate a ListView in JavaFX using Custom Objects?
The linked answer is for a ListView, but the ComboBox is similar, as is a TableView or other virtualized controls that rely on cell factories for display.
To configure cells for the ComboBox drop-down list and button, make calls to both setCellFactory and setButtonCell.
This is the most flexible solution and allows for customization beyond just strings of text. Graphic nodes can be created to completely customize the visual representation of each combo box cell.
Example Code
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.stage.Stage;
public class AnimalComboApp extends Application {
public record Animal(String name, int age, String color) {}
public static class AnimalCell extends ListCell<Animal> {
#Override
public void updateItem(Animal animal, boolean empty) {
super.updateItem(animal, empty);
if (animal == null || empty) {
setText(null);
} else {
setText(animal.name());
}
}
}
#Override
public void start(Stage stage) throws Exception {
ComboBox<Animal> comboBox = new ComboBox<>();
comboBox.getItems().setAll(
new Animal("Liam", 2, "Brown"),
new Animal("Emily", 5, "Gray"),
new Animal("Kian", 3, "Green")
);
comboBox.setCellFactory(listView -> new AnimalCell());
comboBox.setButtonCell(new AnimalCell());
comboBox.getSelectionModel().select(0);
stage.setScene(new Scene(comboBox));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
StringConverter Implementation
An alternative to a cell factory definition is to provide the ComboBox with a StringConverter which can convert to and from a String and an object.
The StringConverter requires fromString and toString to be implemented, but the fromString implementation can just return null unless you also want the user to be able to perform text edits to edit the combo box value.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class AnimalComboApp extends Application {
public record Animal(String name, int age, String color) {}
#Override
public void start(Stage stage) throws Exception {
ComboBox<Animal> comboBox = new ComboBox<>();
comboBox.getItems().setAll(
new Animal("Liam", 2, "Brown"),
new Animal("Emily", 5, "Gray"),
new Animal("Kian", 3, "Green")
);
comboBox.setConverter(new StringConverter<>() {
#Override
public String toString(Animal animal) {
return animal.name();
}
#Override
public Animal fromString(String string) {
return null;
}
});
comboBox.getSelectionModel().select(0);
Scene scene = new Scene(comboBox);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Options NOT to use
toString() implementation
Do not override toString() to customize the cells, that is an anti-pattern.
Use toString() for other purposes such as listing and debugging all the elements of the Animal class, and instead, use the appropriate methods, such as cell factories or string converters, for customizing the UI view of the Animal.
Placing nodes in the combo box list
You might also be tempted to try to put nodes directly in the ComboBox list (for example create Labels with the name of an object in each Label and then create a ComboBox<Label>).
Don't do this, as advised by the ComboBox API documentation section: "A warning about inserting Nodes into the ComboBox items list", it will create bugs in your application.
Related
I have a ChoiceBox where I can select the language for my program. When I select another language, the label gets translated as desired (because it is recomputed using ChoiceBoxSkin#getDisplayText and my StringConverter takes the language into account), but the elements in the popup list stay the same.
Now, I could do something like
public void updateStrings() {
var converter = getConverter();
setConverter(null);
setConverter(converter);
var selected = valueProperty().getValue();
valueProperty().setValue(null);
valueProperty().setValue(selected);
}
in my ChoiceBox-subclass. This will re-populate the popup list with the correctly translated texts. Setting the value again is necessary beacause ChoiceBoxSkin#updatePopupItems (which is triggered when changing the converter) also resets the toggleGroup. That means that the selected item would no longer be marked as selected in the popup list.
Despite being kind of ugly, this actually works for my current use case. However, it breaks if any listener of the valueProperty does something problematic on either setting it to null or selecting the desired item a second time.
Am I missing a cleaner or just all-around better way to achieve this?
Another approach might be to use a custom ChoiceBoxSkin. Extending that, I'd have access to ChoiceBoxSkin#getChoiceBoxPopup (although that is commented with "Test only purpose") and could actually bind the text properties of the RadioMenuItems to the corresponding translated StringProperty. But that breaks as soon as ChoiceBoxSkin#updatePopupItems is triggered from anywhere else...
A MRP should be:
import javafx.scene.control.ChoiceBox;
import javafx.util.StringConverter;
public class LabelChangeChoiceBox extends ChoiceBox<String> {
private boolean duringUpdate = false;
public LabelChangeChoiceBox() {
getItems().addAll("A", "B", "C");
setConverter(new StringConverter<>() {
#Override
public String toString(String item) {
return item + " selected:" + valueProperty().getValue();
}
#Override
public String fromString(String unused) {
throw new UnsupportedOperationException();
}
});
valueProperty().addListener((observable, oldValue, newValue) -> {
if(duringUpdate) {
return;
}
duringUpdate = true;
updateStrings();
duringUpdate = false;
});
}
public void updateStrings() {
var converter = getConverter();
setConverter(null);
setConverter(converter);
var selected = valueProperty().getValue();
valueProperty().setValue(null);
valueProperty().setValue(selected);
}
}
And an Application-class like
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import ui.LabelChangeChoiceBox;
public class Launcher extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new LabelChangeChoiceBox());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
This works but needs the duringUpdate variable and can break if there is another change listener.
I’m not sure if this meets your needs, as your description of the problem is unclear in a few places.
Here’s a ChoiceBox which updates its converter using its own chosen language, and also retains its value when that change occurs:
import java.util.Locale;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.BorderPane;
import javafx.util.StringConverter;
public class FXLocaleSelector
extends Application {
#Override
public void start(Stage stage) {
ChoiceBox<Locale> choiceBox = new ChoiceBox<>();
choiceBox.getItems().addAll(
Locale.ENGLISH,
Locale.FRENCH,
Locale.GERMAN,
Locale.ITALIAN,
Locale.CHINESE,
Locale.JAPANESE,
Locale.KOREAN
);
choiceBox.converterProperty().bind(
Bindings.createObjectBinding(
() -> createConverter(choiceBox.getValue()),
choiceBox.valueProperty()));
BorderPane pane = new BorderPane(choiceBox);
pane.setPadding(new Insets(40));
stage.setScene(new Scene(pane));
stage.setTitle("Locale Selector");
stage.show();
}
private StringConverter<Locale> createConverter(Locale locale) {
Locale conversionLocale =
(locale != null ? locale : Locale.getDefault());
return new StringConverter<Locale>() {
#Override
public String toString(Locale value) {
if (value != null) {
return value.getDisplayName(conversionLocale);
} else {
return "";
}
}
#Override
public Locale fromString(String s) {
return null;
}
};
}
public static void main(String[] args) {
launch(FXLocaleSelector.class, args);
}
}
Not entirely certain whether or not I understand your requirement correctly, my assumptions:
there's a ChoiceBox which contains the "language" for your ui, including the itself: lets say it contains the items Locale.ENGLISH and Locale.GERMAN, the visual representation of its items should be "English", "German" if its value is Locale.ENGLISH and "Englisch", "Deutsch" if its value is Locale.GERMAN
the visual representation is done by a StringConverter configurable with the value
If so, the solution is in separating out concerns - actually, it's not: the problem described (and hacked!) in the question is JDK-8088507: setting the converter doesn't update the selection of the menu items in the drop down. One hack is as bad or good as another, my personal preferenced would go for a custom skin which
adds a change listener to the converter property
reflectively calls updateSelection
Something like:
public static class MyChoiceBoxSkin<T> extends ChoiceBoxSkin<T> {
public MyChoiceBoxSkin(ChoiceBox<T> control) {
super(control);
registerChangeListener(control.converterProperty(), e -> {
// my local reflection helper, use your own
FXUtils.invokeMethod(ChoiceBoxSkin.class, this, "updateSelection");
});
}
}
Note: the hacks - this nor the OP's solution - do not solve the missing offset of the popup on first opening (initially or after selecting an item in the popup).
Not a solution to the question, just one way to have a value-dependent converter ;)
have a StringConverter with a fixed value (for simplicity) for conversion
have a converter controller having that a property with that value and a second property with a converter configured with the value: make sure the converter is replaced on change of the value
bind the controller's value to the box' value and the box' converter to the controller's converter
In (very raw) code:
public static class LanguageConverter<T> extends StringConverter<T> {
private T currentLanguage;
public LanguageConverter(T language) {
currentLanguage = language;
}
#Override
public String toString(T object) {
Object value = currentLanguage;
return "" + object + (value != null ? value : "");
}
#Override
public T fromString(String string) {
return null;
}
}
public static class LanguageController<T> {
private ObjectProperty<StringConverter<T>> currentConverter = new SimpleObjectProperty<>();
private ObjectProperty<T> currentValue = new SimpleObjectProperty<>() {
#Override
protected void invalidated() {
currentConverter.set(new LanguageConverter<>(get()));
}
};
}
Usage:
ChoiceBox<String> box = new ChoiceBox<>();
box.getItems().addAll("A", "B", "C");
box.getSelectionModel().selectFirst();
LanguageController<String> controller = new LanguageController<>();
controller.currentValue.bind(box.valueProperty());
box.converterProperty().bind(controller.currentConverter);
I'm trying to create a student registration system. In this system, students can see course name, course credit, and the instructor of the course by clicking the "Courses" button.For this purpose i have a Courses class, a database, a frame and a JList courslist.
ArrayList<Courses> aq = Database.allCourses();
//allCourses() is a static method in my Database class that returns fields from my Database as an ArrayList<Courses>
courselist.setListData(Driver.converToCoursesArray(aq));
//Driver.converttoCoursesArray() is a static method in my Driver class that takes a ArrayList<Courses> as a paramater and returns a Courses[] array.
Now, my problem is that in my frame, JList always seen like p1.Courses#4532
I've seen a similar problem when i was accidently trying to print an object with System.out.println(). But in this situation i convert the arraylist to an array and my JList holds objects(JList). So i'll be happy if you help me.
You need to override toString() in the Course class, such that it returns the name of the course you want to display.
Take a look at this example:
import javax.swing.*;
import java.awt.*;
public final class Example extends JFrame {
public Example() {
Course[] courses = {
new Course("Course 1"),
new Course("Course 2"),
new Course("Course 3")
};
JList<Course> courseJList = new JList<>(courses);
getContentPane().add(courseJList);
pack();
setMinimumSize(new Dimension(200, 200));
setVisible(true);
}
public static void main(String[] args) {
new Example();
}
}
final class Course {
private final String courseName;
public Course(final String courseName) {
this.courseName = courseName;
}
#Override
public String toString() {
return courseName;
}
}
This displays the following:
so i have a table view with 3 columns and one of them is a column of comboboxes, the way i create the column of combobox is as so
Source = new TableColumn<>("Configure Interface as..");
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
Source.setCellFactory(col -> {
TableCell<TableViewTest, StringProperty> c = new TableCell<>();
ComboBox<String> comboBox = new ComboBox<>(options);
c.itemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
comboBox.valueProperty().unbindBidirectional(oldValue);
}
if (newValue != null) {
comboBox.valueProperty().bindBidirectional(newValue);
}
});
c.graphicProperty().bind(Bindings.when(c.emptyProperty()).then((Node) null).otherwise(comboBox));
return c;
});
the column gets its values from the getter method optionProperty() which resides within my TableViewTest class.
So the problem i'm having is I have another combobox (comboBoxA) that is above my tableview table in my gui, and when ever i change the value of comboBoxA i want to change the values of the comboboxes with the column.
I can do this by calling the following code within the method that is listening for the selection change of comboboxA
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionTwoProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
but the values don't change unless is start scrolling down to near the bottom of the table. is there a way to force the comboboxes to change to the new values within the getter method optionTwoProperty() without me having to scroll down?.
EDIT
Okay so the line
final StringProperty value = i.getValue().optionTwoProperty();
doesnt actaully get called until i start scrolling down.
So, with help from fabian, I think I understand that you want the combo box above the table to change the property in your model class that is represented in the cells in the table column.
One way to do this is to make the type of the combo box function that maps the model class to a property, and populate it with functions mapping to each of the properties you want.
Then you can represent the cell value factory for the table column with a binding that observes all the possible properties that could be represented, along with the selected value in the combo box, and returns the value computed by applying the function from the combo box to the model instance (and retrieving its wrapped value).
For the cell factory for the column, you can observe the selected value in the cell's combo box. When it changes, use the selected item in the combo box above the table to figure out which property to update.
Here's a SSCCE:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithSetAllComboBox extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> itemCol = new TableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> Bindings.createStringBinding(() -> cellData.getValue().getName()));
table.getColumns().add(itemCol);
TableColumn<Item, String> choiceCol = new TableColumn<>("Choice");
ComboBox<Function<Item, StringProperty>> option = new ComboBox<>();
option.getItems().add(Item::choiceProperty);
option.getItems().add(Item::choice2Property);
option.setCellFactory(lv -> createListCell());
option.setButtonCell(createListCell());
option.getSelectionModel().select(0);
ObservableList<String> choices = FXCollections.observableArrayList("First choice", "Second choice", "Third choice");
choiceCol.setCellFactory(col -> {
TableCell<Item, String> cell = new TableCell<>();
ComboBox<String> combo = new ComboBox<>(choices);
cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()).then((Node)null).otherwise(combo));
combo.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! cell.isEmpty() && newValue != null) {
Item item = table.getItems().get(cell.getIndex()) ;
StringProperty property = option.getValue().apply(item);
property.set(newValue);
}
});
cell.itemProperty().addListener((obs, oldItem, newItem) -> combo.setValue(newItem));
return cell ;
});
choiceCol.setPrefWidth(150);
table.getColumns().add(choiceCol);
choiceCol.setCellValueFactory(cellData -> Bindings.createStringBinding(
() -> option.getValue().apply(cellData.getValue()).get(),
cellData.getValue().choiceProperty(),
cellData.getValue().choice2Property(),
option.valueProperty()));
choiceCol.setGraphic(option);
choiceCol.setPrefWidth(200);
for (int i = 1; i <= 30 ; i++) table.getItems().add(new Item("Item "+i ,choices.get(0)));
Button debug = new Button("Debug");
debug.setOnAction(e -> table.getItems().stream().
map(item -> String.format("%s (%s, %s)", item.getName(), item.getChoice(), item.getChoice2())).
forEach(System.out::println));
BorderPane root = new BorderPane(table);
BorderPane.setMargin(debug, new Insets(5));
root.setBottom(debug);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private ListCell<Function<Item, StringProperty>> createListCell() {
return new ListCell<Function<Item, StringProperty>>() {
#Override
public void updateItem(Function<Item, StringProperty> item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item.apply(new Item("", "")).getName());
}
};
}
public static class Item {
private final String name ;
private final StringProperty choice ;
private final StringProperty choice2 ;
public Item(String name, String choice) {
this.choice = new SimpleStringProperty(this, "Choice", choice);
this.choice2 = new SimpleStringProperty(this, "Choice 2", "Second choice");
this.name = name ;
}
public final StringProperty choiceProperty() {
return this.choice;
}
public final java.lang.String getChoice() {
return this.choiceProperty().get();
}
public final void setChoice(final java.lang.String choice) {
this.choiceProperty().set(choice);
}
public String getName() {
return name;
}
public final StringProperty choice2Property() {
return this.choice2;
}
public final java.lang.String getChoice2() {
return this.choice2Property().get();
}
public final void setChoice2(final java.lang.String choice2) {
this.choice2Property().set(choice2);
}
}
public static void main(String[] args) {
launch(args);
}
}
The issue is the TableView not listening to modifications of the cellValueFactory property of the elements of it's columns. Therefore the TableView doesn't know it should redraw it's cells. In JavaFX 8u60 the refresh() method was added for this purpose (for some reason I can't find it in the online javadoc though), which allows you to change the code of your method changing the cellValueFactory like this:
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionTwoProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
tableview.refresh();
In older versions you have to use the workaround of setting the column value to trigger a change in the list:
List<TableColumn<TableViewTest, ?>> columns = tableview.getColumns();
columns.set(columns.indexOf(Source), Source);
But this workaround could cease to work in future versions, since the list is not actually modified with this operation and triggering a list change event is not required by the contract of ObservableList (but replacing the TableColumn with a new instance (and copying the properties) should always work).
Hard to say given the code snippets. Maybe you're not on the javaFX thread when doing the update? In that case use Platform.runLater(...), or share some minimal amout of code to reproduce the problem.
I am new to JavaFX and I am trying to create a simple TreeTableView, containing a
single Boolean column and is rendered with a CheckBoxTreeTableCell.
The problem I am having is that the two CheckBoxTreeItems seem independent (selecting
the root doesn't select the child and the other way around). I even try setting the
independancy manually (see commented code) but it makes no difference.
The documentation for CheckBoxTreeItems says that "By default, CheckBoxTreeItem instances are dependent", which doesn't seem to work for me.
Also, I am expecting toString() value of the Model class to be shown as checkboxes'
texts but no text is drawn, only empty checkboxes. Why is this?
And finally, it is possible to set a graphic node for a CheckBoxTreeItem, and this
node is then shown to the left of the CheckBoxTreeItem. Would it be possible to have
it drawn between the checkbox and the checkbox text instead? Something like:
[x][graphic_node]A simple checkbox text
I am using JDK 1.8.0_40
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.stage.Stage;
public final class CheckBoxTreeItemTest extends Application {
private Stage stage;
public static void main(String[] args) {
launch(args);
}
#Override
public final void start(final Stage stage) throws Exception {
this.stage = stage;
final CheckBoxTreeItem<Model> root = new CheckBoxTreeItem<>(new Model("Root"));
final CheckBoxTreeItem<Model> parent = new CheckBoxTreeItem<>(new Model("Parent"));
final CheckBoxTreeItem<Model> child = new CheckBoxTreeItem<>(new Model("Child"));
//Manually setting independence makes no difference
/*parent.setIndependent(false);
child.setIndependent(false);
root.setIndependent(false);*/
parent.getChildren().add(child);
root.getChildren().add(parent);
final TreeTableColumn<Model, Boolean> selectedColumn =
new TreeTableColumn<>("Selection");
selectedColumn.setEditable(true);
selectedColumn.setCellValueFactory(param -> param.getValue().getValue().selectedProperty());
selectedColumn.setCellFactory(CheckBoxTreeTableCell.<Model>forTreeTableColumn(selectedColumn));
final TreeTableView<Model> table = new TreeTableView<>(root);
table.setShowRoot(false);
table.setEditable(true);
table.getColumns().add(selectedColumn);
final Scene scene = new Scene(table, 500, 350);
stage.setScene(scene);
stage.show();
}
private class Model {
private final BooleanProperty selected;
private final StringProperty name;
public Model(final String name) {
this.selected = new SimpleBooleanProperty(false);
this.name = new SimpleStringProperty(name);
}
public final void setSelected(final boolean selected) {
this.selected.set(selected);
}
public final boolean isSelected() {
return selected.get();
}
public final BooleanProperty selectedProperty() {
return selected;
}
public final StringProperty nameProperty() {
return name;
}
#Override
public String toString() {
return "Model [selected=" + selected + ", name=" + name + "]";
}
}
}
A CheckBoxTreeItem provides a selected property. It is this property that respects the independent state of the CheckBoxTreeItem (i.e. if the parent CheckBoxTreeItem is selected, then this CheckBoxTreeItem is automatically selected, etc).
However, in your application, the CheckBoxTreeItem's selected property is not the property represented by the item, because you set the cell value factory to map to the selected property of the Model instance represented by the item. So checking the check box sets Model.selected to true, but of course there is no logic managing that property in terms of parent and/or child selected properties.
Typically when you have your own boolean property representing the state of the checkbox, you would not use a CheckBoxTreeItem. However, if you want the functionality of the non-independent properties, you would have to implement that yourself. Since that logic is actually quite complicated, if you want your own Model class, I would just bidirectionally bind the property of interest to the CheckBoxTreeItem's selectedProperty:
Model rootModel = new Model("Root");
final CheckBoxTreeItem<Model> root = new CheckBoxTreeItem<>(rootModel);
root.selectedProperty().bindBidirectional(rootModel.selectedProperty());
Model parentModel = new Model("Parent");
final CheckBoxTreeItem<Model> parent = new CheckBoxTreeItem<>( parentModel);
parent.selectedProperty().bindBidirectional(parentModel.selectedProperty());
Model childModel = new Model("Child");
final CheckBoxTreeItem<Model> child = new CheckBoxTreeItem<>(childModel);
child.selectedProperty().bindBidirectional(childModel.selectedProperty());
In order to have a Netbeans liked property inspector windows, I am making use of the following class to help me achieve this.
com.l2fprod.common.propertysheet.PropertySheetPanel
So far, it works fine for class with simple properties like String, int...
However, when come to slightly complicated class with composited relationship, things get more complicated.
For example, I have two animals (interface). One is Cat (Simple class with name and age) and Dog (Another simple class with name and age).
It takes no effort to display them through GUI windows.
However, when come to class with composited relationship. A Zoo, which can contains multiple animals (A class with array list to hold animals), I have problem to display all the animals properties within a single window.
The following is the screen shoot
(source: googlepages.com)
Partial source code is shown here
ObjectInspectorJFrame objectInspectorJFrame0 = new ObjectInspectorJFrame(cat);
objectInspectorJFrame0.setVisible(true);
objectInspectorJFrame0.setState(java.awt.Frame.NORMAL);
ObjectInspectorJFrame objectInspectorJFrame1 = new ObjectInspectorJFrame(dog);
objectInspectorJFrame1.setVisible(true);
objectInspectorJFrame1.setState(java.awt.Frame.NORMAL);
// I wish to see all "animals" and their properties in this windows. :(
// How?
ObjectInspectorJFrame objectInspectorJFrame2 = new ObjectInspectorJFrame(zoo);
objectInspectorJFrame2.setVisible(true);
objectInspectorJFrame2.setState(java.awt.Frame.NORMAL);
Complete source code can be downloaded from
http://yancheng.cheok.googlepages.com/sandbox.zip
I wish within "Zoo" windows, it can display all the properties for all animals.
PropertySheetPanel as is only populates its table reading the properties for a given Java Bean.
You need to extend PropertySheetPanel behaviour and populate the properties from a given Collection. Iterate your collection and use addProperty(Property) to populate the table.
You can also use instrospection or beanutils lib to discover the collection elements.
EDIT: Example added.
package com.stackoverflow.swing.PropertySheetPanel;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.l2fprod.common.propertysheet.DefaultProperty;
import com.l2fprod.common.propertysheet.PropertySheetPanel;
/**
* An example that creates a l2fprod PropertySheetPanel that displays any
* Collection.
*/
public class CollectionPropertySheet<C> extends PropertySheetPanel {
// Choose some bean. An animal as example.
static class Animal {
private String name;
private String family;
public Animal(String name, String family) {
this.name = name;
this.family = family;
}
#Override public String toString() {
return name + " " + family;
}
}
/**
* #param simpleModel The input collection as data model.
*/
public CollectionPropertySheet(Collection<C> simpleModel) {
super();
populateCollectionProperties(simpleModel);
}
private void populateCollectionProperties(Collection<C> collection) {
int index = 0;
for (C entry : collection) {
// Define property properties
DefaultProperty property = new DefaultProperty();
property.setDisplayName(entry.getClass().getSimpleName() + "[" + index++ +"]");
property.setValue(entry.toString());
// Set any other properties ...
// and add.
addProperty(property);
}
}
// Start me here!
public static void main(String[] args) {
// Inside EDT
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
JFrame frame = new JFrame("A simple example...");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CollectionPropertySheet<Animal>(getAnimals()));
frame.pack();
frame.setVisible(true);
}
private Collection<Animal> getAnimals() {
Collection<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal("Lion", "Felidae"));
animals.add(new Animal("Duck", "Anatidae"));
animals.add(new Animal("Cat", "Felidae"));
return animals;
}
});
}
}