I have a JavaFX user interface with several controls; the values should be stored inside fields of a Model class; the UI class has a reference to Model.
Say the Model class is the basic:
public static class Model{String myText; /*javabeans getters and setters provided too*/}
The JavaFX Application is the following.
public class T08 extends Application {
Model model;
#Override
public void start(Stage primaryStage) throws Exception {
model = new Model();
BorderPane bp = new BorderPane();
primaryStage.setScene(new Scene(bp));
//this is the component that should be connected to model.myText
TextField textField = new TextField();
bp.setCenter(textField);
primaryStage.show();
}
Question
The user can write text in textField control and the text should be saved into model.myText.
During application startup i need to load the data into the Model and have it rendered to the controls.
I've tried with JavaFX 2.x bindings, but they seem to focus on unidirectional connections.
What are my options to accomplish this in a neat way?
One way is to use myTextProperty.bindBidirectional() instead of myTextProperty.bind(), AFAIK.
Ok I wrote some SSCCE sample code, since code explains the things more precisely :)
First example is for this question:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class JavaFXApplication10 extends Application {
private Model model = new Model();
#Override
public void start(Stage primaryStage) {
final TextField textField = new TextField();
textField.setText(model.getMyText());
Button btn = new Button();
btn.setText("Done");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
model.setMyText(textField.getText());
System.out.println("Done.");
System.out.println("New value: " + model.getMyText());
}
});
BorderPane root = new BorderPane();
root.setTop(textField);
root.setBottom(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
// Data Model
public static class Model {
private String myText = "model myText default";
public String getMyText() {
return myText;
}
public void setMyText(String myText) {
this.myText = myText;
}
}
}
The second modified version (uses bidirectional binding) of this example is here.
In both examples try to click the button.
Maybe you take a look at https://github.com/laubfall/modelfx. I wrote this library exactly for this reason. It supports bidirectional Bindings for JavaFX-Components (that are composed in a hierarchy) and Properties that resides in a Bean. For further Documentation refer to the Wiki-Pages of the Github-Project modelfx. Hope you find it helpful!
ps: the core functionality uses Bindings.bindBidrectional of JavaFX.
Related
When you right click on a TextField there are Undo, Redo, Cut, Copy, Paste, Delete, and Select All options.
I want to add a "Register" MenuItem to that list from my controller class, but do not know how.
Here is what I got so far:
This overwrites the existing menu items:
ContextMenu contextMenu = new ContextMenu();
MenuItem register = new MenuItem("Register");
contextMenu.getItems().add(register);
charName.setContextMenu(contextMenu);
Both of these return null:
charName.getContextMenu()
charName.contextMenuProperty().getValue()
You can replace the in-built TextField ContextMenu by setting your own (as below):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class GiveMeContext extends Application {
#Override
public void start(final Stage stage) throws Exception {
ContextMenu contextMenu = new ContextMenu();
MenuItem register = new MenuItem("Register");
contextMenu.getItems().add(register);
TextField textField = new TextField();
textField.setContextMenu(contextMenu);
stage.setScene(new Scene(textField));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Adding to the in-built ContextMenu is a bit tricker and requires overriding non-public API.
You cannot get the in-built ContextMenu using the public textField.getContextMenu property as it is not returned (that method only returns a menu that has been set by the application code, not the internal JavaFX control skin implementation).
Be aware that the following code will almost certainly break in Java 9 as it uses deprecated com.sun APIs which will likely no longer be available. For further details on this, refer to JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
import com.sun.javafx.scene.control.skin.TextFieldSkin;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class GiveMeContext extends Application {
#Override
public void start(final Stage stage) throws Exception {
TextField textField = new TextField();
TextFieldSkin customContextSkin = new TextFieldSkin(textField) {
#Override
public void populateContextMenu(ContextMenu contextMenu) {
super.populateContextMenu(contextMenu);
contextMenu.getItems().add(0, new SeparatorMenuItem());
contextMenu.getItems().add(0, new MenuItem("Register"));
}
};
textField.setSkin(customContextSkin);
stage.setScene(new Scene(textField));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
so I'm writing a javafx app and I need to be able to select the cells from the list view (for copy paste purposes) but I don't want to make it editable, I mean, the content cannot be changed unless I want to (allowing it through a button, for example).
So I have the following code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
StackPane pane = new StackPane();
pane.getChildren().add(contactsList);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show(); }
public static void main(String[] args) {
launch(args);
}
}
and if I set 'contactsList' as not editable, I'm not able to edit, neither select.
As you can see (image bellow),I'm editing the cell, but I want to be able to select the text(not the item), but I don't want to be able to delete characters (text selectable but not editable).
so after breaking my head off, lots of research and API reading, I came up with a solution. This does EXACTLY what I wanted to do. Here is the demo if someone needs it ;)
So the idea is, each time we want to select the content of a row we need to select the row, get the textField and set the editing to true or false, (every time).
So in the demo that I made, I placed a button so you can toggle the editing to true or false to be sure that's is working, and how is working.
Cheers.
I commented some of the code for better understanding, if you have any questions about this just let me know.
package sample;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
private boolean editable = false;
public static IndexedCell getCell(final Control control, final int index) {
return getVirtualFlow(control).getCell(index);
}
public static VirtualFlow<?> getVirtualFlow(Control control) {
Group group = new Group();
Scene scene = new Scene(group);
Stage stage = new Stage();
if(control.getScene() == null) {
group.getChildren().setAll(control);
stage.setScene(scene);
stage.show();
}
VirtualFlow<?>flow = (VirtualFlow<?>) control.lookup("#virtual-flow");
return flow;
}
public void setEditable(ListView contactsList){
//this needs to be done since we need to run our code after the text field was rendered
//so we need to invoke our code after this happens, if not it will throw a null pointer...
Platform.runLater(() -> {
//this is one of the most important guys because javafx api says that
//TextFieldListCell.forListView() allows editing of the cell content when the cell is double-clicked,
// or when {#link ListView#edit(int)} is called.
int rowIndex = contactsList.getSelectionModel().getSelectedIndex();
contactsList.edit(rowIndex);
ListCell rootCell = (ListCell) getCell(contactsList, rowIndex);
TextField textField = (TextField) rootCell.getGraphic();
textField.setEditable(editable);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
contactsList.setEditable(true);
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
contactsList.setOnEditStart(e -> {
setEditable(contactsList);
});
StackPane pane = new StackPane();
Button editBtn = new Button("Toggle edit");
editBtn.setOnAction(event -> {
editable = !editable;
editBtn.setText("Editing = " + editable);
//to cancel any editing that might be occuring
contactsList.getSelectionModel().clearSelection();
});
pane.getChildren().addAll(contactsList,editBtn);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If I understand you correctly, it is not necessary to set the listview to 'not editable', as the default behaviour should suffice for your purpose. Take a look at this code, for example:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ListView listView = new ListView();
listView.getItems().addAll("one","two","three","four");
listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(listView.getSelectionModel().getSelectedItem());
}
});
StackPane root = new StackPane();
root.getChildren().add(listView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("ListView Example");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I changed nothing about the editable-property of the ListView, but I can select every item, without being able to edit it (in the sense of changing its value). You can easily add an EventHandler to the ListView to perform whatever operation you want to perform. You could also add an EventHandler to every cell of the ListView by manipulating the CellFactory, as shown in this answer: How to handle ListView item clicked action?
Here's what works for me:
TableView<DataBean> table = new TableView<>();
table.setItems(...); // list of some DataBean objects with dataBeanField proprty
table.setEditable(true);
TableColumn<DataBean, String> column = new TableColumn<>("SomeData");
column.setCellValueFactory(new PropertyValueFactory<DataBean, String>("dataBeanField"));
column.setCellFactory(new Callback<TableColumn<DataBean, String>, TableCell<DataBean, String>>() {
#Override
public TableCell<DataBean, String> call(TableColumn<DataBean, String> param) {
return new TextFieldTableCell<>(new DefaultStringConverter() {
private String defaultValue = "";
#Override
public String fromString(String newValue) {
return super.fromString(defaultValue);
}
#Override
public String toString(String value) {
return defaultValue = super.toString(value);
}
});
}
});
My stage contains a ToggleSwitch and two StackPanes which we'll call A and B. Both StackPanes are located in the same space within the parent StackPane. This means that if both A and B are visible and set to managed, they each take up half the allocated space like this:
I'm trying to hide StackPane B upon initalization so that StackPane A takes up the full space... and then when I click the toggle button, it should hide StackPane A and show StackPane B, making B take up the full space.
The initial hiding of StackPane B works fine, but I'm having trouble writing a change listener for the ToggleSwitch in my controller class. Here is my code, and where I'm having trouble:
application class:
public class showPanes extends Application {
Stage stage = new Stage();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
StackPane root = (StackPane) FXMLLoader.load(Drag.class.getResource("twoPanes.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Pane Switcher");
scene.getStylesheets().add("styleMain.css");
stage.setScene(scene);
stage.show();
}
}
The answer found here uses Toggle Groups, and James' answer here uses Buttons. I can't find a solution for ToggleSwitch. I tried to adapt the first answer for use with ToggleSwitch but it's producing an error like:
and says
Cannot resolve method 'addListener(anonymous
javafx.beans.value.ChangeListener)'
How do I fix the listener?
controller class:
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import java.net.URL;
import java.util.ResourceBundle;
import org.controlsfx.control.ToggleSwitch;
public class compsController implements Initializable {
#FXML
private StackPane paneA, paneB;
#FXML
private ToggleSwitch toggleSwitch;
#Override
public void initialize(URL location, ResourceBundle resources) {
paneB.setManaged(false);
paneB.setVisible(false);
toggleSwitch.selectedProperty().addListener(new ChangeListener < ToggleSwitch > () {
#Override
public void changed(ObservableValue < ? extends ToggleSwitch > ov, ToggleSwitch t, ToggleSwitch t1) {
paneA.setManaged(false);
paneA.setVisible(false);
paneB.setManaged(true);
paneB.setVisible(true);
}
});
}
}
You could also use the Binding API of JavaFx like below,
#Override
public void initialize(URL location, ResourceBundle resources) {
paneA.managedProperty().bind(toggleSwitch.selectedProperty());
paneA.visibleProperty().bind(toggleSwitch.selectedProperty());
paneB.managedProperty().bind(toggleSwitch.selectedProperty().not());
paneB.visibleProperty().bind(toggleSwitch.selectedProperty().not());
}
}
I first noticed this by having a huge memory leak caused by resizing javafx.scene.control.Pagination.
I created a sample app to show the behavior. Every click on any button causes more memory to be consumed. I suppose that the correct way to deal with this is to remove any listeners on the ToggleButtons before clearing them but PaginationSkin isn't doing that.
Have I missed something or is this a real bug in the Pagination code?
Are there any workarounds?
The same behavior is seen it both 8u40 and 8u45. I haven't tried any older versions yet.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ToggleGroupLeak extends Application {
private HBox hbox;
private ToggleGroup toggleGroup;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
toggleGroup = new ToggleGroup();
hbox = new HBox();
redo();
root.getChildren().add(hbox);
primaryStage.setScene(new Scene(root, 700, 100));
primaryStage.show();
}
private void redo() {
hbox.getChildren().clear();
toggleGroup.getToggles().clear();
for (int i = 0; i < 20; i++) {
addButton(i);
}
}
private void addButton(final int i) {
ToggleButton btn = new ToggleButton("" + i);
btn.setToggleGroup(toggleGroup);
btn.setOnAction(e -> redo());
hbox.getChildren().add(btn);
}
}
Concretely, usage of Pagination in an AnchorPane seems to cause the same leak by resizing the control.
FXML
<Pagination fx:id="pagination" visible="true" AnchorPane.topAnchor="3.0" AnchorPane.leftAnchor="12.0" AnchorPane.rightAnchor="130.0"/>
Java
#FXML
protected Pagination pagination;
// Init method run once
pagination.setMaxPageIndicatorCount(100);
pagination.currentPageIndexProperty().addListener((observable, oldPageIndex, newPageIndex) -> {
if (!oldPageIndex.equals(newPageIndex)) {
if (searchParams.getPage() == newPageIndex.intValue()) {
return;
}
searchParams.setPage(newPageIndex.intValue());
// Code that inits the new page
}
});
EDIT: Added pagination specific code
I have an app in JavaFX that is getting a bit large, and I want to keep the code readable.
I have a LineChart that I want to have zoom functionality built in, that occurs on a mouseclick. I know I need to register a mouse listener to the chart. What I cannot figure out from Oracle examples, ie as written here:
http://docs.oracle.com/javafx/2/events/handlers.htm
is how to NOT have my handler defined inline to the registering. In other words, I want the body of the handler (which is many lines of code) to be in another class. Can I do that? And if so, how do I register the handler to my chart in my main Javafx controller code?
Place your handler in a new class which implements the the Mouse EventHandler and register an instance of your class with your target node via the node's setOnClicked method.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* JavaFX sample for registering a click handler defined in a separate class.
* http://stackoverflow.com/questions/12326180/registering-mouse-handler-but-handler-not-inline-in-javafx
*/
public class ClickHandlerSample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws Exception {
stage.setTitle("Left click to zoom in, right click to zoom out");
ImageView imageView = new ImageView("http://upload.wikimedia.org/wikipedia/commons/b/b7/Idylls_of_the_King_3.jpg");
imageView.setPreserveRatio(true);
imageView.setFitWidth(150);
imageView.setOnMouseClicked(new ClickToZoomHandler());
final StackPane layout = new StackPane();
layout.getChildren().addAll(imageView);
layout.setStyle("-fx-background-color: cornsilk;");
stage.setScene(new Scene(layout, 400, 500));
stage.show();
}
private static class ClickToZoomHandler implements EventHandler<MouseEvent> {
#Override public void handle(final MouseEvent event) {
if (event.getSource() instanceof Node) {
final Node n = (Node) event.getSource();
switch (event.getButton()) {
case PRIMARY:
n.setScaleX(n.getScaleX()*1.1);
n.setScaleY(n.getScaleY()*1.1);
break;
case SECONDARY:
n.setScaleX(n.getScaleX()/1.1);
n.setScaleY(n.getScaleY()/1.1);
break;
}
}
}
}
}