Related
I have a TableView which accepts data by individual cell editing. Now I also want to enter data by pasting from a file as shown below.
11.12 23.32 15.43
22.23 24.45 26.65
I want to paste data using say CTRL+V. I have already seen posts like post in StackOverflow or the GIT repo. I could not paste data for more than one row.
Here I am giving the main code.
package testmatrix;
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class TestMATRIX extends Application {
TableView<CommonDataClass> matrixData = new TableView<CommonDataClass>();
final ObservableList<CommonDataClass> matData = FXCollections.
observableArrayList();
TableColumn[] matrixDataColumns = new TableColumn[6];
int numColVal = 0;
int theNDX = 0;
int maxRowNum = 0;
TextField TC1 = new TextField();
TextField TC2 = new TextField();
TextField TC3 = new TextField();
TextField TC4 = new TextField();
TextField TC5 = new TextField();
TextField TC6 = new TextField();
boolean numColStatus = false;
int oldRowVal = 0;
int oldColVal = 0;
boolean newRow = false;
boolean newCol = false;
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
TextField myTextField = new TextField();
Label colL = new Label("Column Number->");
TextField colNumT = new TextField();
Button getNum = new Button("SET");
colNumT.setMaxWidth(40);
TableUtils.doCopyPasteHandler (matrixData, matData);
matrixData.setVisible(true);
matrixData.setEditable(true);
matrixData.getSelectionModel().setCellSelectionEnabled(true);
matrixData.getSelectionModel ().setSelectionMode (SelectionMode.MULTIPLE);
HBox hb1 = new HBox();
HBox hb2 = new HBox();
hb1.getChildren().add(matrixData);
hb2.getChildren().addAll(colL, colNumT, getNum);
root.setCenter(hb1);
root.setRight(hb2);
getNum.addEventHandler(ActionEvent.ACTION, e -> {
numColStatus = (colNumT.getText().isEmpty()) ? false : true;
numColVal = (numColStatus) ? Integer.parseInt(colNumT.getText()): 0;
if (numColStatus) {
addRowBelow();
matrixData.getColumns ().clear ();
for (int ii = 0; ii < numColVal; ii++) {
matrixDataColumns[ii] = createCol(ii);
editCommit(ii);
}
matrixData.setItems(matData);
if (numColStatus) {
for (int ii = 0; ii < numColVal; ii++) {
matrixData.getColumns().add(matrixDataColumns[ii]);
}
}
matrixData.refresh();
}
});
Scene scene = new Scene(root, 1200, 400);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
TableColumn<CommonDataClass, String> createCol(int icol) {
TableColumn<CommonDataClass, String> column = new TableColumn<>();
column.setMinWidth(30);
column.setStyle(
"-fx-alignment: CENTER-RIGHT;-fx-font-family: monospace; -fx-font-size: 10px; ");
String nameC = "myD" + (icol + 1);
System.out.println("colName->" + nameC);
column.setCellValueFactory(
new PropertyValueFactory<CommonDataClass, String>(nameC));
column.setCellFactory(
new DragSelectionCellFactory<CommonDataClass, String>(TextFieldTableCell.forTableColumn()
)
);
column.setMinWidth(120);
return column;
}
private void editCommit(int ii) {
System.out.println("Command came here");
matrixDataColumns[ii].setOnEditCommit(
new EventHandler<CellEditEvent<CommonDataClass, String>>() {
#Override
public void handle(CellEditEvent<CommonDataClass, String> event) {
int colNum = event.getTablePosition().getColumn();
switch (colNum) {
case 0:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD1(event.getNewValue());
break;
case 1:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD2(event.getNewValue());
break;
case 2:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD3(event.getNewValue());
break;
case 3:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD4(event.getNewValue());
break;
case 4:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD5(event.getNewValue());
break;
case 5:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD6(event.getNewValue());
break;
}
matrixData.setItems(matData);
matrixData.refresh();
if (!event.getNewValue().isEmpty()) {
addRowBelow();
}
}
});
}
void addRowBelow() {
matData.add(new CommonDataClass(
TC1.getText(), TC2.getText(), TC3.getText(),
TC4.getText(), TC5.getText(), TC6.getText()
));
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Here I am showing the data structure for the TableView.
package testmatrix;
import javafx.beans.property.*;
public class CommonDataClass {
private final SimpleStringProperty myD1;
private final SimpleStringProperty myD2;
private final SimpleStringProperty myD3;
private final SimpleStringProperty myD4;
private final SimpleStringProperty myD5;
private final SimpleStringProperty myD6;
public CommonDataClass(String myStr1, String myStr2, String myStr3, String myStr4, String myStr5, String myStr6) {
this.myD1 = new SimpleStringProperty(myStr1);
this.myD2 = new SimpleStringProperty(myStr2);
this.myD3 = new SimpleStringProperty(myStr3);
this.myD4 = new SimpleStringProperty(myStr4);
this.myD5 = new SimpleStringProperty(myStr5);
this.myD6 = new SimpleStringProperty(myStr6);
}
public String getMyD1(){
return myD1.get();
}
public String getMyD2(){
return myD2.get();
}
public String getMyD3(){
return myD3.get();
}
public String getMyD4(){
return myD4.get();
}
public String getMyD5(){
return myD5.get();
}
public String getMyD6(){
return myD6.get();
}
public void setMyD1(String myStr){
myD1.set(myStr);
}
public void setMyD2(String myStr){
myD2.set(myStr);
}
public void setMyD3(String myStr){
myD3.set(myStr);
}
public void setMyD4(String myStr){
myD4.set(myStr);
}
public void setMyD5(String myStr){
myD5.set(myStr);
}
public void setMyD6(String myStr){
myD6.set(myStr);
}
public StringProperty dataNameProperty(int index) {
StringProperty strProp = null;
switch (index) {
case 0:
strProp = myD1;
break;
case 1:
strProp = myD2;
break;
case 2:
strProp = myD3;
break;
case 3:
strProp = myD4;
break;
case 4:
strProp = myD5;
break;
case 5:
strProp = myD6;
break;
}
return strProp;
}
}
Here I am giving the code for the DrageSelection for cell.
package testmatrix;
import javafx.scene.control.TableCell;
import javafx.scene.input.*;
public class DragSelectionCell extends TableCell<CommonDataClass, String> {
public DragSelectionCell() {
setOnDragDetected ((MouseEvent event) -> {
startFullDrag ();
getTableColumn ().getTableView ().getSelectionModel ().select (
getIndex (), getTableColumn ());
});
setOnMouseDragEntered ((MouseDragEvent event) -> {
getTableColumn ().getTableView ().getSelectionModel ().select (
getIndex (), getTableColumn ());
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem (item, empty);
if ( empty ) {
setText (null);
} else {
setText (item);
}
}
}
Here I am giving the DragSelectionCellfactory code.
package testmatrix;
import javafx.scene.control.*;
import javafx.util.Callback;
public class DragSelectionCellFactory<CommonDataClass, String> implements
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> {
private final Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> factory;
public DragSelectionCellFactory(
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> factory) {
this.factory = factory;
}
public DragSelectionCellFactory() {
this (col -> new TableCell<CommonDataClass, String> () {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem (item, empty);
if ( empty || item == null ) {
setText (null);
} else {
setText (item.toString ());
}
}
});
}
#Override
public TableCell<CommonDataClass, String> call(
final TableColumn<CommonDataClass, String> col) {
TableCell<CommonDataClass, String> cell = factory.call (col);
cell.setOnDragDetected (event -> {
cell.startFullDrag ();
col.getTableView ().getSelectionModel ().select (cell.getIndex (),
col);
});
cell.setOnMouseDragEntered (event -> {
col.getTableView ().getSelectionModel ().select (cell.getIndex (),
col);
});
cell.setOnMouseEntered (e -> {
String item = cell.getItem ();
if ( item != null ) {
}
});
return cell;
}
}
The following code blocks describe similar to the Table Utility for copy/paste routine as mentioned in the beginning. However, paste part was not working and so I tried to modify. Now I am totally confused. The copy part was copying, while paste part as per code shown in the reference above could not paste with all the rows and columns.
package testmatrix;
import java.util.StringTokenizer;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.input.*;
public class TableUtils extends TableCell<CommonDataClass, String> {
ObservableList<CommonDataClass> datTab = FXCollections.
observableArrayList ();
public static void doCopyPasteHandler(TableView<?> table,
ObservableList<CommonDataClass> myData) {
table.setOnKeyPressed (new TableKeyEventHandler ());
}
public static class TableKeyEventHandler implements EventHandler<KeyEvent> {
KeyCodeCombination copyCode = new KeyCodeCombination (KeyCode.C,
KeyCodeCombination.CONTROL_ANY);
KeyCodeCombination pasteCode = new KeyCodeCombination (KeyCode.V,
KeyCodeCombination.CONTROL_ANY);
//#Override
public void handle(KeyEvent event) {
if ( copyCode.match (event) ) {
if ( event.getSource () instanceof TableView ) {
copySelection2Clipboard ((TableView<?>) event.getSource ()); // this will copy to clipboard
event.consume (); // After using event please consume
}
} else if ( pasteCode.match (event) ) {
if ( event.getSource () instanceof TableView ) {
pasteFromClipboard ((TableView<?>) event.getSource ());
event.consume ();
}
}
}
}
public static void copySelection2Clipboard(TableView<?> table) {
StringBuilder clipboardString = new StringBuilder ();
ObservableList<TablePosition> positionList = table.getSelectionModel ().
getSelectedCells ();
int prevRow = -1;
for ( TablePosition pos : positionList ) {
int row = pos.getRow ();
int col = pos.getColumn ();
if ( prevRow == row ) { // determine whether we advance in a row or col (newline)
clipboardString.append ('\t');
} else if ( prevRow != -1 ) {
clipboardString.append ('\n');
}
String text = "";
Object obsValue = (Object) table.getColumns ().get (col).
getCellObservableValue (row);
if ( obsValue == null ) {
text = "";
} else if ( obsValue instanceof StringProperty ) {
text = ((StringProperty) obsValue).get ();
} else {
System.out.println ("Unsupported observable value: " + obsValue);
}
clipboardString.append (text);
prevRow = row;
}
ClipboardContent clipboardContent = new ClipboardContent ();
clipboardContent.putString (clipboardString.toString ());
}
public static void pasteFromClipboard(TableView<?> table) {
if ( table.getSelectionModel ().getSelectedCells ().size () == 0 ) {
return;
}
TablePosition pasteCellPosition = table.getSelectionModel ().
getSelectedCells ().get (0); // get cell position at start
String pasteString = Clipboard.getSystemClipboard ().getString ();
StringTokenizer rowTokenizer = new StringTokenizer (pasteString, "\n");
int rowNum = rowTokenizer.countTokens () + 1;
int rowCB = -1;
while (rowTokenizer.hasMoreTokens ()) {
rowCB++;
String rowString = rowTokenizer.nextToken ();
StringTokenizer colTokenizer = new StringTokenizer (rowString, "\t");
int colCB = -1;
while (colTokenizer.hasMoreTokens ()) {
colCB++;
String clpCellCont = colTokenizer.nextToken ();
int rowTable = pasteCellPosition.getRow () + rowCB;
int colTable = pasteCellPosition.getColumn () + colCB;
if ( rowTable >= table.getItems ().size () ) {
continue;
}
if ( colTable >= table.getColumns ().size () ) {
continue;
}
TableColumn tabCol = table.getVisibleLeafColumn (colTable);
ObservableValue obsVal = tabCol.
getCellObservableValue (rowTable);
}
}
}
}
It would be of great help, if I get any help. Thanks and Regards
You couldn't insert a value since there is no code in your event handler, that assigns a value.
Furthermore you do not use the properties, which makes things more complicated.
If you do use the properties
TableColumn<CommonDataClass, String> createCol(int icol) {
...
column.setCellValueFactory(
cd -> cd.getValue().dataNameProperty(icol));
you can use the properties to assign the new values:
public static <T> void pasteFromClipboard(TableView<T> table) {
if (table.getSelectionModel().getSelectedCells().isEmpty()) {
return;
}
TablePosition pasteCellPosition = table.getSelectionModel().
getSelectedCells().get(0); // get cell position at start
Clipboard cb = Clipboard.getSystemClipboard();
// check, if clipboard contains a string
if (!cb.hasString()) {
return;
}
String pasteString = cb.getString();
String[][] values = Stream.of(pasteString.split("\r?\n"))
.map(line -> line.split("\t")).toArray(String[][]::new);
final int offsetY = pasteCellPosition.getRow();
final int offsetX = pasteCellPosition.getColumn();
final int maxY = Math.min(table.getItems().size() - offsetY, values.length);
final int colMax = table.getColumns().size() - offsetX;
for (int y = 0; y < maxY; y++) {
String[] r = values[y];
final int maxX = Math.min(colMax, r.length);
T rowObject = table.getItems().get(y+offsetY);
for (int x = 0; x < maxX; x++) {
Object property = table.getColumns().get(x + offsetX).getCellObservableValue(rowObject);
if (property instanceof StringProperty) {
// write value using the property
((StringProperty) property).set(r[x]);
}
}
}
}
This only inserts values into existing rows...
Note though, that you should really use the naming conventions in your DragSelectionCellFactory class, i.e. use a single letter as name for type parameters. In your case the type parameters are named CommonDataClass and String, i.e. the names of existing classes, which will almost surely lead to confusion...
Probably you should remove the type parameters though:
public class DragSelectionCellFactory implements
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> {
Furthermore copying the content will not work, since you forgot to set the clipboard content:
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(clipboardString.toString());
Clipboard.getSystemClipboard().setContent(clipboardContent);
Furthermore there is no guarantee that the selected cells are in a rectangular area. Cells can be selected arbitrarily by holding down Ctrl.
Also
getNum.addEventHandler(ActionEvent.ACTION, e -> {...});
could (and in this case IMHO should) be replaced with
getNum.setOnAction(e -> {...});
As mentioned here and here there is no easy way to determine the required height of a webview, until "RT-25005 Automatic preferred sizing of WebView" is implemented.
Are there any workarounds to this issue? I couldn't find a solution in SO or elsewhere. However since i think this is no uncommon problem, there needs to be a workaround. Any idea?
For Webviewsembeded in a stage I found the following solution (see here):
webView.getEngine().executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
);
or
Double.parseDouble(webView.getEngine().executeScript("document.height").toString())
However this doesn't seem to work for Webviews embedded in a treecell, like here. In this case I always get too big numbers as a result.
Minimal running example (including recommendation of jewelsea):
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.w3c.dom.Document;
public class TableViewSampleHTML extends Application {
private final ObservableList<MyData> data = FXCollections.observableArrayList(new MyData(1L), new MyData(3L), new MyData(2L), new MyData(4L), new MyData(1L));
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage stage) {
final Scene scene = new Scene(new Group());
TableView<MyData> table = new TableView<>();
table.setPrefHeight(700);
final TableColumn<MyData, Long> nameCol = new TableColumn("So So");
nameCol.setMinWidth(200);
nameCol.setCellValueFactory(new PropertyValueFactory<>("i"));
// Allow to display Textflow in Column
nameCol.setCellFactory(new Callback<TableColumn<MyData, Long>, TableCell<MyData, Long>>() {
#Override
public TableCell<MyData, Long> call(TableColumn<MyData, Long> column) {
return new TableCell<MyData, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
setStyle("");
} else {
WebView webview = new WebView();
webview.setPrefWidth(700.0);
WebEngine engine = webview.getEngine();
String textHTML = new String(new char[item.intValue()]).replace("\0", " <b> bold </b> normal, ");
// textHTML = "<body>"
// + textHTML + "</body>";
engine.loadContent(textHTML);
setGraphic(webview);
engine.documentProperty().addListener((obj, prev, newv) -> {
String heightText = engine.executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
).toString();
System.out.println("heighttext: " + heightText);
webview.setPrefHeight(Double.parseDouble(heightText.replace("px", "")));
this.setPrefHeight(Double.parseDouble(heightText.replace("px", "")));
setGraphic(webview);
});
}
}
};
}
});
table.setItems(data);
table.getColumns().addAll(nameCol);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
public static class MyData {
private Long i;
public MyData(Long i) {
this.i = i;
}
public Long getI() {
return i;
}
}
}
Now the outout is
heighttext: 581px
heighttext: 581px
However these values seem to be too big. See screeenshot:
Some progress has been made and cell heights are now calculated more realisticly. Kindly see the relevant the adapted code below.
Relevant changens:
Seems like it is mandatory to call webview.setPrefHeight(-1); before executing the jevascript.
Javascript has been modified. No big change seen, but maybe the result is more general
Open points:
For some reason i still have to add + 15.0 to the calculated height. This is a hack. Seems like some additional lenght has to be considered somewhere.
Functionality on recalculation after resize of column has is not optimal. Using table.refresh() causes significant delay in rendering.
public class TableViewSampleHTML extends Application {
private final ObservableList<MyData> data = FXCollections.observableArrayList(new MyData(1L), new MyData(14L), new MyData(2L), new MyData(3L), new MyData(15L));
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage stage) {
final Scene scene = new Scene(new Group(), 400, 800);
TableView<MyData> table = new TableView<>();
table.setPrefWidth(400);
table.setPrefHeight(800);
final TableColumn<MyData, Long> nameCol = new TableColumn("So So");
final TableColumn<MyData, Long> col2 = new TableColumn("la la");
nameCol.setPrefWidth(200);
col2.setCellValueFactory(new PropertyValueFactory<>("i"));
nameCol.setCellValueFactory(new PropertyValueFactory<>("i"));
nameCol.widthProperty().addListener((ob,oldV,newV) -> {table.refresh();} );
// Allow to display Textflow in Column
nameCol.setCellFactory(new Callback<TableColumn<MyData, Long>, TableCell<MyData, Long>>() {
#Override
public TableCell<MyData, Long> call(TableColumn<MyData, Long> column) {
return new TableCell<MyData, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
setStyle("");
} else {
WebView webview = new WebView();
WebEngine engine = webview.getEngine();
webview.setPrefHeight(-1); // <- Absolute must at this position (before calling the Javascript)
setGraphic(webview);
String textHTML = new String(new char[item.intValue()]).replace("\0", " <b> bold </b> normal, ");
textHTML = "<body>"
+ textHTML + "</body>";
engine.loadContent(textHTML);
engine.documentProperty().addListener((obj, prev, newv) -> {
String heightText = engine.executeScript( // <- Some modification, which gives moreless the same result than the original
"var body = document.body,"
+ "html = document.documentElement;"
+ "Math.max( body.scrollHeight , body.offsetHeight, "
+ "html.clientHeight, html.scrollHeight , html.offsetHeight );"
).toString();
System.out.println("heighttext: " + heightText);
Double height = Double.parseDouble(heightText.replace("px", "")) + 15.0; // <- Why are this 15.0 required??
webview.setPrefHeight(height);
this.setPrefHeight(height);
});
}
}
};
}
});
table.setItems(data);
table.getColumns().addAll(nameCol);
table.getColumns().addAll(col2);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
public static class MyData {
private Long i;
public MyData(Long i) {
this.i = i;
}
public Long getI() {
return i;
}
}
}
Output now looks like:
From the example you linked (JavaFX webview, get document height) the height of the document is computed in a ChangeListener on the document:
engine.documentProperty().addListener((prop, oldDoc, newDoc) -> {
String heightText = engine.executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
).toString();
System.out.println("heighttext: " + heightText);
});
Output:
heighttext: 36px
heighttext: 581px
heighttext: 581px
In the code in your question you are not executing the height check based upon a ChangeListener. So you are querying the height of the WebView document before the document has been loaded (which is why it is returning zero for your code).
Basedon BerndGirt's answer.
public class WebviewCellFactory<S,T> implements Callback<TableColumn<S,T>, TableCell<S,T>> {
#Override
public TableCell<S, T> call(TableColumn<S, T> column) {
return new TableCell<S, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
setStyle("");
} else {
WebView webview = new WebView();
WebEngine engine = webview.getEngine();
webview.setPrefHeight(-1); // <- Absolute must at this position (before calling the Javascript)
webview.setBlendMode(BlendMode.DARKEN);
setGraphic(webview);
engine.loadContent("<body topmargin=0 leftmargin=0 style=\"background-color: transparent;\">"+item+"</body>");
engine.documentProperty().addListener((obj, prev, newv) -> {
String heightText = engine.executeScript( // <- Some modification, which gives moreless the same result than the original
"var body = document.body,"
+ "html = document.documentElement;"
+ "Math.max( body.scrollHeight , body.offsetHeight, "
+ "html.clientHeight, html.scrollHeight , html.offsetHeight );"
).toString();
Double height = Double.parseDouble(heightText.replace("px", "")) + 10 ; // <- Why are this 15.0 required??
webview.setPrefHeight(height);
this.setPrefHeight(height);
});
}
}
};
}
}
then you just need to set
tableColumn.setCellFactory(new WebviewCellFactory());
if there are any mistakes please tell me.
Is there an easy way to put a scroll bar in an Alert box and have it actually scroll? I'm adding a grid pane to the scroll pane. While the Alert box is active, I'm adding and removing content as the user requests. Unfortunately, there is a limit to ho much I can add because the Alert box grows beyond the screen height. Hence the reason for a scroll pane. The problem is, it doesn't seem to think it needs to scroll even though the content is below the screen
I've tried a bunch of things, but each time the scroll bars grow with the scroll pane. I even tried a custom ScrollPane as suggested by James_D. Still not luck.
Any help would be awesome!
Here is the code for the custom Alert box
import java.util.ArrayList;
import java.util.List;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XmlVisitor.TextPredictor;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class SettingsChangeWindow extends Alert {
public enum SETTING_TYPE {SINGLE, MULTIPLE};
private SETTING_TYPE type = null;
private IndexedGridPane parentGrid;
private SettingBean bean;
private DialogPane parentPane;
public SettingsChangeWindow(SettingBean bean) {
super(AlertType.CONFIRMATION);
this.bean = bean;
this.type = bean.getType();
SizeableScrollPane scroll = new SizeableScrollPane();
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
parentGrid = new IndexedGridPane();
scroll.setContent(parentGrid);
this.parentPane = getDialogPane();
setResizable(true);
if(type == SETTING_TYPE.SINGLE){
FriendlyVBox vbox = new FriendlyVBox();
setTitle("Change " + bean.getName());
setHeaderText("Change the " + bean.getName() + " value by changing the value in the box");
parentGrid.setPadding(new Insets(20, 150, 0, 10));
vbox.getChildren().addAll(new Label(bean.getName()), new AutoTextBox(bean.getValue()));
parentGrid.add(vbox, 0, 0);
}else{
setTitle("Change " + bean.getName());
String header = "Change the " + bean.getName() + " value by changing the value in the box\n";
header += "You may add and delete value sets (may require resizing)";
setHeaderText(header);
parentGrid.add(new AddButton(), 1, 0);
parentGrid.add(new RemoveButton(), 2, 0);
addMultipleValues();
}
this.parentPane.setContent(scroll);
// parentPane.setMinHeight(GridPane.USE_PREF_SIZE);
// parentPane.setMinWidth(GridPane.USE_PREF_SIZE);
// getDialogPane().getChildren().stream().forEach(node -> ((Label)node).setMinHeight(Region.USE_PREF_SIZE));
}
public SettingBean getValue(){
return bean;
}
private void addMultipleValues(){
List<Object> values = bean.getChildren();
if(bean.getName().equals("TSPAddressPostal") || bean.getName().equals("SchemeOperatorAddressPostal")){
for(Object addr : values){
PhysicalAddressBean address = (PhysicalAddressBean)addr;
addPhysicalAddress(address);
}
}else{
for(Object uri : values){
addURI((String)uri);
}
}
}
public void saveValue(){
if(type == SETTING_TYPE.SINGLE){
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof FriendlyVBox){
// cast to FriendlyVBox
String value = ((FriendlyVBox)child).getTextField().getText();
this.bean.setValue(value);
}
}
}else{
saveMultipleValues();
}
}
/**
* For values in XML that can have multiple child nodes
*/
private void saveMultipleValues(){
switch(bean.getName()){
case "TSPAddressPostal" :
savePostalAddress();
break;
case "SchemeOperatorAddressPostal":
savePostalAddress();
break;
default:
saveURI();
break;
}
}
/**
* If the setting bean is encapsulating a list of physical address
* (when the name is: PostalAddress) populate via predefined structure
*/
private void savePostalAddress(){
List<Object> addresses = new ArrayList<>();
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof IndexedGridPane){
IndexedGridPane pane = (IndexedGridPane) child;
PhysicalAddressBean add = new PhysicalAddressBean();
// each address attribute in the order listed in Trust List XML
add.setStreetAddress(((FriendlyVBox)pane.get(0, 1)).getTextField().getText());
add.setLocality(((FriendlyVBox)pane.get(0, 2)).getTextField().getText());
add.setPostalCode(((FriendlyVBox)pane.get(0, 3)).getTextField().getText());
add.setCountryName(((FriendlyVBox)pane.get(0, 4)).getTextField().getText());
// add address bean to list
addresses.add(add);
}
}
bean.setChildren(addresses);
}
/**
* used to store any values in the XML that can have multiple child URI values
*/
private void saveURI(){
List<Object> uris = new ArrayList<>();
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof FriendlyVBox){
FriendlyVBox vBox = (FriendlyVBox) child;
uris.add(vBox.getTextField().getText());
}
}
bean.setChildren(uris);
}
private void addURI(String uri){
int newSlot = parentGrid.getRowCount();
FriendlyVBox vBox = new FriendlyVBox();
vBox.getChildren().addAll(new Label("\n" + bean.getName()), new AutoTextBox(""));
parentGrid.add(vBox, 0, newSlot);
}
private void addPhysicalAddress(){
int newSlot = parentGrid.getRowCount();
IndexedGridPane pane = new IndexedGridPane();
Label label = new Label("\nPostal Address");
pane.add(label, 0, 0);
label.setFont(Font.font("system", FontWeight.BOLD, 12));
FriendlyVBox postal = new FriendlyVBox();
postal.getChildren().addAll(new Label("Street Address"), new AutoTextBox(""));
FriendlyVBox local = new FriendlyVBox();
local.getChildren().addAll(new Label("Locale"), new AutoTextBox(""));
FriendlyVBox postalCode = new FriendlyVBox();
postalCode.getChildren().addAll(new Label("Postal Code"), new AutoTextBox(""));
FriendlyVBox country = new FriendlyVBox();
country.getChildren().addAll(new Label("Country Name"), new AutoTextBox(""));
pane.add(postal, 0, 1);
pane.add(local, 0, 2);
pane.add(postalCode, 0, 3);
pane.add(country, 0, 4);
parentGrid.add(pane, 0, newSlot);
}
private void addPhysicalAddress(PhysicalAddressBean address){
int newSlot = parentGrid.getRowCount();
IndexedGridPane pane = new IndexedGridPane();
Label label = new Label("\nPostal Address");
pane.add(label, 0, 0);
label.setFont(Font.font("system", FontWeight.BOLD, 12));
FriendlyVBox street = new FriendlyVBox();
street.getChildren().addAll(new Label("Street Address"), new AutoTextBox(address.getStreetAddress()));
FriendlyVBox local = new FriendlyVBox();
local.getChildren().addAll(new Label("Locale"), new AutoTextBox(address.getLocality()));
FriendlyVBox postalCode = new FriendlyVBox();
postalCode.getChildren().addAll(new Label("Postal Code"), new AutoTextBox(address.getPostalCode()));
FriendlyVBox country = new FriendlyVBox();
country.getChildren().addAll(new Label("Country Name"), new AutoTextBox(address.getCountryName()));
pane.add(street, 0, 1);
pane.add(local, 0, 2);
pane.add(postalCode, 0, 3);
pane.add(country, 0, 4);
parentGrid.add(pane, 0, newSlot);
}
private class IndexedGridPane extends GridPane{
public Node get(final int row, final int column) {
Node result = null;
ObservableList<Node> childrens = super.getChildren();
for (Node node : childrens) {
if(super.getRowIndex(node) == row && super.getColumnIndex(node) == column) {
result = node;
break;
}
}
return result;
}
public int getRowCount() {
int numRows = getRowConstraints().size();
for (int i = 0; i < getChildren().size(); i++) {
Node child = getChildren().get(i);
if (child.isManaged()) {
Integer rowIndex = GridPane.getRowIndex(child);
if(rowIndex != null){
numRows = Math.max(numRows,rowIndex+1);
}
}
}
return numRows;
}
}
private class AutoTextBox extends TextField{
public AutoTextBox(String contents){
setMinWidth(Region.USE_PREF_SIZE);
setMaxWidth(Region.USE_PREF_SIZE);
textProperty().addListener(new AutoAdjustText());
setText(contents);
}
private class AutoAdjustText implements ChangeListener<String>{
#Override
public void changed(ObservableValue<? extends String> ov,
String prevText, String currText) {
Platform.runLater(() -> {
Text text = new Text(currText);
text.setFont(getFont()); // Set the same font, so the size is the same
double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField
+ getPadding().getLeft() + getPadding().getRight() // Add the padding of the TextField
+ 2d; // Add some spacing
setPrefWidth(width); // Set the width
positionCaret(getCaretPosition()); // If you remove this line, it flashes a little bit
});
}
}
}
private class FriendlyVBox extends VBox{
public TextField getTextField(){
List<Node> children = getChildren();
for(Node child : children){
if(child instanceof TextField){
return (TextField)child;
}
}
return null;
}
}
private class AddButton extends Button{
public AddButton(){
setText("Add+");
onActionProperty().set(new AddValue());
}
private class AddValue implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
if(bean.getName().equals("TSPAddressPostal") || bean.getName().equals("SchemeOperatorAddressPostal")){
addPhysicalAddress();
}else{
addURI("Add URI here");
// parentPane.setContent(parentGrid);
}
parentPane.getScene().getWindow().sizeToScene();
}
}
}
private class RemoveButton extends Button{
public RemoveButton(){
setText("Remove");
onActionProperty().set(new RemoveValue());
}
private class RemoveValue implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
int rowCount = parentGrid.getRowCount();
parentGrid.getChildren().remove(rowCount);
parentPane.getScene().getWindow().sizeToScene();
}
}
}
private class SizeableScrollPane extends ScrollPane{
public SizeableScrollPane() {
viewportBoundsProperty().addListener(new Resizer());
hvalueProperty().addListener(new Resizer());
vvalueProperty().addListener(new Resizer());
}
private class Resizer implements ChangeListener<Object> {
#Override
public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
double hmin = getHmin();
double hmax = getHmax();
double hvalue = getHvalue();
double contentWidth = getContent().getLayoutBounds().getWidth();
double viewportWidth = getViewportBounds().getWidth();
double hoffset =
Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
double vmin = getVmin();
double vmax = getVmax();
double vvalue = getVvalue();
double contentHeight = getContent().getLayoutBounds().getHeight();
double viewportHeight = getViewportBounds().getHeight();
double voffset =
Math.max(0, contentHeight - viewportHeight) * (vvalue - vmin) / (vmax - vmin);
System.out.printf("Offset: [%.1f, %.1f] width: %.1f height: %.1f %n",
hoffset, voffset, viewportWidth, viewportHeight);
}
}
}
}
I feel stupid.
if you comment out the lines in the constructor:
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
And you comment out the lines in the button listeners:
parentPane.getScene().getWindow().sizeToScene();
All is good in the world. You don't even need a custom ScrollPane, it works as expected. I hope this helps someone else
I have been trying to show subscript and superscript text in HTMLEditor. there are two buttons for sub and sup mode. the user types the (sub/sup)text in a textfield and press the OK button which allows the textfield text to be rendered as sub or sup in HTMLEditor. The code is as follows:
import java.util.List;
import java.util.regex.Pattern;
import javafx.application.*;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.geometry.Orientation;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.web.HTMLEditor;
import javafx.stage.Stage;
public class HTMLEditorCustomizationSample extends Application {
// limits the fonts a user can select from in the html editor.
private static final List<String> limitedFonts = FXCollections.observableArrayList("Arial", "Times New Roman", "Courier New", "Comic Sans MS");
String sup = " ⁺⁻⁼⁽⁾⁰¹²³⁴⁵⁶⁷⁸⁹ᴬᵃᴭᵆᵄᵅᶛᴮᵇᶜᶝᴰᵈᶞᴱᵉᴲᵊᵋᶟᵌᶠᴳᵍᶢˠʰᴴʱᴵⁱᶦᶤᶧᶥʲᴶᶨᶡᴷᵏˡᴸᶫᶪᶩᴹᵐᶬᴺⁿᶰᶮᶯᵑᴼᵒᵓᵔᵕᶱᴽᴾᵖᶲʳᴿʴʵʶˢᶳᶴᵀᵗᶵᵁᵘᶸᵙᶶᶣᵚᶭᶷᵛⱽᶹᶺʷᵂˣʸᶻᶼᶽᶾꝰᵜᵝᵞᵟᶿᵠᵡᵸჼˤⵯ";
String supchars = " +−=()0123456789AaÆᴂɐɑɒBbcɕDdðEeƎəɛɜɜfGgɡɣhHɦIiɪɨᵻɩjJʝɟKklLʟᶅɭMmɱNnɴɲɳŋOoɔᴖᴗɵȢPpɸrRɹɻʁsʂʃTtƫUuᴜᴝʉɥɯɰʊvVʋʌwWxyzʐʑʒꝯᴥβγδθφχнნʕⵡ";
String subchars=" +−=()0123456789aeəhijklmnoprstuvxβγρφχ";
String sub=" ₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₔₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ";
char[] csup = sup.toCharArray();
char[] characters = supchars.toCharArray();
char[] csub = sub.toCharArray();
char[] character = subchars.toCharArray();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// create a new html editor and show it before we start modifying it.
final HTMLEditor htmlEditor = new HTMLEditor();
stage.setScene(new Scene(htmlEditor));
stage.show();
// hide controls we don't need.
hideImageNodesMatching(htmlEditor, Pattern.compile(".*(Cut|Copy|Paste).*"), 0);
Node seperator = htmlEditor.lookup(".separator");
seperator.setVisible(false);
seperator.setManaged(false);
// modify font selections.
int i = 0;
for (Node candidate : (htmlEditor.lookupAll("MenuButton"))) {
// fonts are selected by the second menu in the htmlEditor.
if (candidate instanceof MenuButton && i == 1) {
// limit the font selections to our predefined list.
MenuButton menuButton = (MenuButton) candidate;
List<MenuItem> removalList = FXCollections.observableArrayList();
final List<MenuItem> fontSelections = menuButton.getItems();
for (MenuItem item : fontSelections) {
if (!limitedFonts.contains(item.getText())) {
removalList.add(item);
}
}
fontSelections.removeAll(removalList);
// Select a font from out limited font selection.
// Selection done in Platform.runLater because if you try to do
// the selection immediately, it won't take place.
Platform.runLater(new Runnable() {
#Override
public void run() {
boolean fontSelected = false;
for (final MenuItem item : fontSelections) {
if ("Comic Sans MS".equals(item.getText())) {
if (item instanceof RadioMenuItem) {
((RadioMenuItem) item).setSelected(true);
fontSelected = true;
}
}
}
if (!fontSelected && fontSelections.size() > 0 && fontSelections.get(0) instanceof RadioMenuItem) {
((RadioMenuItem) fontSelections.get(0)).setSelected(true);
}
}
});
}
i++;
}
// add a custom button to the top toolbar.
Node node = htmlEditor.lookup(".top-toolbar");
if (node instanceof ToolBar) {
ToolBar bar = (ToolBar) node;
ToggleButton supButton = new ToggleButton("x²");
ToggleButton subButton = new ToggleButton("x₂");
TextField txt = new TextField();
Button okBtn = new Button("OK");
Button clrBtn = new Button("CLEAR");
ToggleGroup group = new ToggleGroup();
supButton.setToggleGroup(group);
subButton.setToggleGroup(group);
Separator v1=new Separator();
v1.setOrientation(Orientation.VERTICAL);
Separator v2=new Separator();
v2.setOrientation(Orientation.VERTICAL);
txt.setDisable(true);
okBtn.setDisable(true);;
clrBtn.setDisable(true);
bar.getItems().add(v1);
bar.getItems().add(supButton);
bar.getItems().add(subButton);
bar.getItems().add(v2);
bar.getItems().add(txt);
bar.getItems().add(okBtn);
bar.getItems().add(clrBtn);
okBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
System.out.println(htmlEditor.getHtmlText());
if (supButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
String text = htmlEditor.getHtmlText().replaceAll("</p></body></html>", "");
text = text.replaceAll("<html dir=\"ltr\"><head></head><body contenteditable=\"true\"><p>", "");
System.out.println(text);
text="<p>"+text + "<sup>"+ txt.getText()+"</sup></p>";
System.out.println(text);
htmlEditor.setHtmlText(text);
}
else if (subButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
String text = htmlEditor.getHtmlText().replaceAll("</p></body></html>", "");
text = text.replaceAll("<html dir=\"ltr\"><head></head><body contenteditable=\"true\"><p>", "");
System.out.println(text);
text=text + "<sub>"+ txt.getText()+"</sup></p>";
System.out.println(text);
htmlEditor.setHtmlText(text);
}
}
});
clrBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
txt.clear();
}
});
supButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if (supButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
txt.setDisable(false);
okBtn.setDisable(false);;
clrBtn.setDisable(false);
}
}
});
subButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if (subButton.isSelected()) {
txt.setPromptText(" Enter the subscript text ");
txt.setDisable(false);
okBtn.setDisable(false);;
clrBtn.setDisable(false);
}
}
});
}
}
private String convertSupText(String dsup) {
char[] cdsup = dsup.toCharArray();
String data="";
for (int i = 0; i < cdsup.length; i++) {
for (int j = 0; j < characters.length; j++) {
if (cdsup[i] == characters[j]) {
data = data + csup[j];
}
}
}
return data;
}
private String convertSubText(String dsup) {
char[] cdsup = dsup.toCharArray();
String data="";
for (int i = 0; i < cdsup.length; i++) {
for (int j = 0; j < character.length; j++) {
if (cdsup[i] == character[j]) {
data = data + csub[j];
}
}
}
return data;
}
// hide buttons containing nodes whose image url matches a given name pattern.
public void hideImageNodesMatching(Node node, Pattern imageNamePattern, int depth) {
if (node instanceof ImageView) {
ImageView imageView = (ImageView) node;
String url = imageView.getImage().impl_getUrl();
if (url != null && imageNamePattern.matcher(url).matches()) {
Node button = imageView.getParent().getParent();
button.setVisible(false);
button.setManaged(false);
}
}
if (node instanceof Parent) {
for (Node child : ((Parent) node).getChildrenUnmodifiable()) {
hideImageNodesMatching(child, imageNamePattern, depth + 1);
}
}
}
}
The problem is that after adding the subscript or superscript text, the cursor still remains in subscript or superscript mode and every time the text is added it goes on a newline.
#Manoj I think your problem is that you don't know what the HTMLeditor is doing with any text you enter in the textfield (aka WebPage). Appearantly it is applying the your <sub> tag to the next text you enter (adding 1 and typing a normal 2 afterwards results in 12):
<html dir="ltr"><head></head><body contenteditable="true"><p><br><sup>1</sup></p></body></html>
<html dir="ltr"><head></head><body contenteditable="true"><p><br><sup>1<font size="2">1</font></sup></p></body></html>
I looked into the files (HTMLEditor>HTMLEditorSkin>WebPage>twkExecuteCommand) and in the end commands like bold/italic are executed in a dll (jfxwebkit). My knowledge is exceeded here. I see no solution which would not involve rewriting the whole HTMLEditor + native libraries.
(just included this in an answer bc comment length was exceeded)
thought so. I have done a work around using webview along with html editor. And it works fine for now. The code is as follows:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Separator;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class FXMLDocumentController implements Initializable {
#FXML
private HTMLEditor HE;
#FXML
private WebView WV;
WebEngine webEngine;
Button supButton;
Button subButton;
Tooltip sup;
Tooltip sub;
Alert info= new Alert(Alert.AlertType.INFORMATION);;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
webEngine = WV.getEngine();
supButton = new Button("x²");
subButton = new Button("x₂");
supButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
info.setTitle("SUCCESS");
info.setHeaderText("Information");
info.setContentText("Use <sup>Text to to superscripted</sup> to use superscript fuction.\n Press Preview button to preview the changes");
info.showAndWait();
}});
subButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
info.setTitle("SUCCESS");
info.setHeaderText("Information");
info.setContentText("Use <sub>Text to to subscripted</sub> to use subscript fuction.\n Press Preview button to preview the changes");
info.showAndWait();
}});
sup = new Tooltip();
sub = new Tooltip();
sup.setText(" Use <sup>Text to to superscripted</sup> to use superscript fuction.\n Press Preview button to preview the changes ");
sub.setText(" Use <sub>Text to to subscripted</sub> to use subscript fuction.\n Press Preview button to preview the changes ");
Node node = HE.lookup(".top-toolbar");
if (node instanceof ToolBar) {
ToolBar bar = (ToolBar) node;
Separator v2 = new Separator();
v2.setOrientation(Orientation.VERTICAL);
bar.getItems().add(supButton);
bar.getItems().add(subButton);
bar.getItems().add(v2);
}
supButton.setTooltip(sup);
subButton.setTooltip(sub);
}
#FXML
private void handleKeyTyped(ActionEvent event) {
String text = HE.getHtmlText();
text = text.replaceAll("<sup>", "<sup>");
text = text.replaceAll("</sup>", "</sup>");
text = text.replaceAll("<sub>", "<sub>");
text = text.replaceAll("</sub>", "</sub>");
webEngine.loadContent(text);
}
}
I´m using a FXML to set my form, but I need to set the limit of characters in textfields. How can I made this ?
You can't directly set a limit to number of characters. But you can add a listener to lengthProperty() of the textfield
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldLimit extends Application {
private static final int LIMIT = 10;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) {
final TextField textField = new TextField();
textField.lengthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
if (newValue.intValue() > oldValue.intValue()) {
// Check if the new character is greater than LIMIT
if (textField.getText().length() >= LIMIT) {
// if it's 11th character then just setText to previous
// one
textField.setText(textField.getText().substring(0, LIMIT));
}
}
}
});
VBox vbox = new VBox(20);
vbox.getChildren().add(textField);
Scene scene = new Scene(vbox, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
One more elegance solution
Pattern pattern = Pattern.compile(".{0,25}");
TextFormatter formatter = new TextFormatter((UnaryOperator<TextFormatter.Change>) change -> {
return pattern.matcher(change.getControlNewText()).matches() ? change : null;
});
textField.setTextFormatter(formatter);
where 0 and 25 - min and max amount of chars. + ability to set a pattern of input text
Here is my solution to limit the length of a textfield.
I would not recommend solutions which use a listener (on text property or on length property), they do not behave correctly in all situations (for what I have seen).
I create an HTML input text with a max length, and compare it to my textfield in JavaFX. I had the same behavior with paste operations (Ctrl + V), cancel operations (Ctrl + Z) in both cases. The goal here is to check if the text is valid BEFORE modifying the textfield.
We could use a similar approach for a numeric text field.
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.control.TextField;
public class LimitedTextField extends TextField {
private final IntegerProperty maxLength;
public LimitedTextField() {
super();
this.maxLength = new SimpleIntegerProperty(-1);
}
public IntegerProperty maxLengthProperty() {
return this.maxLength;
}
public final Integer getMaxLength() {
return this.maxLength.getValue();
}
public final void setMaxLength(Integer maxLength) {
Objects.requireNonNull(maxLength, "Max length cannot be null, -1 for no limit");
this.maxLength.setValue(maxLength);
}
#Override
public void replaceText(int start, int end, String insertedText) {
if (this.getMaxLength() <= 0) {
// Default behavior, in case of no max length
super.replaceText(start, end, insertedText);
}
else {
// Get the text in the textfield, before the user enters something
String currentText = this.getText() == null ? "" : this.getText();
// Compute the text that should normally be in the textfield now
String finalText = currentText.substring(0, start) + insertedText + currentText.substring(end);
// If the max length is not excedeed
int numberOfexceedingCharacters = finalText.length() - this.getMaxLength();
if (numberOfexceedingCharacters <= 0) {
// Normal behavior
super.replaceText(start, end, insertedText);
}
else {
// Otherwise, cut the the text that was going to be inserted
String cutInsertedText = insertedText.substring(
0,
insertedText.length() - numberOfexceedingCharacters
);
// And replace this text
super.replaceText(start, end, cutInsertedText);
}
}
}
}
Tested with JavaFX 8 and Java 8u45
This is a very simple solution that seems to work for me.
textfield.setOnKeyTyped(event ->{
int maxCharacters = 5;
if(tfInput.getText().length() > maxCharacters) event.consume();
});
I use a simple call to ChangeListener, where I test the condition to perform stops.
textFild.addListener((observable, oldValue, newValue) -> {
if (newValue.length() == MAX_SIZE) {
textField.setText(oldValue);
}
});
This is a better way to do the job on a generic text field:
public static void addTextLimiter(final TextField tf, final int maxLength) {
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
if (tf.getText().length() > maxLength) {
String s = tf.getText().substring(0, maxLength);
tf.setText(s);
}
}
});
}
Works perfectly, except for that Undo bug.
the following 1-liner will exactly do it, wheras 5 is the limit of the TextField tf:
tf.setTextFormatter(new TextFormatter<>(c -> c.getControlNewText().matches(".{0,5}") ? c : null));
This is a solution that works well:
#FXML
void limitTextFields(KeyEvent event) {
int maxLength = 5;
TextField tf = (TextField) event.getSource();
if (tf.getText().length() > maxLength) {
tf.deletePreviousChar();
}
}