I have an FXML file that has a Pane as one of it's entries, used for the output of our program. I would like to have this Pane contain an HTMLEditor. I'm a little bit confused at what to do to accomplish this. The class uses the Singleton pattern as recommended, and I can call into the Controller to get the Pane.
Then I find myself having to create an inner class, since HTMLEditor is not a Node. So I extended rectangle to do this, and use getChildren.add(htmlEditorWrapper) to try and add it as a Node. Of course, the HTMLEditor does not show up when I run the program.
The gist of my question: How do I add an HTMLEditor to a Pane (which is in the fxml file)?
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.scene.web.HTMLEditor;
/**
* Gets the controller's outputPane (the console in the gui)
* #author Matt
*
*/
public class OutputPanel{
private static Pane pane;
private static HtmlEditorWrap htmlEditor = new HtmlEditorWrap();
private static final OutputPanel outputPanel = new OutputPanel();
private OutputPanel(){}
public static OutputPanel getInstance(){
pane = Controller.getOutputPane();
pane.getChildren().add(htmlEditor);
return outputPanel;
}
public void clear(){
//htmlEditor.setHtmlText();
}
public static void write(String text){
htmlEditor.setHtmlText(text + "\n");
}
}
class HtmlEditorWrap extends Rectangle{
HTMLEditor htmlEditor = new HTMLEditor();
public HtmlEditorWrap(){
htmlEditor.setLayoutX(200);
htmlEditor.setLayoutY(200);
htmlEditor.setHtmlText("TESTING");
}
public void setHtmlText(String text){
htmlEditor.setHtmlText(text);
}
}
Actually HtmlEditor is a Node. Try adding it directly.
And how did you obtain an editor by extending Rectangle?
Related
(My) expected behaviour:
When you call setFocusTraversable(false) on a Node with children (i.e. the Node has a method getChildren() that returns a List of Nodes) the focusTraversable properties of that node and all its children get the value false.
Actual behaviour:
However, when I call setFocusTraversable(false) on for example a TextFlow, its children are still being able to receive focus. This is illustrated in the code below.
Why does the setFocusTraversable(boolean) method work this way, and how should one work around this limitation?
// File: App.java
package nl.aronhoogeveen.bugreports;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
// TextFlow 1
Text text1 = new Text("This is text1");
Hyperlink hyperlink1 = new Hyperlink("This is hyperlink1");
Text text2 = new Text("This is text2");
Hyperlink hyperlink2 = new Hyperlink("This is hyperlink2");
TextFlow textFlow1 = new TextFlow(text1, hyperlink1, text2, hyperlink2);
textFlow1.setFocusTraversable(false);
// TextFlow 2 (CustomTextFlow)
Text text3 = new Text("This is text3");
Hyperlink hyperlink3 = new Hyperlink("This is hyperlink3");
Text text4 = new Text("This is text4");
Hyperlink hyperlink4 = new Hyperlink("This is SPARTAAAAA");
CustomTextFlow textFlow2 = new CustomTextFlow(text3, hyperlink3, text4, hyperlink4);
textFlow2.customSetFocusTraversable(false);
BorderPane rootBorderPane = new BorderPane();
rootBorderPane.setTop(textFlow1);
rootBorderPane.setBottom(textFlow2);
stage.setScene(new Scene(rootBorderPane));
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch();
}
}
// File: CustomTextFlow.java
package nl.aronhoogeveen.bugreports;
import javafx.scene.Node;
import javafx.scene.text.TextFlow;
public class CustomTextFlow extends TextFlow {
public CustomTextFlow(Node... children) {
super(children);
}
/**
* Sets the property focusTraversable of all children's children to {#code b}.
*
* #param b the value to set for the properties
*/
public void customSetFocusTraversable(boolean b) {
for (Node child : getChildren()) {
// Assume for simplicity that the children are not Parents or other types that have children
child.setFocusTraversable(b);
}
}
}
I filed a bug on the ControlsFX component HyperlinkLabel regarding this behaviour, and after investigating I discovered that this behaviour was already present in the JavaFX TextFlow component that is used by the HyperlinkLabel and therefore I am posting this question.
I'm new to javafx programming, and i dont understand why my javafx Text isn't getting updated, when it is changed.
I want to make a timer, that counts from 60 to 0. I'm trying to change the timeCounter Text, for every second that has passed.
Help would be appreciated!
Here's my controller code:
public class Controller {
TimerUtil timerUtil;
#FXML
private Button startButton;
#FXML
private Text timeCounter;
#FXML
private Text pointCounter;
#FXML
private Circle circle;
#FXML
private void handleStartButtonClick(ActionEvent event) {
timerUtil = new TimerUtil();
}
private class TimerUtil extends Pane {
private int tmp = 60;
private Timeline animation;
public TimerUtil(){
getChildren().add(timeCounter);
animation = new Timeline(new KeyFrame(Duration.seconds(1), e -> timeLabel()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
}
private void timeLabel(){
if(tmp > 0){
tmp--;
}
timeCounter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
}
}
Your error occurs because the label has been silently removed from it's displayed parent node:
You have your TimerUtil class extend Pane (I have no idea why).
You add the timeCounter text to the TimeUtil pane (again, I have no idea why).
Adding the timeCounter text to the TimeUtil pane will silently remove it from the parent which the FXML loader injected it into.
You are probably only displaying the parent which the FXML loader injected.
You are never displaying the TimerUtil pane.
Therefore, even though the text is getting updated by your timeline, you never see it.
To better understand your error, read:
JavaFX - Why does adding a node to a pane multiple times or to different panes result in an error?
From the Node javadoc:
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
Once you fix your error, the basic concept works for me. Here is the runnable example I created from your code:
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timer extends Application {
private int tmp = 60;
private Text counter = new Text();
private Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(1), e -> updateCounter())
);
#Override
public void start(Stage stage) {
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
StackPane layout = new StackPane(counter);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private void updateCounter() {
if (tmp > 0){
tmp--;
} else {
animation.stop();
}
counter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
public static void main(String[] args) {
launch(args);
}
}
I want to create a final class, having only static methods- an instance of this class will not be needed- it should be a static container. This class is supposed to have a map field containing created scenes. Now the problem is- method getClass() is not static and I cannot contain it in my static initializer block. Is there a way of creating scenes from FXML files without the use of non-static methods?
Here is the code:
package gui;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.util.Map;
import java.util.TreeMap;
public class ViewManager {
/**
* Class containing constant height and width of the window.
*/
public static final class Bounds{
public static final int HEIGHT = 800;
public static final int WIDTH = 800;
}
/**
* Enum class containing paths to following scenes.
*/
public enum SceneName{
LOGIN_VIEW("/login_view.fxml"),
MAIN_VIEW("/main_view.fxml");
private String path;
SceneName(String path) {
this.path = path;
}
#Override
public String toString() {
return path;
}
}
private static Map<SceneName, Scene> sceneContainer;
static{
sceneContainer = new TreeMap<>();
for(SceneName sceneName : SceneName.values()) {
//here is the non-static reference
Parent root = FXMLLoader.load(getClass().getResource(SceneName.LOGIN_VIEW.toString()));
sceneContainer.put(SceneName.LOGIN_VIEW, new Scene(root, Bounds.HEIGHT, Bounds.WIDTH));
}
}
public static Map<SceneName, Scene> getSceneContainer() {
return sceneContainer;
}
}
If you only need access to a certain Class instance, simply use ClassName.class:
// also changed this to use the loop variable instead of loading the same scene twice
Parent root = FXMLLoader.load(ViewManager.class.getResource(sceneName.toString()));
sceneContainer.put(sceneName, new Scene(root, Bounds.HEIGHT, Bounds.WIDTH));
In general using static too often should be avoided though. A singleton could be the better option. Even better if you're able to pass a ViewManager instance to all the classes that need it... (Taking a look at dependency injection may be a good idea.)
Here's the code :
Main File :
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testobjectarray;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
*
*
*/
public class TestObjectArray extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application. main() serves only as fallback in case the application can not be launched through deployment artifacts, e.g., in IDEs
* with limited FX support. NetBeans ignores main().
*
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
myClass[] c = new myClass[5];
c[2].myMethod();
}
}
Class declared outside :
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testobjectarray;
/**
*
*
*/
public class myClass {
public void myMethod(){
System.out.println("Inside myMethod");
}
}
Problem, I get errors when I compile i.e. initializing an array of objects and invoking the method. If I have just one object, it works great. Any help appreciated.
The problem with your code is you are initialising an array of size 5, but you never add values to it. Try the following code
myClass[] c = new myClass[5];
c[0] = new myClass();
c[1] = new myClass(); // and so on till c[4]
//now you can call the methods
c[1].myMethod();
In this code:
public class ESM extends Application {
private Stage primaryStage;
#FXML
private ToolBar mainToolBar;
#Override
public void start(final Stage stage) throws Exception {
try{
this.primaryStage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/nz/co/great_ape/esm3/main_window.fxml"));
Scene scene = new Scene(root, 800, 700);
// Setup main stage to be full screen, no min or max buttons.
// TODO: How will this handle multiple screens? Apparently not well :-(
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
primaryStage.setX(bounds.getMinX());
primaryStage.setY(bounds.getMinY());
primaryStage.setWidth(bounds.getWidth());
primaryStage.setHeight(bounds.getHeight());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setTitle("ESM three");
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("This will fail because mainToolBar is null. Why?");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
} catch (Exception ex) {
Logger.getLogger(ESM.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Use initialize() to setup widgets from scenebuilder files, it is
* called by FXMLLoader.
*/
#FXML
public void initialize(){
System.out.println("initialize() But when here all is good and mainToolBar is a ToolBar.");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support.
*
* #param args The command line arguments.
*/
public static void main(String[] args) {
launch(args);
}
}
I cant see why it's got a value in the initialise() but in the start it's null. When debuging it's clear that initiialize() is called by FXMLLOader from inside start()
I was going to post the fxml but it does not seem to work as nothig shows in the preview. Any way, it's a real basic file, a BordePane and a ToolBar.
Any clues?
Always create a new class for your FXML Controller, don't try to reuse an Application class as a Controller class.
An Application instance is created by the JavaFX application launcher.
A Controller instance is created by the JavaFX FXML loader.
You don't supply the FXML that you use, but I am going to guess that it has it's Controller class erroneously set to be your application class.
So in your code, what happens is:
An instance of the application is created when you run the program (via the launch method).
In your application start method, you invoke the FXMLLoader, which instantiates a new Controller (in your case a new instance of the application class).
The FXMLLoader injects the #FXML tagged members into the new application object and invokes the initialize on the new object.
But your original application object doesn't know anything about the new application object and hence doesn't have a menu bar set in it.
In summary, to fix this:
Create a new controller class that the FXMLLoader can instantiate.
Change your fxml to reference the new controller class.
If your application really needs to reference the controller, then you can use the getController method on the FXML loader and in your controller class provide public methods to retrieve required elements (like your menu bar). See my answer to Passing Parameters JavaFX FXML for some more examples of this method.
import javafx.scene.control.ToolBar;
import javafx.fxml.FXML;
public class ESMController {
#FXML
private ToolBar mainToolBar;
public ToolBar getMainToolBar() { return mainToolBar; }
#FXML
public void initialize(){
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
}