I am looking for a simple solution to making first and last line of TextArea uneditable.
As seen on the picture, I need to keep the first and last line, user can edit or input whatever he wants in the curly brackets.
I have actually come up with this simple class, but it kinda breaks when user manages to get the closing curly bracket on the second line, leaving no lines between the first and the last one, rendering the user unable to edit anything.
Thanks for all responses.
public static class ScriptArea extends TextArea {
#Override
public void replaceText(int start, int end, String text) {
String currentToStart = getText().substring(0, start);
String startToEnd = getText().substring(start);
if (currentToStart.contains("\n") && startToEnd.contains("\n")) {
super.replaceText(start, end, text.equals("\n")?"\n\t":text);
}
}
}
Use a TextFormatter with a filter that vetoes any changes that don't leave the text in the correct form:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.stage.Stage;
public class TextAreaFixedStartEndLines extends Application {
private final String start = "function collideWith(mobj, tar, dir) {\n";
private final String end = "\n}";
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setTextFormatter(new TextFormatter<String>((Change c) -> {
String proposed = c.getControlNewText();
if (proposed.startsWith(start) && proposed.endsWith(end)) {
return c;
} else {
return null ;
}
}));
textArea.setText(start+"\n"+end);
primaryStage.setScene(new Scene(textArea, 600, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I'm using some TextFields in JavaFX, and I want to limit their area of shown text. I already limit their maximum number of characters, but in some cases, the maximum char number typed is larger than the Textfield's width. As you see, the text is overlapping a custom Erase-text button I added.
I want to know if I can move the text's right margin a little bit to the left (without changing the TextField's properties - size, coordinates), so the button and the text won't overlap anymore.
I already tried margins, padding, but they don't do what I need.
How do I limit my TextField's maximum length (from stackoverflow):
public class LimitedJFXTextField extends JFXTextField {
private final IntegerProperty maxLength;
public LimitedJFXTextField() {
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);
}
}
}
}
Here is a solution using ControlsFX.
import java.io.IOException;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.controlsfx.control.textfield.CustomTextField;
/**
* JavaFX App
*/
public class App extends Application
{
private static Scene scene;
#Override
public void start(Stage stage) throws IOException
{
CustomTextField customTextField = new CustomTextField();
customTextField.setText("Hello World!");
Label labelX = new Label("x");
labelX.setTextFill(Color.RED);
customTextField.setRight(labelX);
customTextField.setMaxWidth(200);
scene = new Scene(new StackPane(customTextField), 500, 500);//loadFXML("primary"), 640, 480);
stage.setScene(scene);
stage.show();
}
}
I have never used JFXTextField, so take this answer with a grain of salt.
However, since JFXTextField extends TextField, you should be able to change properties of TextField. One of these is called padding:
So using just the "normal" TextField, you can use this:
public class ExampleApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
var textField = new TextField();
textField.setPadding(new Insets(0, 50, 0, 0)); // This adds 50px right padding
textField.setText("Hello World!");
var label = new Label("x");
var pane = new StackPane(textField, label);
StackPane.setAlignment(label, Pos.CENTER_RIGHT);
var scene = new Scene(pane, 500, 500);
stage.setScene(scene);
stage.show();
}
}
Or if you prefer FXML, use this:
<TextField>
<padding>
<Insets right="50"/>
</padding>
</TextField>
The StackPane as well as the Label are just for the sake of demonstrating some overlaying item, same as in Sedricks answer. The important part which actually restricts the text inside of the textfield is the padding.
I'm new to JavaFX. I try to program a simple GUI but I face those problem whom might be related.
I set files with a File Chooser and want to do pretty basic operations:
save the last folder used
write the name of the selected file in the VBox
Here's my code (which compiles):
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static Stage primaryStageS;
public static Scene mainScene;
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene((new Test(primaryStage).getScene()));
primaryStageS = primaryStage;
primaryStage.setTitle("Parcel Manager Main Page");
primaryStage.initStyle(StageStyle.DECORATED);
VBox main = new VBox(new Label("Test program"));
mainScene = new Scene(main, 800, 600);
primaryStage.setScene((new Test(primaryStage)).getScene());
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
public class Object1 {
String name;
public Object1(File f) throws IOException {
name = f.getName();
}
public String getName() {
return name;
}
}
public class Test {
Object1 collec;
String collecName;
File lastFolder;
Pane rootGroup;
public Test(Stage stage) {
setButtons(stage);
}
public void setButtons(Stage stageGoal) {
VBox vbox = new VBox();
Button b = getButton(stageGoal);
vbox.getChildren().addAll(b, new Label(getCollecName() == null ? "no name" : collecName));
final GridPane inputGridPane = new GridPane();
GridPane.setConstraints(vbox, 0, 0);
inputGridPane.getChildren().addAll(vbox);
rootGroup = new VBox(12);
rootGroup.getChildren().addAll(inputGridPane);
rootGroup.setPadding(new Insets(12, 12, 12, 12));
}
public Button getButton(Stage stage) {
FileChooser fileChooserParcel = new FileChooser();
fileChooserParcel.setInitialDirectory(getLastFolder());
Button button = new Button("Select a File");
button.setOnAction(e -> {
File f = fileChooserParcel.showOpenDialog(stage);
if (f != null) {
try {
collec = new Object1(f);
} catch (IOException e1) {
e1.printStackTrace();
}
setLastFolder(f.getParentFile());
setCollecName(collec);
setButtons(stage); // tried to reload every buttons - doesn't work
stage.setWidth(stage.getWidth() + 0.0001); // found this dirty hack but doesn't work
}
});
return button;
}
public void setCollecName(Object1 o1) {
collecName = o1.getName();
}
public String getCollecName() {
return collecName;
}
public File getLastFolder() {
return lastFolder;
}
public void setLastFolder(File folder) {
System.out.println("set last folder: " + folder);
lastFolder = folder;
}
private Scene getScene() {
return new Scene(rootGroup, 800, 600);
}
}
}
I cannot refresh the Nodes, either to set a current Initial Directory or display the collecName on the VBox. I tried to regenerate them with reloading of objects or resizing the window, but nothing works. When I print the variables on console, I see that they changes. But haven't found any refresh method for any of my objects.
I bet it's a design program issue, but I have been moving things around for the last week and doesn't know how to fix this.
Thanks !
You are only setting the initial directory once. I guess you want to set it every time you click the button. So move that line of code to inside the handler.
Compare the below getButton() method with yours.
public Button getButton(Stage stage) {
FileChooser fileChooserParcel = new FileChooser();
Button button = new Button("Select a File");
button.setOnAction(e -> {
fileChooserParcel.setInitialDirectory(getLastFolder()); // CHANGE HERE.
File f = fileChooserParcel.showOpenDialog(stage);
if (f != null) {
try {
collec = new Object1(f);
} catch (IOException e1) {
e1.printStackTrace();
}
setLastFolder(f.getParentFile());
setCollecName(collec);
setButtons(stage); // tried to reload every buttons - doesn't work
stage.setWidth(stage.getWidth() + 0.0001); // found this dirty hack but doesn't work
}
});
return button;
}
I have a JavaFX list using flowless project. But I get a strange border (I have not enabled any border and forced the border to have width=0px) that is present and has a gradient effect:
When I set the cell background-inset to -1, the border is gone, so I believe this issue is related to the background. Although this is not a solution because if I enable the border (which I want to), the gradient effect is still present.
Does anyone knows how I can get rid of this border?
I use JavaFX 8 with latest 201 release.
EDIT: I made the following example show casing the issue:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
import org.fxmisc.flowless.Cell;
import org.fxmisc.flowless.VirtualFlow;
import org.fxmisc.flowless.VirtualizedScrollPane;
public class Main extends Application {
private ObservableList<Line> logLines;
private VirtualFlow<Line, Cell<Line, LineCell>> listView;
private VirtualizedScrollPane<VirtualFlow> listScrollPane;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
logLines = FXCollections.observableArrayList();
for(int i=1; i < 50; i++) {
logLines.add(new Line("Line "+i));
}
listView = VirtualFlow.createVertical(logLines, (line) -> Cell.wrapNode(new LineCell(line)));
listScrollPane = new VirtualizedScrollPane<>(listView);
Scene scene = new Scene(listScrollPane, 200, 600, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
private class Line {
private String text;
public Line(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
private class LineCell extends TextFlow {
public LineCell(Line line) {
super();
Text t = new Text(line.getText());
t.setFill(Color.BLACK);
super.setStyle("-fx-background-color: green;");
super.getChildren().add(t);
}
}
}
Thanks!
I have found a solution to this issue. Instead of LineCell extending TextFlow, it now extends StackPane and I add the TextFlow to the Stackpane.
private class LineCell extends StackPane {
public LineCell(Line line) {
super();
Text t = new Text(line.getText());
t.setFill(Color.BLACK);
TextFlow tf = new TextFlow(t);
super.setStyle("-fx-background-color: green;");
super.getChildren().add(tf);
}
}
I'm using JavaFX to create a Java application which is able to apply a TranslateTransition to a generic node and recall it continuously.
I retrieved a simple right arrow from this url https://www.google.it/search?q=arrow.png&espv=2&source=lnms&tbm=isch&sa=X&ved=0ahUKEwiGheeJvYrTAhWMB5oKHU3-DxgQ_AUIBigB&biw=1600&bih=764#imgrc=rH0TbMkQY2kUaM:
and used it to create the node to translate.
This is my AnimatedNode class:
package application.model.utils.addon;
import javafx.animation.TranslateTransition;
import javafx.scene.Node;
import javafx.util.Duration;
public class AnimatedNode {
private Node node;
private double positionY;
private TranslateTransition translateTransition;
private boolean animated;
private int reverse = 1;
public AnimatedNode(Node node, double animationTime) {
setPositionY(0.0);
setNode(node);
setTranslateTransition(animationTime);
}
public void play() {
if(translateTransition != null && !isAnimated()) {
setAnimated(true);
new Thread() {
#Override
public void run() {
while(isAnimated()) {
translateTransition.setToY(positionY + 50 * reverse);
translateTransition.play();
reverse = -reverse;
setPositionY(translateTransition.getToY());
}
}
}.start();
}
}
public void stop() {
setAnimated(false);
}
public Node getNode() {
return node;
}
private void setNode(Node node) {
this.node = node;
}
public TranslateTransition getTranslateTransition() {
return translateTransition;
}
private void setTranslateTransition(double animationTime) {
translateTransition = new TranslateTransition();
if(node != null) {
translateTransition.setDuration(Duration.seconds(animationTime));
translateTransition.setNode(node);
}
}
public double getPositionY() {
return positionY;
}
private void setPositionY(double positionY) {
this.positionY = positionY;
}
public boolean isAnimated() {
return animated;
}
private void setAnimated(boolean animated) {
this.animated = animated;
}
}
and this is the Application class
package test;
import application.model.utils.addon.AnimatedNode;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Test extends Application {
private final String TITLE = "Test application";
private final double WIDTH = 600;
private final double HEIGHT = 400;
private final String ARROW_PATH = "file:resources/png/arrow.png";
private BorderPane rootPane;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle(TITLE);
rootPane = new BorderPane();
rootPane.setPrefSize(WIDTH, HEIGHT);
Image image = new Image(ARROW_PATH);
ImageView imageView = new ImageView(image);
imageView.setFitWidth(WIDTH);
imageView.setFitHeight(HEIGHT);
imageView.setPreserveRatio(true);
AnimatedNode animatedNode = new AnimatedNode(imageView, 0.7);
Pane pane = new Pane();
pane.getChildren().add(animatedNode.getNode());
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
if(arg0.getButton().equals(MouseButton.PRIMARY))
animatedNode.play();
if(arg0.getButton().equals(MouseButton.SECONDARY))
animatedNode.stop();
}
});
rootPane.setCenter(pane);
Scene scene = new Scene(rootPane, WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The node is added to a generic pane; the pane has a MouseListener. I can start the TranslateTransition by using the primary button of the mouse and stop it with the secondary one.
I used a Thread in the play() method of AnimatedNode but I still have a continuous delay in the transition.
Is this the best way to perform the transition? Can I improve my code?
Thanks a lot for your support.
Sample
This is a simplified example which demonstrates a continuous animation started and stopped by left and right mouse clicks.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BouncingCat extends Application {
private static final double WIDTH = 100;
private static final double HEIGHT = 100;
private final String ARROW_PATH =
"http://icons.iconarchive.com/icons/iconka/meow-2/64/cat-rascal-icon.png";
// image source: http://www.iconka.com
#Override
public void start(Stage stage) {
Image image = new Image(ARROW_PATH);
ImageView imageView = new ImageView(image);
TranslateTransition animation = new TranslateTransition(
Duration.seconds(0.7), imageView
);
animation.setCycleCount(Animation.INDEFINITE);
animation.setFromY(0);
animation.setToY(50);
animation.setAutoReverse(true);
Pane pane = new Pane(imageView);
Scene scene = new Scene(pane, WIDTH, HEIGHT);
scene.setOnMouseClicked(e -> {
switch (e.getButton()) {
case PRIMARY:
animation.play();
break;
case SECONDARY:
animation.pause();
break;
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Advice
You don't need a Thread when you have a Transition. JavaFX will render updated transition frames automatically each pulse.
I don't advise keeping track of properties in a class, when those same values are already represented in the underlying tools you use.
For example:
replace int reverse = 1; with transition.setAutoReverse(true) or transition.setRate(1) (or -1).
replace animated with transition.getStatus().
instead of double positionY, set the toY of the transition.
I wouldn't advise calling your class AnimatedNode unless it extended node, otherwise it is confusing, instead call it something like AnimationControl.
import javafx.animation.TranslateTransition;
import javafx.scene.Node;
import javafx.util.Duration;
public class AnimationControl {
private final TranslateTransition translateTransition;
public AnimationControl(Duration duration, Node node) {
translateTransition = new TranslateTransition(duration, node);
}
public TranslateTransition getTranslateTransition() {
return translateTransition;
}
}
You only need to encapsulate the node and the transition in the AnimationControl and not other fields unless you need further functionality not apparent in your question and not already provided by Node or Transition. If you have that extra functionality then you can enhance the AnimationControl class above to add it.
Exposing the node and the translate transition is enough, as if the user wants to manage the animation, such as starting and stopping it, then the user can just get it from the AnimationControl class. Depending on your use case, the entire AnimationControl class might be unnecessary as you might not need the encapsulation it provides and might instead prefer to just work directly with the node and the transition (as demoed in the sample).
My goal here is to have some animation on a node (such as a fade transition) that serves as a temporary notice that something is happening. I want the animation completely gone, like it never happened when that something has ended.
The code snipped below is an example of the problem I'm having. In the current state, when the button is hit to stop the process the button just stays at it's current opacity. If the commented line is uncommented, the button no longer stays at it's current opacity but updates to look correct. My problem then is that when the button is hit again, the CSS opacity for the default stylesheet (Modena.css for JavaFX 8) is no longer taking effect.
Is there something I'm doing wrong, or is there a better way altogether?
package gui.control.custom;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Stage stage = new Stage();
HBox box = new HBox();
streamButton = new Button("Start");
streamButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (started) {
stopProcess();
} else {
startProcess();
}
}
});
box.getChildren().add(streamButton);
stage.setScene(new Scene(box));
stage.show();
}
FadeTransition ft;
Button streamButton;
boolean started = false;
private void startProcess() {
streamButton.setDisable(true);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
started = true;
streamButton.setText("Stop");
streamButton.setDisable(false);
startButtonAnim();
});
}
}.start();
}
private void stopProcess() {
streamButton.setText("Start");
stopButtonAnim();
started = false;
}
private void startButtonAnim() {
ft = new FadeTransition(Duration.millis(500), streamButton);
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(Animation.INDEFINITE);
ft.setAutoReverse(true);
ft.play();
}
private void stopButtonAnim() {
ft.stop();
//streamButton.setOpacity(1);
}
public static void main(String[] args) {
launch();
}
}
I think the best solution is to use jumpTo(Duration duration) right before you stop the Animation. Setting the duration to Duration.ZERO.
Circle circle2 = new Circle(250, 120, 80);
circle2.setFill(Color.RED);
circle2.setStroke(Color.BLACK);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(5000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(1000);
fade.setAutoReverse(true);
fade.setNode(circle2);
fade.play();
Button btnStop = new Button("Stop");
btnStop.setOnAction((event) -> {
fade.jumpTo(Duration.ZERO);
fade.stop();
});
Another idea:
I have found this method in javadoc: getCurrentRate(),
which should give you negative result on reversing, so the code would look like this:
private void stopButtonAnim() {
while(ft.getCurrentRate>=0); //waiting till animation goes (skips if already reversing)
while(ft.getCurrentRate<=0); //and till reverse
ft.stop(); //then stop
streamButton.setOpacity(1); //make it 100% ;)
}
Maybe you have to add Thread.sleep(int) to while cycle
I would try this insetad of simply stop(); this line
setOnFinished(e->tryToStop());
And create this method as:
public void tryToStop(){
if(!started)
fm.stop();
}
stopProcess() method changes the started variable, so it will stop in this two cases:
if it is finished
AND
if it is reqested to stop
Not tested, just an idea