JavaFX TableView commit change to object field without setOnEditCommit() - javafx

Can TableView commit changes to generic fields like String or Integer without extra step with setOnEditCommit?
Currently I use such structure:
#FXML
TableColumn<User, String> _tableStateUsername;
#Override
public void initialize(URL location, ResourceBundle resources) {
_tableStateUsername.setCellFactory(TextFieldTableCell.<User, String>forTableColumn(new DefaultStringConverter()));
_tableStateUsername.setCellValueFactory(new PropertyValueFactory<User, String>("username"));
_tableStateUsername.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<User, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<User, String> event) {
event.getRowValue().setUsername(event.getNewValue());
}
});
and it works fine, but I feel that it can be simplified (without using SimpleString)
I tried binding Value factory to field but no luck:
_tableStateUsername.setCellValueFactory(cellData -> Bindings.select(cellData.getValue(), "username"));
Is there any way to make cell set NewValue to bound field automatically?

Related

Can TableColumns contain different classes in a TableView?

I am trying to find a way to populate my tablePrice column with prices of specific menu items. The code shown below works fine but I had to create a price variable in the MenuItem class which was not previously there. MenuItem and PricedMenuItem, as well as other classes are generated from a UML domain model for a restaurant management system. This current method is discouraged as I am modifying the domain model.
The commented section shows how far I got with an error on the setCellValueFactory(). Is there a way for a TableView to contain columns of different classes? If so, can someone please assist me in filling in the column directly from the PricedMenuItem class?
MenuItem has a private name and itemCategory enum, as well as a getCurrentPricedMenuItem() method.
PricedMenuItem has a private price as well as a getPrice() method.
#FXML private TableView<MenuItem> tableView;
#FXML private TableColumn<MenuItem, String> tableName;
#FXML private TableColumn<MenuItem, Double> tablePrice;
#FXML private TableColumn<MenuItem, ItemCategory> tableCategory;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableName.setCellValueFactory(new PropertyValueFactory<MenuItem, String>("Name"));
tableCategory.setCellValueFactory(new PropertyValueFactory<MenuItem, ItemCategory>("itemCategory"));
tablePrice.setCellValueFactory(new PropertyValueFactory<MenuItem, Double>("price"));
/*way to retrieve price directly from PMI
tablePrice.setCellValueFactory(new Callback<CellDataFeatures<MenuItem, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call( CellDataFeatures<MenuItem, String> c) {
return new SimpleStringProperty(c.getValue().getValue().getCurrentPricedMenuItem().getPrice()+"");
}
});
*/
categoryDropDown1.getItems().setAll(ItemCategory.values());
categoryDropDown2.getItems().setAll(ItemCategory.values());
tableView.setItems(loadCurrentMenuItems());
updateBox("Select a menu item to edit.", Color.BLACK);
}
Declare tablePrice as
#FXML private TableColumn<MenuItem, Number> tablePrice;
(see JavaFX Properties in TableView for why).
Then the cell value factory should return a Property<Number> (yours is returning a Property<String>), so you need
tablePrice.setCellValueFactory(new Callback<CellDataFeatures<MenuItem, Number>, ObservableValue<Number>>() {
#Override
public ObservableValue<Number> call( CellDataFeatures<MenuItem, Number> c) {
return new SimpleDoubleProperty(c.getValue().getCurrentPricedMenuItem().getPrice());
}
});
or, using lambda expressions to get rid of most of the boilerplate code
tablePrice.setCellValueFactory(cellData ->
new SimpleDoubleProperty(cellData.getValue().getCurrentPricedMenuItem().getPrice()));
(I'm pretty sure you should not have two getValue() calls in there... but you didn't post your model classes.)

Tableview item dependencies javafx

