So I have created Instrument class and Controller class. I have big problem with bindingBidirectional() method. It gives me an error when i'm trying to bind Combobox property with AmountProperty in Instrument class.
amount.valueProperty().bindBidirectional(instrument.amountProperty());
What am I doing wrong here?
Controller class
public class Controller implements Initializable{
#FXML
private ComboBox<Integer> amount = new ComboBox<>();
ObservableList<Integer> amountOptions = FXCollections.observableArrayList(0, 5, 10, 25, 50);
Instrument instrument = new Instrument();
#Override
public void initialize(URL location, ResourceBundle resources) {
amount.getItems().addAll(amountOptions);
//THIS ONE IS NOT WORKING
amount.valueProperty().bindBidirectional(instrument.amountProperty());
}}
And Instrument class:
public class Instrument {
private IntegerProperty amount = new SimpleIntegerProperty();
public int getAmount() {
return amount.get();
}
public IntegerProperty amountProperty() {
return amount;
}
public void setAmount(int amount) {
this.amount.set(amount);
}
}
IntegerProperty is an implementation of Property<Number>, not of Property<Integer>. The valueProperty in your combo box is a Property<Integer>. Consequently you cannot bind bidirectionally between the two directly, as the types don't match.
You can either change your combo box to be a ComboBox<Number>, or use IntegerProperty.asObject(), which creates an ObjectProperty<Integer> that is bidirectionally bound to the IntegerProperty:
amount.valueProperty().bindBidirectional(
instrument.amountProperty().asObject());
Related
I have an inventory class with an ObservableArray of type Product, each product containing an arraylist of parts. On the main controller I pre-populate with some data, and system output shows that the values are all there and the array of parts are present. The problem I am running in to is when I click a button to open a new controller and populate the product values into fields, all the product fields except arraylist have data, it's as if the arraylist is being wiped in that copy.
Product Class
private ArrayList<Part> associatedParts = new ArrayList<>();
private int productID;
private String name;
private double price;
private int inStock;
private int min;
private int max;
public void addAssociatedPart(Part part) {
associatedParts.add(part);
}
public ArrayList<Part> getAllAssociatedParts() {
return associatedParts;
}
Part Class
private int partID;
private String name;
private double price;
private int inStock;
private int min;
private int max;
Inventory Class
private static ObservableList<Product> products = FXCollections.observableArrayList();
public ObservableList<Product> getAllProducts() {
return products;
}
public void addPart(Part part) {
allParts.add(part);
}
public void addProduct(Product prod) {
products.add(prod);
}
public Part lookupPart(int partid) {
if (partid > allParts.size() || partid < 1) {
return null;
}
else {
return allParts.get(partid-1);
}
}
MainController
#Override
public void initialize(URL url, ResourceBundle rb) {
if (firstLoad) {
inventory.addPart(new Part(1,"Widget",1.13,5,1,8));
inventory.addPart(new Part(2,"Sprocket",2.88,5,1,8));
inventory.addPart(new Part(3,"Gear",3.46,5,1,8));
inventory.addProduct(new Product(1,"Dohicky",13.34,3,1,5));
inventory.lookupProduct(1).addAssociatedPart(inventory.lookupPart(1));
inventory.lookupProduct(1).addAssociatedPart(inventory.lookupPart(2));
inventory.addProduct(new Product(2,"Thingamajig",24.12,3,1,5));
inventory.lookupProduct(1).addAssociatedPart(inventory.lookupPart(2));
inventory.lookupProduct(1).addAssociatedPart(inventory.lookupPart(3));
}
tblProducts.setItems(inventory.getAllProducts());
}
#FXML
private void handleAddMod(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
Stage stage = new Stage();
Parent root = null;
if (event.getSource() == btnProdMod && tblProducts.getSelectionModel().getSelectedItem() != null) {
Product prod = tblProducts.getSelectionModel().getSelectedItem();
loader.setLocation(getClass().getResource("ModifyProduct.fxml"));
root = loader.load();
loader.<ModifyProductController>getController().displayProd(prod);
}
if (root != null) {
Scene scene = new Scene(root);
stage.setScene(scene);
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
}
}
At this point, System.out.println() on any given product will return valid information, and even on the product getallassociatedparts size shows that it is populated.
ModifyProductController
public void displayProd (Product prod) {
System.out.println(prod.getName());
System.out.println(prod.getAllAssociatedParts().size());
}
At this point, the product name (and any other fields within it) will show, but there is nothing in the associatedParts list.
For instance, if I passed part 1 as seen above, the system output shows me Widget, followed by a 0.
The solution is for me to chill out and take a break from the code, #fabian pointed out it worked fine when he tried it, so when looking back over the code I noticed that I had accidentally assigned all parts to product 1, and like an idiot I had only tested on product 2 (last on list so closer to the button? not sure lol)
After updating the main controller parts to lookupProduct(2) instead of 1, it worked just fine:
inventory.lookupProduct(2).addAssociatedPart(inventory.lookupPart(2));
inventory.lookupProduct(2).addAssociatedPart(inventory.lookupPart(3));
I wan't to populate a ChoiceBox from a List<Object>. My Object has a name field which i wan't to use that as the choice text.
Of course i need to know which object the user has selected in order to pass the correct data.
FXML Controller:
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
UniversitiesService uniService = new UniversitiesServiceImpl();
List<University> uniList = uniService.getUniversitiesList();
//uniChoiceBox.setItems(); Need some guidance here
}
University Entity:
private String universityName;
private String universityURL;
private String[] universityDataNames;
//getters setters
Just do
uniChoiceBox.getItems().setAll(uniList);
If you need to configure the display (i.e. if the toString() method in University doesn't give the text you need), add a converter:
uniChoiceBox.setConverter(new StringConverter<University>() {
#Override
public String toString(University uni) {
return uni.getUniversityName();
}
#Override
// not used...
public University fromString(String s) {
return null ;
}
});
The general question: Is there any way to update a label when the value of a simple integer changes ?
I'm talking about simple int's and not stuff like ReadOnlyIntegerWrappers. I've tried the following according to Converting Integer to ObservableValue<Integer> in javafx
(I had to change the identifier (is that what it's called ?) of ObservableValue from Integer to String because I couldn't find a way to bind it to the TextProperty otherwise)
I've included my demo code below which somehow seems to result in a NullPointerException at label.textProperty().bind(m.getObsValue());. The application is written in a MVC-pattern.
Model:
public class Model {
private int value;
private ObservableValue<String> obsInt;
public Model(){
value = 5;
obsInt = new ReadOnlyObjectWrapper<>(value + "");
}
public int getValue(){
return value;
}
public void setValue(int value){
this.value = value;
}
public ObservableValue<String> getObsValue(){
return obsInt;
}
}
Controller:
public class Controller {
private Model m;
private View v;
public Controller(Model m, View v){
this.m = m;
this.v = v;
}
public void handleMouseclick(MouseEvent e){
m.setValue(m.getValue() + 5);
}
public void init(){
v.setOnMouseClicked(this::handleMouseclick);
}
}
View:
public class View extends Region{
private Model m;
private Label label;
public View(Model m)
{
this.m = m;
label.textProperty().bind(m.getObsValue());
label.setLayoutX(200);
label.setLayoutY(200);
paint();
}
public void paint(){
getChildren().clear();
getChildren().addAll(label);
}
#Override
public double computePrefHeight(double width){
return 800;
}
#Override
public double computePrefWidth(double height){
return 600;
}
}
As you might've noticed I'm currently still studying JavaFX. So I probably just missed something stupid. Any advice would be greatly appreciated !
Let's start from the end - the exception is because you never initialize label, so it is null - as simple as that. Using label = new Label(); should solve it.
And now for the bindings - you say you don't want to use IntegerProperty or ReadOnlyIntegerWrapper, but rather use a simple int - that means you have no convenient way of knowing when the value is changed! The label will always contain the initial value of your integer, so you may as well do something like:
label.setText(Integer.toString(m.getValue()));
Instead, I would advise you to do something like
public class Model {
private SimpleIntegerProperty value = new SimpleIntegerProperty(this, "value");
public Model() {
value.set(5);
}
public int getValue(){
return value.get();
}
public void setValue(int value){
this.value.set(value);
}
public IntegerProperty valueProperty(){
return value;
}
}
then you can bind the label's text property using Bindings.convert:
label.textProperty().bind(Bindings.convert(m.valueProperty()));
this way, whenever the model's value is changed, the label text would automatically reflect this.
As you can see, SimpleIntegerProperty is nothing to be afraid of! The arguments in the constructor are optional, but recommended - they are the object this property belongs to (this), and the name of the property ("value", in this case). You can also pass the initial value in the constructor, instead of explicitly setting it in your Model constructor.
I am newbie in building javafx MVVM app.
I've created a simple ViewModel:
public class PersonViewModel {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty age = new SimpleIntegerProperty();
public PersonViewModel() {}
// getters and setters
}
and simple View:
public class PersonView implements Initializable {
#FXML
TextField name;
#FXML
TextField age;
#FXML
Button ok;
#Override
public void initialize(URL location, ResourceBundle resources) {
PersonViewModel viewModel = new PersonViewModel();
name.textProperty().bindBidirectional(viewModel.name);
age.textProperty().bindBidirectional(viewModel.age);
}
}
Can you give me any idea how to make age validation? F.e. I wanna not to allow user to put characters into age (TextField) except [a-zA-Z]. And the main idea of my question to make this validation in ViewModel) Help me pls.
P.S. I wanna make it not using not standard javafx components.
You can use a TextFormatter both to filter input in a text input control, and to convert the text into a value of a specific type. If you want the view model to define the validation rules, then define a method in there representing the validation, and delegate to that method in the filter definition for the TextFormatter. For example:
public class PersonViewModel {
private final StringProperty name = new SimpleStringProperty();
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
nameProperty.set(name);
}
private final IntegerProperty age = new SimpleIntegerProperty();
public IntegerProperty ageProperty() {
return age ;
}
public final int getAge() {
return ageProperty().get();
}
public final void setAge(int age) {
ageProperty.set(age);
}
public boolean validAgeInput(String input) {
// must support partial entry while editing, including empty string
// accept any integer from 0 - 135 (arbitrary upper bound example)
String regex = "([0-9]{0,2})|(1[0-2][0-9])|(13[0-5])";
return input.matches(regex);
}
}
Now you can do:
public class PersonView implements Initializable {
#FXML
TextField name;
#FXML
TextField age;
#FXML
Button ok;
#Override
public void initialize(URL location, ResourceBundle resources) {
PersonViewModel viewModel = new PersonViewModel();
UnaryOperator<Change> filter = change -> {
if (viewModel.validAgeInput(change.getControlNewText()) {
// accept
return change ;
} else {
// reject
return null ;
}
};
TextFormatter<Integer> ageFormatter = new TextFormatter<>(new IntegerStringConverter(), 0, filter);
age.setTextFormatter(ageFormatter);
ageFormatter.valueProperty().bindBidirectional(viewModel.ageProperty().asObject());
name.textProperty().bindBidirectional(viewModel.nameProperty());
}
}
The filter defined here will only accept input in the control if it matches the rule defined by the method in the PersonViewModel. The valueProperty() of the TextFormatter represents the text in the TextField after passing it to the IntegerStringConverter: this is bound bidirectionally to the ageProperty() in the model. (The call to asObject() effectively just converts between an IntegerProperty and an ObjectProperty<Integer>.)
So in class we always use the following syntax. Correct me if i am wrong but this is a bean because it class use getters/setters. It has an nullary constructor and the class implements serializable.
// option 1
private int customerID ;
public CustomerDTO ()
{
this(0);
}
public CustomerDTO(int customerID) {
setCustomerID(customerID);
}
public void setCustomerID(int customerID) {
this.customerID = customerID;
}
public int getCustomerID() {
return customerID;
}
But today i came across something similar like this. i needed to import
import javafx.beans.property.SimpleStringProperty;
But what is the main difference between option 1 and 2.
When should i use option 1 or option 2
And which one is better or does it depends on the situation.
// option 2
private final IntegerProperty customerID;
public CustomerDTO ()
{
this(null);
}
public CustomerDTO(IntegerProperty customerID) {
this.customerID = new SimpleIntegerProperty();
}
public IntegerProperty getCustomerID() {
return customerID;
}
public void setCustomerID(int customerID) {
this.customerID.set(customerID);
}
Option 2 is used when you are building JavaFX application and want to bind your model with gui.
Example:
public class Foo {
private final StringProperty foo = new SimpleStringProperty();
public String getFoo() {
return foo.get();
}
public StringProperty fooProperty() {
return foo;
}
public void setFoo(String foo) {
this.foo.set(foo);
}
}
public class FooController {
#FXML
private TextField fooTextField;
private final Foo foo = new Foo();
#FXML
public void initialize() {
foo.fooProperty().bindBidirectional(fooTextField.textProperty());
}
}
public CustomerDTO(IntegerProperty customerID) { makes no sense, a property is a final class member which encapsulates a value, this value can be set via setters and get via setters, in JavaFX controller classes it is advisable to also implement a getter for the ReadOnlyObjectProperty or ReadOnlyIntegerProperty in your case, this can be done via ReadOnlyIntegerWrapper and its getReadOnlyProperty method. This enables the developor to bind to values from other classes whilst also ensuring that the value exists at any time, JavaFX Bindings are a pretty elegant and object-oriented method of data-encapsulation.
Your "option 2" actually is flawed since it allows property-redefinition which breaks this concept and makes the property itself useless. It will also break GUI functionality except if the property itself can not be redefined, see the accepted answer