bind integer to fillproperty - javafx

I want to bind an integerProperty to the fillProperty of a shape so the change of the integer will change the color of the shape.
Rectangle rect = new Rectangle(20,20);
IntegerProperty intProp = new SimpleIntegerProperty(0);
rect.fillProperty().bind(
Bindings.createObjectBinding(
() -> Color.rgb(intProp.get(), intProp.get(), intProp.get()),intProp));
But I get an error :
A bound value cannot be set
How could I do?

Related

How to get the column title and first column row value for a cell in javafx

I have JavaFX tableview that I have created dynamically. When one double-clicks on a cell on the tableview, I need to get the name of the column the cell is in and the value of the first cell in the row this cell is in. I have tried searching over google and found no particular solution to this problem. Kindly show me some sample code.
Ok, so first, let's assume your TableView is attached to a model:
public TableView<MyModel> myTable;
where MyModel is something like:
public class MyModel {
private Integer id;
private String name;
// ... etc.
}
so, MyModel is a common POJO. You can have columns of your TableView like:
TableColumn<MyModel, Integer> id = new TableColumn<>("ID");
id.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<MyModel, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("name"));
and then, to add the columns to your table:
myTable.getColumns().addAll(id, name);
Now, let's listen to the click event using a rowFactory:
myTable.setRowFactory(tv -> {
TableRow<MyModel> row = new TableRow<>();
row.setOnMouseClicked(event -> {
// check for non-empty rows, double-click with the primary button of the mouse
if (!row.isEmpty() && event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
MyModel element = row.getItem();
// now you can do whatever you want with the myModel variable.
System.out.println(element);
}
});
return row ;
});
That should do the work.

Map integer to string of custom class in combobox

I have a tabelview that displays a list of appointees. Each appointe has a group assigned to it, the id of that group is saved in the appointe class.
I want to display a combobox inside a tablecell that displays the selected group and all other groups that exist. I can set the items of the combobox in the cell factory but i cant set the selected value of the respective appointee.
I have a method that returns the Group from the observable list when i provide it with the id. Thats means i need the id in the cellfactory but i didnt find a way to do this. I also need to display the name of the group and not the refernce to the clas. Is there a way to do this, or should i change my approach?
The Appointee class
public class Appointee {
private SimpleIntegerProperty id;
private SimpleStringProperty firstname;
private SimpleStringProperty lastname;
private SimpleIntegerProperty group;
private SimpleIntegerProperty assigned;
public Appointee(int id, String firstname, String lastname, int group, int assigned){
this.id = new SimpleIntegerProperty(id);
this.firstname = new SimpleStringProperty(firstname);
this.lastname = new SimpleStringProperty(lastname);
this.group = new SimpleIntegerProperty(group);
this.assigned = new SimpleIntegerProperty(assigned);
}
The Group class
public class Group {
private IntegerProperty id;
private StringProperty name;
private IntegerProperty members;
private IntegerProperty assigned;
public Group(int id, String name, int members, int assigned) {
this.id = new SimpleIntegerProperty(id);
this.name = new SimpleStringProperty(name);
this.members = new SimpleIntegerProperty(members);
this.assigned = new SimpleIntegerProperty(assigned);
}
The appointe table view
public AppointeeTableView() {
// define table view
this.setPrefHeight(800);
this.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
this.setItems(MainController.appointeeObervableList);
this.setEditable(true);
// define columns
...
TableColumn groupCol = new TableColumn("Group"); // group
groupCol.setCellFactory(col -> {
TableCell<Group, StringProperty> c = new TableCell<>();
final ComboBox<String> comboBox = new ComboBox(MainController.groupObservableList);
c.graphicProperty().bind(Bindings.when(c.emptyProperty()).then((Node) null).otherwise(comboBox));
return c;
});
groupCol.setEditable(false);
...
}
Override the updateItem method of the TableCell to update the cell, make sure the new value is saved on a change of the TableCell value and use a cellValueFactory.
final Map<Integer, Group> groupById = ...
final ObservableList<Integer> groupIds = ...
TableColumn<Group, Number> groupCol = new TableColumn<>("Group");
groupCol.setCellValueFactory(cd -> cd.getValue().groupProperty());
class GroupCell extends ListCell<Integer> {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
Group group = groupById.get(item);
if (empty || group == null) {
setText("");
} else {
setText(group.getName());
}
}
}
groupCol.setCellFactory(col -> new TableCell<Group, Integer>() {
private final ComboBox<Integer> comboBox = new ComboBox<>(groupIds);
private final ChangeListener<Integer> listener = (o, oldValue, newValue) -> {
Group group = (Group) getTableView().getItems().get(getIndex());
group.setGroup(newValue);
};
{
comboBox.setCellFactory(lv -> new GroupCell());
comboBox.setButtonCell(new GroupCell());
}
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
comboBox.valueProperty().removeListener(listener);
setGraphic(comboBox);
comboBox.setValue((Integer) item);
comboBox.valueProperty().addListener(listener);
}
}
});
It's a bit hard to tell from only some small code snippets, but my general recommendation when working with frontends is to distinguish between the model and the rendering on each level. This applies to JavaFX, Swing and Angular applications alike.
The appointee TableView should likely be TableView<Appointee>.
For the appointee.group property you have two options: either use Group or (e.g. when this would generate too many duplicate data when de-/serializing from/ to JSON) then use a business key. The first option is usually easier to implement and work with. With the second option you'll need some service / code to convert back to a Group and have to think about where/ at what level exactly you want to do the conversion.
Let's go on here with the second option as you currently have specified appointee.group to be an integer.
In this case the group column should be TableColum<Appointee, Integer>.
The group cell then should be TableCell<Appointee, Integer>.
So far we've only talked about the model, not about rendering except that we want to display the appointees in a table.
I recommend to do this also on the next level.
Don't use a ComboBox<String> for a groups comboBox but a ComboBox<Group>. String is how you want to render the group inside the comboBox but the Group is the model. Also ComboBox<Integer>, the type of the business key, is a bit misleading (as you want a Groups comboBox, not an integer comboBox) and limits the flexibility of your code.
Use the converting service / code I've mentioned when pre-selecting a value in the comboBox.
The group cell should have the type ListCell<Group> and in the updateItem method, which concerns about how to render a Group, you could e.g. use the name property to get the String representation.
Of course there are variations of this approach, but make sure that on each level you know what the model of the control is and what the renderer of the control is. Always design your code using the model and use the rendering types only at the lowest rendering level.

