Can I force the TextArea control to automatic expanding the height?
In the following case, I would like to see the scrollbar at ScrollPane control, not at TextArea control.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<ScrollPane fitToHeight="true" fitToWidth="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.91" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.Controller">
<VBox style="-fx-background-color: bisque">
<TextField/>
<TextArea VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets top="20.0"/>
</VBox.margin>
</TextArea>
</VBox>
</ScrollPane>
For now the only solution that is close to your problem is given by #Uluk Biy, that i found here, what i did is just fit his logic, and hide the ScrollBars. The only problem is the size of the TextArea which is binded to that of the Text and so it starts with a minimum height at the beginning of the edition, here is the complete code :
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Launcher extends Application{
private Pane root = new Pane();
private Scene scene;
private ScrollPane scroller;
private Pane content = new Pane();
private TextField textF = new TextField();
private TextArea textA = new TextArea();
private Text textHolder = new Text();
private double oldHeight = 0;
#Override
public void start(Stage stage) throws Exception {
root.getChildren().addAll(yourSP());
scene = new Scene(root,300,316);
stage.setScene(scene);
stage.show();
}
private ScrollPane yourSP(){
content.setMinSize(300, 300);
textF.setPrefSize(260, 40);
textF.setLayoutX(20);
textF.setLayoutY(20);
textA.setPrefSize(260, 200);
textA.setLayoutX(20);
textA.setLayoutY(80);
textA.getStylesheets().add(getClass().getResource("texta.css").toExternalForm());
content.getChildren().addAll(textA,textF);
/*************************#Uluk Biy Code**************************/
textA.setWrapText(true);
textHolder.textProperty().bind(textA.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
oldHeight = newValue.getHeight();
textA.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20);
System.out.println(textHolder.getLayoutBounds().getHeight());
}
}
});
/****************************************************************/
scroller = new ScrollPane(content);
scroller.setHbarPolicy(ScrollBarPolicy.NEVER);
scroller.setPrefSize(300, 316);
return scroller;
}
public static void main(String[] args) {
launch(args);
}
}
Of course the code can be adapted to fxml format, I just have not had enough time to do it, and here is the style of the TextArea:
.text-area > .scroll-pane{
-fx-hbar-policy:never;
-fx-vbar-policy:never;
}
good luck for the continuation !
I'm not saying that I like the solution. It's hacky as hell but actually works a lot better then the one with textHolder - is more stable. It's written in Kotlin with TornadoFX. For Java it should work the same, but probably with a lot more lines of code ;)
The principle is basically the same, but instead of dedicated textHolder object we are using the actual Text node inside the TextArea.
class ExpandableTextArea : TextArea() {
init {
addClass("expandable")
isWrapText = true
children.onChange { a ->
val scrollPane = a.list.first() as ScrollPane
val contentView = scrollPane.content as Region
contentView.childrenUnmodifiable.onChange { b ->
b.next()
if (b.list.size == 2) {
val group = b.list[1] as Group
group.children.onChange { c ->
val text = c.list.first() as Text
text.layoutBoundsProperty().onChange {
if (it != null) {
val targetHeight = it.height + font.size
prefHeight = targetHeight
minHeight = targetHeight
}
}
}
}
}
}
}
}
Of course you still need the same CSS to turn the scrollbars off:
.text-area > .scroll-pane{
-fx-hbar-policy:never;
-fx-vbar-policy:never;
}
Please note that here I'm also setting the minHeight because I want this node to expand "brutally" so it never has to scroll. You can remove that and leave setting the prefHeight but if there is not enough space the node will stop growing and will be scrollable by mouse but without the scrollbars which can be confusing.
BE WARNED: if for some forsaken reason the TextArea children tree structure changes this will blow up.
Related
When you create a rounded VBox in JavaFX, and the background color for the VBox is black, assume the rounded corners after filling the button is white, How can I fill this region with another color (Assume I want it to be totally transparent).
I assume, the questioneer wants to finally hava a floating round VBox.
This can probably be achieved in many ways, without thinking to much about it, I would rather make use of a clipping Node instead of a pure CSS approach (which should be doable as well).
As he already wrote, you would also need to make the Scenes fill Color.TRANSPARENT (and probably the Stage as well).
My approach looks like this. First the FXML file:
<?import java.lang.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:id="root" fx:controller="application.ClipExampleController" style="-fx-background-color: steelblue;">
<center>
<VBox fx:id="vbox" alignment="CENTER" spacing="5" maxWidth="150">
<Label text="Bla"/>
<TextField promptText="Blub"/>
<Button text="Do it"/>
</VBox>
</center>
</BorderPane>
Then the Controller class:
package application;
import javafx.beans.binding.DoubleBinding;
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class ClipExampleController {
#FXML
private BorderPane root;
#FXML
private VBox vbox;
#FXML
protected void initialize() {
Circle clip = new Circle();
clip.radiusProperty().bind(new DoubleBinding() {
{
bind(vbox.widthProperty());
}
#Override
protected double computeValue() {
return vbox.getWidth() / 2 + 25;
}
});
clip.centerXProperty().bind(new DoubleBinding() {
{
bind(root.widthProperty());
}
#Override
protected double computeValue() {
return root.getWidth() / 2;
}
});
clip.centerYProperty().bind(new DoubleBinding() {
{
bind(root.heightProperty());
}
#Override
protected double computeValue() {
return root.getHeight() / 2;
}
});
root.setClip(clip);
}
}
and finally the glue code - the main Application:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClipExample.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 300, 300);
scene.setFill(Color.TRANSPARENT);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So. Assuming, that I understand you correctly, this would be my solution.
assume the rounded corners after filling the button is white, How can I fill this region with another color
Are you talking about the "Stroke?" There is "Fill" (Inside) and "Stroke" (outline, outer-rim, edge, etc)
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Shape.html
The Shape class provides definitions of common properties for objects that represent some form of geometric shape.
These properties include:
The Paint to be applied to the fillable interior of the shape (see setFill).
The Paint to be applied to stroke the outline of the shape (see setStroke).
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Shape.html#setStroke-javafx.scene.paint.Paint- (in case you didn't click setStroke above)
public final void setStroke(Paint value)
Sets the value of the property stroke.
Property description:
Defines parameters of a stroke that is drawn around the outline of a Shape using the settings of the specified Paint. The default value is null for all shapes except Line, Polyline, and Path. The default value is Color.BLACK for those shapes.
From here you fill it with a "Paint" Object which is a base class for many different Classes such as "Color"
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/paint/Color.html#TRANSPARENT
public static final Color TRANSPARENT
A fully transparent color with an ARGB value of #00000000.
So... To sum this up, you are going to want to do.
button.setStroke(Color.TRANSPARENT);
If this, of course, is what you're asking, since it's hard to tell...; However, it seems what I have provided is what you ask, but if not I'll try again :).
In order to set the container's background of the VBox to be transparent, then you need to set the fill property of the scene that contains the VBox to TRANSPARENT COLOR, the following piece of code clarifies that:
This is the vbox style:
.vbox
{
-fx-background-color: black;
-fx-background-radius: 300%;
-fx-alignment:center;
}
if you applied the above style to a vbox with width and height = 200 you will get a circle with black background while the rounded corners filled with white. To make this white corners transparent, you need to add this piece of code:
loader.setLocation(MainApp.class.getResource("view/Test.fxml"));
VBox page = (VBox) loader.load();
Stage testStage = new Stage();
Scene scene = new Scene(page);
scene.setFill(Color.TRANSPARENT);
testStage.setScene(scene);
So, I am new to JavaFX and as part of a project I am attempting to use ImageView to display some images. Before adding to my actual project, I set up the following to be sure I understood how to use ImageViews.
My Controller:
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class TestController {
#FXML
ImageView imageView;
Image image;
public void start(Stage stage){
/*
* THIS WORKS!
*
* image = new Image("file:/C://Users//Owner//Pictures//MyProjectPhotos/picture.jpg");
*/
//BUT THIS DOESN'T :(
image = new Image("file:/JavaFXPractice/photos/picture.jpg");
imageView.setImage(image);
imageView.setOnMouseClicked(me -> System.out.println("hello"));
}
}
The fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="application.TestController">
<children>
<VBox layoutX="156.0" layoutY="88.0" prefHeight="272.0" prefWidth="378.0">
<children>
<ImageView fx:id="imageView" fitHeight="276.0" fitWidth="378.0"
pickOnBounds="true" preserveRatio="true" />
</children>
</VBox>
</children>
</AnchorPane>
And finally this:
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/TestPage.fxml"));
AnchorPane root = (AnchorPane) loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
TestController control = loader.getController();
control.start(primaryStage);
}
public static void main(String[] args) {
launch(args);
}
}
My problem is in the TestController class. As you can see in the multi-line comment (and also the comment/line of code below it), the image displays properly when I get it from my local machine, but not when I get it from my project space. I have tried for hours now to figure this out, but every single time I try to retrieve any photo from my project space, nothing appears on the ImageView. It is not null (neither is the Image), and there are no reported errors. I have also tried searching for an answer, but no luck so far.
You can load your images like this:
image = new Image(getClass().getResource("/photos/picture.jpg").toExternalForm());
under the assumption, that "photos" is a folder at the root of your project. This folder must be on the classpath. Depending on your IDE this can be achieved in different ways. In Eclipse this can be achieved by putting this folder into a "source folder". A typical structure is
project
src
...
res
photos
picture.jpg
where both "src" and "res" are source folders.
I want to maintain single background color(black) for all panes, and for all views. i don't want write css for every view. i am using only vbox and hbox mostly. and very few table views. is there any easy way to write css once and apply to all. thank you in advance
You don't write a css for every view, you give every element the same style class.
Pane pane = new Pane();
pane.getStyleClass().add("bg-black-style");
Somewhere you need to add the stylesheet to the scene
scene.getStylesheets().add("css-file.css");
And in the css file
.bg-black-style {
-fx-background-color: black;
}
This way every thing that should look the same has it's style all in one place.
You can just use .pane in CSS class, and it will work for all the panes.
.pane{
-fx-background-color: black;
}
Same works with .button etc.
You can apply the style sheet to the entire application like this:
package hacks;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import java.net.URL;
/**
* Created by BDay on 7/10/17.<br>
* <br>
* CssStyle sets the style for the entire project
*/
public class CssStyle extends Application {
private String yourCss = "YourResource.css";
public CssStyle() {
try {
Application.setUserAgentStylesheet(getCss()); //null sets default style
} catch (NullPointerException ex) {
System.out.println(yourCss + " resource not found");
}
}
private Button button = new Button("Button Text");
private TextArea textArea = new TextArea("you text here");
private ObservableList<String> listItems = FXCollections.observableArrayList("one", "two", "three");
private ListView listView = new ListView<String>(listItems);
private FlowPane root = new FlowPane(button, textArea, listView);
private Scene scene = new Scene(root);
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(scene);
primaryStage.show();
}
private String getCss() throws NullPointerException {
ClassLoader classLoader = getClass().getClassLoader();
URL resource = classLoader.getResource(yourCss);
String asString = resource.toExternalForm(); //throws null
return asString;
}
}
So here is my situation:
I have built a GUI using the scene Builder and it works just fine: A search box and a list. When the user search a value the results are correctly displayed on the list.
What I've tried to do is to add a progress bar to be showed when the user makes a search. For that I have added a pane to be used as a wrapper and inside the pane there is the progress bar.
On the controller initialization I do a:
progressBarWrapper.setVisible(false)
which works. When I try to activate it inside the search it doesn't:
private void search() {
progressBarWrapper.setVisible(true);
String val = searchField.getText();
if (val!= null && !"".equals(val)) {
ObjectList list = service.getObjects(val);
objects.clear();
objects.addAll(list);
}
progressBarWrapper.setVisible(false);
}
what I've realized is that if I remove the bottom line where the wrapper is set to invisible again I do get my progress bar to show up, however only when I already have the results.
I guess this is some kind of blocking issue or the redraw process of the screen beeing held back while computations are beeing made, however I don't understand what is going on... can somebody shed a light on it so I can develop it further? Just explain why this behaviour happens so I can have an idea on how to solve it.
Thanks
EDIT: Here I am adding a SSCCE to exemplify
Main.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("Main.fxml"));
Parent root = loader.load();
Scene primaryScene = new Scene(root);
primaryStage.setScene(primaryScene);
primaryStage.setTitle("Test");
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
In the controller I have simulated the getting of data through a simple loop with a thread sleep inside.
MainController.java
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.Pane;
public class MainController {
#FXML
private Button startLoop;
#FXML
private ProgressBar progressBar;
#FXML
private Pane progressBarWrapper;
#FXML
private Label loopCounter;
#FXML
public void initialize() {
System.out.println("Application Started");
progressBarWrapper.setVisible(false);
}
#FXML
private void doLoop() {
progressBarWrapper.setVisible(true);
for (int i = 1; i <= 5; i++) {
progressBar.setProgress(i * 0.2);
loopCounter.setText(String.format("%s loop(s)", i));
try {
Thread.sleep(1000); // 1000 milliseconds is one second.
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
progressBarWrapper.setVisible(false);
}
}
And the View:
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<children>
<Button fx:id="startLoop" layoutX="258.0" layoutY="14.0" mnemonicParsing="false" onAction="#doLoop" prefHeight="80.0" prefWidth="84.0" text="START LOOP" />
<Label fx:id="loopCounter" layoutX="286.0" layoutY="165.0" text="0">
<font>
<Font name="System Bold" size="48.0" />
</font>
</Label>
<Pane fx:id="progressBarWrapper" layoutX="461.0" layoutY="297.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<ProgressBar fx:id="progressBar" layoutX="200.0" layoutY="191.0" prefWidth="200.0" progress="0.0">
<cursor>
<Cursor fx:constant="WAIT" />
</cursor>
</ProgressBar>
</children>
</Pane>
</children>
</AnchorPane>
All is simply under a package called "application"
During some help by the user kleopatra in the comments I came accross the following link from oracle:
https://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
Basically my problem sums up to the following:
The JavaFX scene graph, which represents the graphical user interface
of a JavaFX application, is not thread-safe and can only be accessed
and modified from the UI thread also known as the JavaFX Application
thread. Implementing long-running tasks on the JavaFX Application
thread inevitably makes an application UI unresponsive. A best
practice is to do these tasks on one or more background threads and
let the JavaFX Application thread process user events.
Hope it helps someone!
Is it possible to change the default behaviour of a JavaFX TextArea, so that pressing Tab passes the focus to the next component?
While #ItachiUchiha solution works, as he states, it depends on the layout (box in his sample).
Based on this question, you can modify the default behavior of a TextArea, regardless of the layout.
But you will need to use for this private API, which may change at any time without notice.
In this sample Tab and Shitf+Tab will have the desired behavior, while Ctrl+Tab will insert "\t" on the text area.
#Override
public void start(Stage primaryStage) {
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
if (event.getCode() == KeyCode.TAB) {
TextAreaSkin skin = (TextAreaSkin) area.getSkin();
if (skin.getBehavior() instanceof TextAreaBehavior) {
TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior();
if (event.isControlDown()) {
behavior.callAction("InsertTab");
} else if (event.isShiftDown()) {
behavior.callAction("TraversePrevious");
} else {
behavior.callAction("TraverseNext");
}
event.consume();
}
}
});
VBox root = new VBox(20, new Button("Button 1"), area, new Button("Button 2"));
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Well, you definitely can do this, but it depends on the Layout to which the TextArea is added to. I have created a simple example where a TextArea and a TextField are both added to a VBox. There is a keyEventHandler which monitors the keyPress event on the TextArea and sends the focus to the next child(if any)
import java.util.Iterator;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextAreaTabFocus extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox box = new VBox();
TextArea textArea = new TextArea();
TextField textField = new TextField();
box.getChildren().addAll(textArea, textField);
final EventHandler<KeyEvent> keyEventHandler =
keyEvent -> {
if (keyEvent.getCode() == KeyCode.TAB) {
Iterator<Node> itr = box.getChildren().iterator();
while(itr.hasNext()) {
if(itr.next() == keyEvent.getSource()) {
if(itr.hasNext()){
itr.next().requestFocus();
}
//If TextArea is the last child
else {
box.getChildren().get(0).requestFocus();
}
break;
}
}
keyEvent.consume();
}
};
textArea.setOnKeyPressed(keyEventHandler);
Scene scene = new Scene(box, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}