Good evening,
I am working on a project and I'm at lost at how to configure my tableviews to depend on each other. I would like for the parts in one tableview to depend on the products in the other. How would I go about doing that? I prefer to not use sql at the moment just to keep everything simple as possible. The code snippet is listed below:
public class maincontroller {
private ObservableList<Part> parts = FXCollections.observableArrayList();
private ObservableList<Product> products = FXCollections.observableArrayList();
//code to swap between controllers
#Override
public void initialize(URL url, ResourceBundle rb) {
partsID.setCellValueFactory(new PropertyValueFactory<>("partID"));
partsName.setCellValueFactory(new PropertyValueFactory<>("name"));
partsinvlevel.setCellValueFactory(new PropertyValueFactory<>("instock"));
partscost.setCellValueFactory(new PropertyValueFactory<>("price"));
parttable.setItems(parts);
productsID.setCellValueFactory(new PropertyValueFactory<>("productID"));
productsName.setCellValueFactory(new PropertyValueFactory<>("name"));
productsInvlevel.setCellValueFactory(new PropertyValueFactory<>("instock"));
productsprice.setCellValueFactory(new PropertyValueFactory<>("price"));
producttable.setItems(products);
parttable.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Part> observable, Part oldValue, Part newValue) -> {
});
producttable.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Product> observable, Product oldValue, Product newValue) -> {
});
}
}
I'm not too sure about the relationship between Part and Product. From the context, I would assume each specific Product would have its own list of Parts that is related to it. I would also assume that that list of Parts is stored within Product.
Product:
public class Product
{
private final ObservableList<Part> parts = FXCollections.observableArrayList();
public final ObservableList<Part> getParts() { return parts; }
........
}
This is what you can do:
producttable.getSelectionModel().selectedItemProperty().addListener(observable, oldProduct, newProduct) ->
{
if (newProduct != null)
{
parts.clear();
parts.addAll(newProduct.getParts());
}
});

Creating a simple slider in javaFX

What i am trying to achieve is to create a method next to the initialize method, that can handle the current value of a slidebar. I want that method to pass back the value into a Timeline. The problem i am facing is that the method that handles the slider and when its changing is a void method, but i need that value in double type.
I am having hard time understanding the concept of addlistener and what the difference is between Observable, ObservableValue, InvalidationListener, and ChangeListener.
/*DATA FIELD*/
private double cycle;
private Timeline loop;
#FXML
private Slider slider1;
/*METHODS*/
#FXML
public double frequency(){
slider1.valueProperty().addListener(new InvalidationListener(){
#Override
public void invalidated(Observable observable){
cycle = slider1.setValue(slider1.getValue());
}});
return cycle;
public void initialize(URL url, ResourceBundle rb) {
slider1 = new Slider(100, 3000, 500);
loop = new Timeline(new KeyFrame(Duration.millis(cycle), (ActionEvent event) -> {
gb.nextGen(gb.getBoard());
graphic.draw(gb.getBoard());
}
A good way to do this is using property bindings instead. You could bind the value of your slider, to update any other field. An example to bind your slider to a label:
#FXML
private Label label;
#FXML
private Slider slider;
#Override
public void initialize(URL url, ResourceBundle rb) {
label.textProperty().bindBidirectional(slider.valueProperty(), new NumberStringConverter());
}
And in your concrete case, you could bind it to a double property. You could do something as the following:
private final DoubleProperty doubleProperty = new SimpleDoubleProperty();
#FXML
private Slider slider;
#Override
public void initialize(URL url, ResourceBundle rb) {
doubleProperty.bind(slider.valueProperty());
//When you're in need to pass the primitive forward, get it from the property
doubleProperty.get();
}
The binding basically does the following: change the slider, change whatever it's bound to. When using bidirectional binding, it goes both ways. E.g. bind a slider to a textfield, both update each other.

JavaFX - Update a text field through observer pattern

I am trying to update a text field through observer pattern. The update function in the observer (FXML controller) is called after clicking on a listItem in another controller class. And that works fine. The only problem is that my textfield won't update.
Here is my update function in the observer.
#Override
public void update(Observable o, final Object arg) {
System.out.println("test"); // works
firstNameTextField.setText("test"); // doesn't work (text field is still empty)
System.out.println(firstNameTextField.getText()); //works and shows me the word "test" on my console
}
The funny thing is, if I print the text from the text field on my console it's printing the word "test" on the console. It seems like the text field value is updated but it doesn't show up on the ui.
EDIT:
This is my MainController
public class MainController extends Observable implements Initializable {
private ObservableList<String> items = FXCollections.observableArrayList("item1", "item2");
private List<UserProfile> userProfiles = new ArrayList<UserProfile>();
private String[] tabTitles = { "Profile"};
#FXML
private TabPane tabPane;
#FXML
ListView<String> listView;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
for (String tabTitle : tabTitles) {
Tab tab = new Tab(tabTitle);
tab.setClosable(false);
tabPane.getTabs().add(tab);
}
tabPane.getSelectionModel().clearSelection();
for (Tab tab : tabPane.getTabs()) {
try {
String newStringValue = tab.getText();
Parent root = FXMLLoader.load(getClass().getResource("profile.fxml"));
tab.setContent(root);
FXMLLoader fxmlLoader = new FXMLLoader();
Object p = fxmlLoader.load(getClass().getResource("profile.fxml").openStream());
if (fxmlLoader.getController() instanceof ProfileController) {
ProfileController profileController = (ProfileController) fxmlLoader.getController();
this.addObserver(profileController);
}
} catch (IOException e) {
e.printStackTrace();
}
}
tabPane.getSelectionModel().selectFirst();
listView.setItems(items);
listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
setChanged();
notifyObservers();
}
});
}
}
ProfileController
public class ProfileController implements Initializable, Observer {
#FXML
TextField firstNameTextField;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
}
#Override
public void update(Observable o, final Object arg) {
System.out.println("test"); // works
firstNameTextField.setText("test"); // doesn't work (text field is still empty)
System.out.println(firstNameTextField.getText()); //works and shows me the word "test" on my console
}
}
Can anybody help me out with this?
Thanks!
When you execute
Object p = fxmlLoader.load(getClass().getResource("profile.fxml").openStream());
if (fxmlLoader.getController() instanceof ProfileController) {
ProfileController profileController = (ProfileController) fxmlLoader.getController();
this.addObserver(profileController);
}
you load the structure represented by profile.fxml, and place that hierarchy (including firstNameTextField) in the object you called p. When you invoke update(...) on profileController, it changes the text in the text field that is part of the hierarchy of p. However, you never do anything with p: you don't display it in your UI. So when you change the text of the text field, the changes are of course invisible (because you are changing a text field that isn't displayed).
Presumably, since you said you have the text field displayed, somewhere in the code you couldn't be bothered to include you are loading profile.fxml and displaying the content in the UI. You need to get the reference to that controller, and register it as an observer. Registering an arbitrary instance of the same class will not have the desired effect.

