I am trying to set the focus on the JTextPane so that when the window opens it can immediately be edited with the keyboard. However, nothing I've done seems to give the JTextPane focus on startup. Is this just an issue with using JavaFX with Swing?
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingNode;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javax.swing.*;
public class TestDialog {
#FXML
private ListView listView;
#FXML
private SwingNode node;
private ObservableList<Integer> obsList;
#FXML
public void initialize(){
JTextPane pane = new JTextPane();
SwingUtilities.invokeLater(() -> pane.requestFocusInWindow());
pane.setText("This issue is not reproducible in JDK 8 early-access build (8u172) which is yet to be released.");
node.setContent(pane);
obsList = FXCollections.observableArrayList();
for(int x = 0; x < 12; x++){
obsList.add(x);
}
listView.setItems(obsList);
node.setFocusTraversable(true);
node.requestFocus();
pane.requestFocus();
pane.grabFocus();
}
#FXML
private void removeItem(ActionEvent event) {
obsList.remove(0);
}
}
It's working now thanks to a solution from BWC_semaJ. Rather than using:
SwingUtilities.invokeLater(() -> pane.requestFocusInWindow());
I should have used:
Platform.runLater(() -> {swingNode.requestFocus();}); //Use this instead
I don't know if this helps but below is the demo program I made based on the sample code and for some reason it works for me:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingNode;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.*;
public class Test extends Application {
#FXML
private ListView listView;
#FXML
private SwingNode node;
private ObservableList<Integer> obsList;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage arg0) throws Exception {
initialize(arg0);
}
#FXML
public void initialize(Stage stage){
JTextPane pane = new JTextPane();
// The program runs the same no matter if one of the below two lines are used or if neither are used
//SwingUtilities.invokeLater(() -> pane.requestFocusInWindow());
//Platform.runLater(() -> {node.requestFocus();});
pane.setText("This issue is not reproducible in JDK 8 early-access build (8u172) which is yet to be released.");
node = new SwingNode();
node.setContent(pane);
obsList = FXCollections.observableArrayList();
for(int x = 0; x < 12; x++){
obsList.add(x);
}
listView = new ListView();
listView.setItems(obsList);
node.setFocusTraversable(true);
node.requestFocus();
pane.requestFocus();
pane.grabFocus();
StackPane root = new StackPane();
root.getChildren().add(node);
Scene scene = new Scene(root, 500, 500);
stage.setTitle("Test");
stage.setScene(scene);
stage.show();
}
#FXML
private void removeItem(ActionEvent event) {
obsList.remove(0);
}
}
Related
I can't find a way to add a simple "String" row to my tableView.
In fact i can add a row but its content is not visible ...
Here is my code:
#FXML
private TableView<String> table;
#FXML
private TableColumn<String, String> table2;
public ObservableList<String> getLanes()
{
ObservableList<String> lanes=FXCollections.observableArrayList();
lanes.add("TEST");
return lanes;
}
Then:
table.setItems(getLanes()); //Not working
and
table.getItems().add("TEST"); //Not working
But without success.
I read that and that as well as other documentations but it did not help me to do it in this simple way.
EDIT:
Adding this line solved my problem:
table2.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue()));
Here is a simple application where we are trying to load a single value into a TableView column. It also shows how to set a cellValueFactory() on a table column.
tableColumn.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue()));
MCVE
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TableView<String> tableView = new TableView<>();
TableColumn<String, String> tableColumn = new TableColumn<>("Name");
tableColumn.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue()));
tableView.getColumns().add(tableColumn);
ObservableList<String> items = FXCollections.observableArrayList("Itachi");
tableView.setItems(items);
VBox root = new VBox(tableView);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 275);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I just want to create copiable label in JavaFX.
I have tried to create TextField that have no background, have no focus border and default background color, but I have no success.
I have found a lot of questions how to remove focus background from control but all of that looks like "hacks".
Is there are any standard solution to implement copyable text?
You can create a TextField without the border and background color with css:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CopyableLabel extends Application {
#Override
public void start(Stage primaryStage) {
TextField copyable = new TextField("Copy this");
copyable.setEditable(false);
copyable.getStyleClass().add("copyable-label");
TextField tf2 = new TextField();
VBox root = new VBox();
root.getChildren().addAll(copyable, tf2);
Scene scene = new Scene(root, 250, 150);
scene.getStylesheets().add(getClass().getResource("copyable-text.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and
copyable-text.css:
.copyable-label, .copyable-label:focused {
-fx-background-color: transparent ;
-fx-background-insets: 0px ;
}
This is the solution I used, where there is a small button besides the label to be able to copy the text:
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import java.util.Locale;
public class CopiableLabel extends Label
{
public CopiableLabel()
{
addCopyButton();
}
public CopiableLabel(String text)
{
super(text);
addCopyButton();
}
public CopiableLabel(String text, Node graphic)
{
super(text, graphic);
}
private void addCopyButton()
{
Button button = new Button();
button.visibleProperty().bind(textProperty().isEmpty().not());
button.managedProperty().bind(textProperty().isEmpty().not());
button.setFocusTraversable(false);
button.setPadding(new Insets(0.0, 4.0, 0.0, 4.0));
button.setOnAction(actionEvent -> AppUtils.copyToClipboard(getText()));
Glyph clipboardIcon = AppUtils.createFontAwesomeIcon(FontAwesome.Glyph.CLIPBOARD);
clipboardIcon.setFontSize(8.0);
button.setGraphic(clipboardIcon);
setGraphic(button);
setContentDisplay(ContentDisplay.RIGHT);
}
}
I'm making an applet using JavaFX 8 (via a JFXPanel) and was planning on using a Popup to display an area with a TextField in it. This works as expected, until the application loses focus. After that, I'm unable to make the TextField regain focus so you can't type in it any more. I've tried calling requestFocus() on the TextField but that didn't seem to do anything. The problem can be seen in the simple example below:
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Popup;
import javax.swing.*;
public class FXApplet extends JApplet {
protected Scene scene;
protected Group root;
#Override
public final void init() {
SwingUtilities.invokeLater(() -> initSwing());
}
private void initSwing() {
JFXPanel fxPanel = new JFXPanel();
add(fxPanel);
Platform.runLater(() -> {
initFX(fxPanel);
initApplet();
});
}
private void initFX(JFXPanel fxPanel) {
root = new Group();
scene = new Scene(root);
fxPanel.setScene(scene);
}
public void initApplet() {
Popup popup = new Popup();
popup.setAutoHide(false);
popup.getContent().add(new TextField());
popup.show(scene.getWindow());
}
}
If you create a binding between a JavaFX TextField and a property, then this binding is invalidated on every keystroke, which causes a change to the text.
If you have a chain of bindings the default behavior could cause problems, because in the middle of the editing values may be not valid.
Ok, I know I could create an uni-directional binding from the property to the textfield and register a change listener to get informed when the cursor leaves the field and update the property manually if necessary.
Is there an easy, elegant way to change this behavior so that the binding is only invalidated when the editing is complete, e.g. when the cursor leaves the field?
Thanks
I think you've pretty much described the only way to do it. Here's about the cleanest way I can see to implement it (using Java 8, though it's easy enough to convert the lambdas back to be JavaFX 2.2 compatible if you need):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CommitBoundTextField extends Application {
#Override
public void start(Stage primaryStage) {
TextField tf1 = new TextField();
createCommitBinding(tf1).addListener((obs, oldText, newText) ->
System.out.printf("Text 1 changed from \"%s\" to \"%s\"%n", oldText, newText));
TextField tf2 = new TextField();
createCommitBinding(tf2).addListener((obs, oldText, newText) ->
System.out.printf("Text 2 changed from \"%s\" to \"%s\"%n", oldText, newText));
VBox root = new VBox(5, tf1, tf2);
Scene scene = new Scene(root, 250, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
private StringBinding createCommitBinding(TextField textField) {
StringBinding binding = Bindings.createStringBinding(() -> textField.getText());
textField.addEventHandler(ActionEvent.ACTION, evt -> binding.invalidate());
textField.focusedProperty().addListener((obs, wasFocused, isFocused)-> {
if (! isFocused) binding.invalidate();
});
return binding ;
}
public static void main(String[] args) {
launch(args);
}
}
I realize that I am a little late with a response, but thought this might be useful to someone.
When using TextFields, I often attach a TextFormatter to help validate entries. You can attach a listener to the formatters' valueProperty. That property is updated when the text is committed, rather than on every keystroke.
Here's an example of what I am talking about using a TextField specialized for integer inputs. When you make edits in the text field, the changes will be reflected in the Label when you tap Enter, lose focus by clicking the button, switch to a different window, and so on.
import javafx.application.Application;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
class IntTextField extends TextField {
private final IntegerProperty value;
TextFormatter<Integer> formatter;
public double getValue() {
return value.getValue();
}
public void setValue(int newValue) {
value.setValue(newValue);
}
public IntegerProperty valueProperty() {
return value;
}
public StringBinding getStringBinding () {
return value.asString();
}
IntTextField(int initValue) {
value = new SimpleIntegerProperty(initValue);
setText(initValue + "");
formatter = new TextFormatter(new IntegerStringConverter(), initValue);
formatter.valueProperty().addListener((ObservableValue<? extends Integer> obs,
Integer oldValue, Integer newValue) -> value.setValue(newValue));
setTextFormatter(formatter);
}
IntTextField() {
this(0);
}
}
public class TFBindingDemo extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TFBindingDemo");
IntTextField intTextField = new IntTextField(12345);
intTextField.setMaxWidth(150);
Label label = new Label("Type in the TextField");
label.textProperty().bind(intTextField.getStringBinding());
Button removeFocusButton = new Button("Click Here to Remove Focus");
VBox root = new VBox(20, intTextField, label, removeFocusButton);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
Scene scene = new Scene(root, 325, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I don't know if anyone could understand my problem from the title, but here's more specific description. I have class, in which I created a FlowPane, where I added objects of another class(images packed inside VBoxes). Each VBox have ContextMenu, where is MenuItem "Remove File". My problem is, how to remove this object while beeing inside the VBox class. Here is a little part of my code:
//removed, entire code is below after edit
The code where I'm accessing my CustomPane (my class of FlowPane, with specified attributes) works, because I can remove object if I'm doing it by their indexes, but when I remove one of them, other's indexes changes, so I'm looking for another solution. I need to specifically remove the object of the class in the code.
Okay so here is so called sscce, which of I had no idea, since now:
package sscce;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Sscce extends Application {
#Override
public void start(Stage primaryStage) {
CustomPane root = new CustomPane();
root.setPadding(new Insets(20));
root.setHgap(10);
root.setVgap(10);
for (int i = 0; i < 10; i++) {
RectangleBox recB = new RectangleBox();
root.getChildren().add(recB);
}
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class RectangleBox extends VBox {
static int index = 0;
public RectangleBox() {
Rectangle rec = new Rectangle(150, 150);
rec.setFill(Color.GREEN);
StackPane sp = new StackPane();
sp.getChildren().add(rec);
Label label = new Label(Integer.toString(index));
index++;
sp.getChildren().add(label);
getChildren().add(sp);
final ContextMenu cm = new ContextMenu();
MenuItem removeRec = new MenuItem("Remove Rectangle");
removeRec.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
((CustomPane) getParent()).getPane().getChildren().remove(0); //here is the problem, I want this line to remove the rectangle I clicked on(now it's removing first element in the pane).
}
});
cm.getItems().add(removeRec);
createContextMenuEvent(cm, rec);
}
private void createContextMenuEvent(final ContextMenu cm, final Rectangle rec) {
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.SECONDARY) {
cm.show(rec, t.getScreenX(), t.getScreenY());
}
}
});
}
}
class CustomPane extends FlowPane {
public CustomPane() {
//setAlignment(Pos.CENTER);
setHgap(25);
setVgap(25);
setPadding(new Insets(20));
}
public CustomPane getPane() {
return this;
}
}
It should work after copy/paste this entire code to java project. So I removed everything that is not neccessary, and I have replaced images to rectangles, now this program looks kind of stupid;p
I added comment to a line I have problem with. I hope now it's a lot clearer than before.
try this:
((CustomPane) RectangleBox.this.getParent()).getChildren().remove(RectangleBox.this);
hope it helps.