javafx Pagination memory leak when replacing buttons - java

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

Related

How to make a ListView selectable but not editable

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);
}
});
}
});

In javafx, how does one make methods done in event handlers affect the rest of the code?

Basically my code is like this:
fileOpener.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
myFileList.add(openMusicTracks.showOpenDialog(window));
System.out.println(myFileList.getName(0)); //prints file name so I know this works
}
});
I want the add method (that's inside of the EventHandler) to actually edit the arraylist for everywhere else so that later when I reference it in
ObservableList<String> playList = FXCollections.observableArrayList ();
for(int i = 0; i < myFileList.size(); i++) {
playList.add(i, myFileList.get(i).getName());
System.out.println(myFileList.getName(0)); //doesn't print the file name, so I know this doesn't work.
}
the arraylist won't be empty. How do I do this? I'm sorry if there's a more elegant way to word this, but I have honestly no idea how to research this, I've tried. Thanks.
A simple example which shows how can an ArrayList be shared between methods.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class Main extends Application {
private List<String> list = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
Button add = new Button("Add");
Button display = new Button("Show");
// Add Items
add.setOnAction(event -> list.add("Item"));
// Display Items
display.setOnAction(e -> {
printAndClear();
});
VBox root = new VBox(10, add, display);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private void printAndClear() {
list.forEach(System.out::println);
list.clear();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX custom Fonts

Actually searched everywhere but any answer can't help me, sohere's the problem:
I want to add a custom font to my buttons. I already tried to write a css java class and a lot of other solutions, but nothing helped my.
/code deleted/
I'd be so glad if anyone could help me!
Update:
I tried this:
package view;
import ...
public class SceneStyle {
ImageView header = new ImageView("/view/images/header.jpg");
ImageView chatImg = new ImageView("/view/images/chat_win.jpg");
//Layout Style
//public String font1 = "Impact";
//public String font2 = "Comic Sans MS";
public static String color1 = "#00adf0";
public static String color2 = "#0076a3";
public void setLabelStyle(Label label) {
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
}
public void setLabelStyle2(Label label){
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
}
public void setLabelStyle3(Label label){
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.RED);
}
public void setButtonStyle(Button button){
button.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 30; -fx-font-style: normal");
Scene scene = new Scene(button);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
button.setTextFill(Color.web(color2));
button.setPrefWidth(190);
}
public void setBorderPaneStyle(BorderPane pane, VBox btnBox, HBox scoreBox){
pane.setTop(header);
pane.setLeft(btnBox);
pane.setRight(chatImg);
pane.setBottom(scoreBox);
pane.getLeft().setStyle("-fx-background-image: url(\"/view/images/border_left.jpg\");");
pane.getBottom().setStyle("-fx-background-image: url(\"/view/images/bottom.jpg\");");
//pane.getCenter().setStyle("-fx-background-image: url(\"/view/images/center.jpg\");");
//TODO: why can't I implement this?
}
public static String getFontColor1(){ return color1; }
public static String getFontColor2(){ return color2; }
Thanks for the detailed answers, but actually I don't want to write a start method which is overwritten. Just want to implemtent the font in my code. And yes now I'm trying with the Google Fonts.
Thanks for answering my question.
Here is a simple example of how to use custom fonts in your JavaFX application. This example is just an edited version of the sample FontLoad application.
Output
Project Structure
The follow diagram is the project structure.
Java Class
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FontLoad extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label("JavaFX Application");
Button button = new Button("My Button");
VBox box = new VBox(15, label, button);
box.setAlignment(Pos.CENTER);
Scene scene = new Scene(box, 500, 300);
scene.getStylesheets().add(getClass().getResource("/fontstyle.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Stylesheet
#font-face {
font-family: 'Fleftex';
src: url('fonts/Fleftex_M.ttf');
}
.label {
-fx-font-family: 'Fleftex';
-fx-font-size: 20;
}
.button .text {
-fx-font-family: 'Fleftex';
}
Update >= 8u60
Starting with this version the attribute “font-family” is ignored and you have to use the real name of the TTF.
By example: to use the font “Fleftex” contained on the file Fleftex_M.ttf You have to use this CSS file :
#font-face {
src: url(“fonts/Fleftex_M.ttf”);
}
.label {
-fx-font-family: 'Fleftex';
}
Here is a little example showing how to load and set a custom font.
package createfont;
import java.io.IOException;
import java.io.InputStream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class CustomFontTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
String currentFontFile = "English Gothic, 17th c..TTF";
InputStream fontStream = CustomFontTest.class.getResourceAsStream(currentFontFile);
if (fontStream != null) {
Font bgFont = Font.loadFont(fontStream, 36);
fontStream.close();
final Button button = new Button("Press me");
button.setFont(bgFont);
BorderPane root = new BorderPane();
root.setCenter(button);
Scene scene = new Scene(root, 500, 100);
primaryStage.setTitle("CustomFontTest");
primaryStage.setScene(scene);
primaryStage.show();
} else {
throw new IOException("Could not create font: " + currentFontFile);
}
}
public static void main(String[] args) {
launch(args);
}
}
I tried this:
public void setLabelStyle(Label label) {
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
in each method. Because I don't want write a new methode which overrides the start method just like above. (Actually thanks for this detailed answer, but it isn't what I am searching for).
But also my solution doesn't work well...

JavaFX - Action during event

i am trying to influence a UI-Element during an event in javaFX.
void buttonClicked(ActionEvent e) {
labelInfo.setText("restarting - might take a few seconds");
jBoss.restart();
labelInfo.setText("JBoss successfully restarted");
}
The action "jBoss.restart()" waits till the JBoss is restarted.
The problem:
the text "restarting - ..." is not displayed. The application waits till the JBoss is restarted and then it shows the Text "JBoss successfully restarted".
My thoughts:
the scene is refreshed AFTER the event is completed. So the first label-change will not happen.
How can i show a info message during an event?
The problem it's that the FX Thread has no safe operations. So I'm guessing that jBoss.restart() it's taking a lot of time. So you have to put this command in a Service. Also I recommend to you a progress indicator to show to the user you are making a long operation.
Here it is an example but I encourage you to go to Concurrency in JavaFX and take a deep look on it. Maybe there are other things that can help you.
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
private Label labelInfo;
private Button button;
private ProgressIndicator progressIndicator;
#Override
public void start(Stage stage) throws Exception {
VBox vbox = new VBox(5);
vbox.setAlignment(Pos.CENTER);
labelInfo = new Label();
button = new Button("Restart");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
buttonClicked(event);
}
});
progressIndicator = new ProgressIndicator(-1);
progressIndicator.setVisible(false);
vbox.getChildren().addAll(labelInfo, progressIndicator, button);
Scene scene = new Scene(vbox, 300, 200);
stage.setScene(scene);
stage.show();
}
void buttonClicked(ActionEvent e) {
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
updateMessage("restarting - might take a few seconds");
// Here the blocking operation
// jBoss.restart();
Thread.sleep(10000);
updateMessage("JBoss successfully restarted");
return null;
}
};
}
};
// Make the progress indicator visible while running
progressIndicator.visibleProperty().bind(service.runningProperty());
// Bind the message of the service to text of the label
labelInfo.textProperty().bind(service.messageProperty());
// Disable the button, to prevent more clicks during the execution of
// the service
button.disableProperty().bind(service.runningProperty());
service.start();
}
}

JavaFX connecting components to model

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.

Categories

Resources