JavaFX Change label text from another class with controller

I want to change the text of a Label with the controller from another class. I have made a method in the FXMLDocumentController, which sets the text to the label:
public void setLabelText(String text)
{
lbZeit.setText(text);
}
Now I want to change this text from another class like my SerialHandlerClass. First, I need the controller, am I right? So I did this:
FXMLLoader loader = new FXMLLoader(FXMLDocumentController.class.getResource("FXMLDocument.fxml"));
loader.load();
controller = (FXMLDocumentController) loader.getController();
Now I run the "setLabelText" method....
controller.setLabelText("asd");
... and nothing happens...
It's very funny, because when I add System.out.println(text); to the "setLabelText(String text)" method, the program writes the correct text to the console.
But, why?
Sorry for my bad english, it's not my native language :)
Thanks,
Julian
You are not updating the label because you are creating another instance of FXMLDocumentController when you use the FXMLoader.
You should set the controller instance, that contains the label, as a parameter to the other Class.
Below you have the code that could solve your need. Here I set the Controller instance to the Connector class, so you can call the setLabelText method from the other class:
public class Connector {
public static void Connecting(FXMLDocumentController controller) {
try {
System.out.println("Connector.Connecting(): Called");
controller.setLabelText("Bye World");
} catch (IOException ex) {
Logger.getLogger(Connector.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Connector.Connecting(this);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setLabelText(String text)
{
System.out.println("FXMLDocumentController.setLabelText(): Called");
label.setText(text);
}
}
Note:
If your routine is going to take longer to execute whatever it needs to, you might want to use a Task, so you don't freeze your UI. To update the Label, you have to bind the text property and then update the Text value using the updateMessage() method.
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Task<Boolean> connectorTask = new ConnectorTask();
label.textProperty().bind(connectorTask.messageProperty());
connectorTask.setOnSucceeded(e -> {
// this is going to be called if the task ends up without error
label.textProperty().unbind();
});
new Thread(connectorTask).start();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
//public void setLabelText(String text)
//{
// System.out.println("FXMLDocumentController.setLabelText(): Called");
// label.setText(text);
//}
public class ConnectorTask extends Task<Boolean> {
#Override
protected Boolean call() throws Exception {
// ... do whatever you need here
// then you call this method to update the TextProperty from the Label that was bound.
updateMessage("Bye World");
return Boolean.TRUE;
}
}
}
NOTE:
There is a possible duplicate question for this, please see my answer for this question here!

Resources