JavaFX: Bind to nested position

I have a Pane that contains some VBoxs and Lines. I want to bind the endpoints of the Lines to the "anchors" inside the VBoxs (basically I want to bind to the position of a Node nested arbitrarily deep in the VBox), but I can't figure out what value represents the Nodes position relative to the top Pane. I've tried layout properties, translate properties, as well as bounds in local and bounds in parent, and none of them seem to work. What am I missing?
(I can provide a code sample if needed, but I don't think it helps explain my problem any better since I can't get it to work.)
EDIT: I forgot to mention the VBox can be moved around the pane freely, which is why I need to bind the lines.
EDIT: Here's some source showing my progress. I can get the correct location, but it's not binding
public class Graph extends Application {
private double startX;
private double startY;
private ObjectBinding<Bounds> bounds;
private DoubleBinding tx;
private DoubleBinding ty;
#Override
public void start(Stage primaryStage) throws Exception {
Pane pane = new Pane();
Circle target = new Circle(5, Color.RED);
VBox node = wrap(target);
Line connector = new Line();
bounds = Bindings.createObjectBinding(() -> {
Bounds nodeLocal = target.getBoundsInLocal();
Bounds nodeScene = target.localToScene(nodeLocal);
Bounds nodePane = pane.sceneToLocal(nodeScene);
return nodePane;
},
target.boundsInLocalProperty(),
target.localToSceneTransformProperty(),
pane.localToSceneTransformProperty()
);
connector.setStartX(0);
connector.setStartY(0);
tx = Bindings.createDoubleBinding(() -> bounds.get().getMinX(), bounds);
ty = Bindings.createDoubleBinding(() -> bounds.get().getMinY(), bounds);
connector.endXProperty().bind(tx);
connector.endYProperty().bind(ty);
connector.setStroke(Color.BLACK);
pane.getChildren().add(node);
pane.getChildren().add(connector);
node.relocate(100, 100);
primaryStage.setScene(new Scene(pane, 300, 300));
primaryStage.show();
}
private VBox wrap(Circle target) {
VBox node = new VBox(new Label("Node"), new StackPane(new Rectangle(50, 50, Color.GRAY), target));
node.setOnMousePressed(event -> {
Node source = (Node) event.getSource();
startX = source.getBoundsInParent().getMinX() - event.getScreenX();
startY = source.getBoundsInParent().getMinY() - event.getScreenY();
});
node.setOnMouseDragged(event -> {
Node source = (Node) event.getSource();
double offsetX = event.getScreenX() + startX;
double offsetY = event.getScreenY() + startY;
source.relocate(offsetX, offsetY);
});
return node;
}
}
Given any Node node in a (meaning it has a parent or indirect ancestor) Pane pane, the basic idea is to do
ObjectBinding<Bounds> boundsInPaneBinding = Bindings.createObjectBinding(() -> {
Bounds nodeLocal = node.getBoundsInLocal();
Bounds nodeScene = node.localToScene(nodeLocal);
Bounds nodePane = pane.sceneToLocal(nodeScene);
return nodePane ;
}, node.boundsInLocalProperty(), node.localToSceneTransformProperty(),
pane.localToSceneTransformProperty());
Then boundsInPaneBinding is an ObservableValue<Bounds> that always contains the bounds of the Node in the Pane's coordinate system. So you can then do things like
line.startXProperty().bind(Bindings.createDoubleBinding(
() -> boundsInPaneBinding.get().getMinX(),
boundsInPaneBinding));
The tricky part here is making sure that the bindings don't get garbage collected prematurely. (See here for a discussion.) First, you need to retain a reference to the binding:
private ObjectBinding<Bounds> boundsInPaneBinding ;
and then (for reasons I can't quite figure out), the binding must actually evaluate the bound property directly:
boundsInPaneBinding = Bindings.createObjectBinding(() -> {
Bounds nodeLocal = node.getBoundsInLocal();
// note how this actually gets the value of localToSceneTransformProperty():
Bounds nodeScene = node.getLocalToSceneTransform().apply(nodeLocal);
Bounds nodePane = pane.sceneToLocal(nodeScene);
return nodePane ;
}, node.boundsInLocalProperty(), node.localToSceneTransformProperty(),
pane.localToSceneTransformProperty());

JavaFX cannot draw line in BarChart

I want a draw a line in a Barchart. the line reflects a certain value to which other values are compared. I applied the solution for a LineChart to my situation (see code). However, no line is drawn.
public XYChart buildBarChart ()
{
Line valueMarker = new Line ();
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
BarChart<String,Number> barChart = new BarChart<String,Number> (xAxis,yAxis);
barChart.setBarGap (-10);
barChart.setCategoryGap (0);
barChart.getYAxis().setTickLabelsVisible(false);
barChart.getYAxis().setOpacity(0);
yAxis.setLabel ("Score");
// update marker
Node chartArea = barChart.lookup (".chart-plot-background");
Bounds chartAreaBounds = chartArea.localToScene (chartArea.getBoundsInLocal ());
// remember scene position of chart area
yShift = chartAreaBounds.getMinY();
// set x parameters of the valueMarker to chart area bounds
valueMarker.setStartX (chartAreaBounds.getMinX());
valueMarker.setEndX (chartAreaBounds.getMaxX());
// find pixel position of that value
double displayPosition = yAxis.getDisplayPosition (valNederland);
// update marker
valueMarker.setStartY (yShift + displayPosition);
valueMarker.setEndY (yShift + displayPosition);
BarChart.Series<String,Number> series1 = new BarChart.Series<String,Number> ();
BarChart.Series<String,Number> series2 = new BarChart.Series<String,Number> ();
BarChart.Series<String,Number> series3 = new BarChart.Series<String,Number> ();
BarChart.Series<String,Number> series4 = new BarChart.Series<String,Number> ();
series1.getData ().add (new XYChart.Data<String,Number> (sRegio, valRegio_2015));
series2.getData ().add (new XYChart.Data<String,Number> (sOmgeving, valOmgeving));
series3.getData ().add (new XYChart.Data<String,Number> (sRest, valRest));
series4.getData ().add (new XYChart.Data<String,Number> (sNl, valNederland));
barChart.getData().addAll (series1, series2, series3, series4);
return barChart;
} /*** buildBarChart ***/
Essential for this situation is to have the coordinates of the ChartArea. When I inspect the properties of ChartArea in the debugger I find that all values of _geomBounds are 'unlogic', i.e. maxX/Y = -1 and minX/Y = -1. I inspect just one line after chartASreaBounds is assigned a value.
To me it seems that the barChart lookup fails as I had a similar problem before. Does anyone have a suggestion of how to correct this situation?
Edit
The BarChart is created in the constructor as follows:
public Charter (int nTimes, int nCategories, float [] years, String [] titles, String label, Indicator ind, float [] past)
{
this ();
// all kind of code to do somethong with the parameters omitted
// ...
// Setting up the BarChart. Note that past == null in my experiments
HBox hBox = new HBox ();
XYChart bar = buildBarChart ();
bar.setPrefHeight (height);
bar.setPrefWidth (width);
VBox buttons = buildButtonView ();
if (past != null)
{
XYChart line = buildLineChart ();
line.setPrefHeight (height);
line.setPrefWidth (width);
hBox.getChildren ().addAll (bar, line);
} else
{
hBox.getChildren ().addAll (bar);
} // if
Label dsecription = new Label (label);
this.getProperties ().put ("--Indicator", ind.getName ());
this.getChildren ().addAll (dsecription, hBox);
} /*** Charter ***/
After a Charter has been created it is added to the VBox and therewith shows on the screen as VBox is already added to the scene.
It looks like you want to calculate the position of the bar chart on the screen before it is added to the scene. Since at that point it is not yet on the screen that position is -1 or not defined.
I tend to make my layouts in fxml, therefore such problems don't appear. If you need to do it in code, I think first adding it to the children of hBox before drawing the line could solve the issue.

Set the value of a property when other property changes

I have a class like this:
class SomeObject{
public SimpleDoubleProperty Vre = new SimpleDoubleProperty(0);
public SimpleDoubleProperty Vim = new SimpleDoubleProperty(0);
public SimpleDoubleProperty Vabs = new SimpleDoubleProperty(0);
SomeObject(){
Label results_label = new Label();
results_label.textProperty().bind(Vabs.asString());
}
}
I want that whenever I change the properties Vre or Vim, the value of Vabs is updated to the module of Vre+j*Vim which would make the label results_label display the module of the complex number.
PS: For those thinking about using a complex number right away, I don' t want to do that.
Thanks.
Just create the required binding:
vAbs.bind(Bindings.createDoubleBinding(
() -> Math.sqrt(vRe.get() * vRe.get() + vIm.get() * vIm.get()),
vRe, vIm);

Resources