I'm developing a software for the comparison of different amounts of data and I want to use charts to display them in a horizontal way.
Important for me is to display just 3 charts on one side. If there are more than 3 the user shall scroll to the other charts.
It won't look exactly like that pic, I'm too lazy for css. The idea is to put all the charts in a GridPane inside a ScrollPane. Bind the charts so the width is exactly 1/3 of the TabPane width.
I used FXML since it's easier to make a scene.
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<TabPane fx:id="tabPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" tabClosingPolicy="UNAVAILABLE" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="horizcharts.FXMLDocumentController">
<tabs>
<Tab text="Main View">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<Button layoutX="181.0" layoutY="112.0" mnemonicParsing="false" onAction="#addChart" text="Add Chart" />
</children></AnchorPane>
</content>
</Tab>
<Tab text="Compare">
<content>
<ScrollPane maxWidth="1.7976931348623157E308">
<content>
<GridPane fx:id="grid" gridLinesVisible="true">
</GridPane>
</content>
</ScrollPane>
</content>
</Tab>
</tabs>
</TabPane>
FXMLDocumentController.java
package horizcharts;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Label;
import javafx.scene.control.TabPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
public class FXMLDocumentController implements Initializable {
#FXML GridPane grid;
#FXML TabPane tabPane;
private int numCharts = 0;
#FXML private void addChart(ActionEvent event) {
VBox vb = randChart(numCharts);
GridPane.setConstraints(vb, numCharts++,0);
grid.getChildren().add(vb);
}
private VBox randChart(int num){
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
BarChart<String, Number> bc = new BarChart(xAxis, yAxis);
BarChart.Series<String, Number> series = new BarChart.Series<>();
series.setName("Bar Chart "+num);
bc.getData().add(series);
for (int i = 0; i<5; i++){
series.getData().add(new BarChart.Data("cat "+i, Math.random()*10*i));
}
bc.prefWidthProperty().bind(tabPane.widthProperty().subtract(6).divide(3));
bc.prefHeightProperty().bind(tabPane.heightProperty().subtract(180));//guess heights
VBox vb = new VBox(5,new Label("Version "+num), bc, new Label("precision"), new Label("recall"));
return vb;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Regular main class HorizCharts.java.
package horizcharts;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class HorizCharts extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Related
i'm try a write a simple javafx program using the GmapsFX. this is the code that i copied from internet for learn the main rudiments of this library.
this is the file .fxml
<?import com.lynden.gmapsfx.GoogleMapView?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GoogleMapView fx:id="mapView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<Label alignment="CENTER" contentDisplay="CENTER" text="Map" AnchorPane.bottomAnchor="376.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
this is the controller:
package application;
import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.GoogleMap;
import com.lynden.gmapsfx.javascript.object.InfoWindow;
import com.lynden.gmapsfx.javascript.object.InfoWindowOptions;
import com.lynden.gmapsfx.javascript.object.LatLong;
import com.lynden.gmapsfx.javascript.object.MapOptions;
import com.lynden.gmapsfx.javascript.object.MapTypeIdEnum;
import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions;
import javafx.fxml.FXML;
public class SampleController implements MapComponentInitializedListener {
#FXML
private GoogleMapView mapView;
private GoogleMap map = null;
#Override
public void mapInitialized() {
MapOptions mapOptions = new MapOptions();
LatLong latLong = new LatLong(47.6097, -122.3331);
mapOptions.center(latLong)
.mapType(MapTypeIdEnum.ROADMAP)
.overviewMapControl(false)
.panControl(false)
.rotateControl(false)
.scaleControl(false)
.streetViewControl(false)
.zoomControl(false)
.zoom(12);
map = mapView.createMap(mapOptions);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(latLong);
Marker marker = new Marker(markerOptions);
map.addMarker(marker);
InfoWindowOptions infoWindowOptions = new InfoWindowOptions();
infoWindowOptions.content("<h2>ROMA</h2>Location di Roma<br>");
InfoWindow infoWindow = new InfoWindow(infoWindowOptions);
infoWindow.open(map, marker);
}
#FXML
public void initialize() {
assert mapView != null : "fx:id=\"mapView\" was not injected: check your FXML file 'Sample.fxml'.";
mapView.addMapInializedListener(this);
}
}
and the answer from eclipse is:
1866 [JavaFX Application Thread] INFO com.lynden.gmapsfx.GoogleMapView - Alert: Hide directions called
is there anyone that can help me, please
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.
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.
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!
I assign styleclass of ToggleButton in FXML file as follows:
<ToggleButton fx:id="Button" styleClass="defaultStyle">
Later, in my code I change the style classes as follows:
#FXML private ToggleButton Button;
Button.getStyleClass().remove("defaultStyle");
Button.getStyleClass().add("newStyle");
The CSS file is defined as:
.defaultStyle { -fx-background-color: black;}
.newStyle { -fx-background-color: red;}
EDITED:
The new style is applied when done in the Controller, but the new style is not being applied when done somewhere else. When I debug, I see the correct style-class being added & removed to the button.
Anyone got a workaround for this problem? I appreciate your help in advance.
Style class removing and adding are working as expected. I guess your problem is the ToggleButton has not been injected correctly, it should be:
#FXML private ToggleButton Button;
...
Button.getStyleClass().remove("defaultStyle");
Button.getStyleClass().add("newStyle");
in the controller class. Note the capital b of "Button" since you have defined fx:id="Button" in FXML file. Also note that you don't need to instantiate the ToggleButton Button (like new ToggleButton()) yourself.
EDIT:
Here is code example for changing the styleclass. As I said it is working as expected. Compare it with yours.
Sample.fxml:
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="somepackage.SampleController">
<stylesheets>
<String fx:value="somepackage/style.css" />
</stylesheets>
<children>
<ToggleButton layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="mybutton" styleClass="defaultStyle" />
</children>
</AnchorPane>
SampleController.java:
package somepackage;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ToggleButton;
public class SampleController implements Initializable {
#FXML
private ToggleButton mybutton;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("before :" + mybutton.getStyleClass());
mybutton.getStyleClass().remove("defaultStyle");
mybutton.getStyleClass().add("newStyle");
System.out.println("after :" + mybutton.getStyleClass());
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
MainDemo.java:
package somepackage;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
System.out.println("version: " + com.sun.javafx.runtime.VersionInfo.getRuntimeVersion());
Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The css file includes selectors of yours.
Since you said it is only done when the controller does it.
Use the FXMLLoader to load your controller. then you change the StyleClass to the newStyle.