I want to snap a picture of a webpage using the JavaFX "snapshot" method. My problem is that the WebView doesn't load the webpage before I snap the snapshot. Putting the call to snapshot inside a listener that tests when the webview's getLoadWorker succeeds doesn't work. I get inside the "if" of the listener (see below) before the page actually loads.
To demonstrate this, the following standalone code both attempts to snap the snapshot upon "success" and allows you to click a button to do the same. The button click (marked "BUTTON") works, because you click it after the webpage loads. However the automatic snapshot (marked "AUTOMATIC") produces a blank image, because for some reason the worker state is reaching "SUCCEEDED" before the page is actually loaded.
Can someone tell me what I'm doing wrong with the getLoadWorker listener? Thank you for any help!
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import javax.imageio.ImageIO;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class OpenHTML extends Application {
#Override
public void start(Stage primaryStage) {
final VBox vbox = new VBox(2);
final Button btn = new Button();
WebView webView = new WebView();
webView.setMaxWidth(600);
final WebEngine webEngine = webView.getEngine();
File myHTMLFile = new File("C:/Temp/test.html");
try {
webEngine.load(myHTMLFile.toURI().toURL().toString());
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
webEngine.getLoadWorker().stateProperty()
.addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> ov, State oldState,
State newState) {
if (newState == Worker.State.SUCCEEDED) {
System.out.println("succeeded");
// AUTOMATIC
snapit(vbox, "snapshot_auto.png");
}
}
});
vbox.getChildren().add(btn);
vbox.getChildren().add(webView);
btn.setText("Snap a picture");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// BUTTON
snapit(vbox, "snapshot_by_btn.png");
}
});
Scene scene = new Scene(new Group(vbox), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void snapit(VBox vbox, String filename) {
WritableImage snapshot = vbox.snapshot(new SnapshotParameters(), null);
File file = new File("C:/Temp/" + filename);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(snapshot, null);
try {
ImageIO.write(renderedImage, "png", file);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
OpenHTML.launch(args);
}
}
Related
I'm using javaFX to create an application.
I have a hyper-link somewhere and I've set an (onAction) for it as shown below
Hyperlink studentList = ...; // It's given proper object
studentList.setOnAction(...);
now somewhere else i used this method to simluate a click on this hyperlink
studentList.fire();
now my problem is that how can i distinguish real click/keyPress from fire() method ?
Here's one way to do it. Just add an EventHandler to the setOnMousePressed property. Be sure to add it to setOnMousePressed and not e.g. setOnMouseClicked, since setOnMousePressed is invoked before the fire() is invoked while setOnMouseClicked is invoked after.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MCVE extends Application {
#Override
public void start(Stage stage) {
VBox content = new VBox(5);
content.setPadding(new Insets(10));
Hyperlink link = new Hyperlink("Hyperlink");
Button fireButton = new Button("Fire hyperlink");
fireButton.setOnAction(e -> link.fire());
BooleanProperty mouseClicked = new SimpleBooleanProperty(false);
link.setOnMousePressed(e -> {
System.out.println("Mouse click");
mouseClicked.set(true);
});
link.setOnAction(e -> {
if (!mouseClicked.get()) {
System.out.println("No mouse click");
}
mouseClicked.set(false);
});
content.getChildren().addAll(link, fireButton);
stage.setScene(new Scene(content));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I want to take a snapshot of a website without any video, so it's just plain text with some css and pictures.
I am using a WebView (which is the scene of the JFXPanel) to load the website and then save it via
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
(where "scene" is the scene of the JFXPanel)
but the saved image just displays a part of the website, instead of the complete content (see picture).
How do I ensure/enforce that the dimensions of the image matches the dimensions of the JFXPanel content and everthing is visible?
Complete Code:
package renderer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class HtmlRenderer extends Application {
private JFXPanel jfxPanel;
private WebView webView;
public void start(Stage stage) {
jfxPanel = new JFXPanel();
webView = new WebView();
webView.getEngine().getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
HtmlRenderer.this.toImage(jfxPanel.getScene());
try {
Platform.exit();
HtmlRenderer.this.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
jfxPanel.setScene(new Scene(webView));
this.updateView("http://www.stackoverflow.com/");
}
private void toImage(Scene scene) {
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
// TODO: save in matching dir using proper filename
File file = new File("D:/workspace/SiteChecker/test.png");
try {
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
} catch (IOException e) {
// TODO: exception handling
}
}
public void updateView(String url) {
webView.getEngine().load(url);
}
private void reloadView() {
webView.getEngine().reload();
}
}
So I found a solution, but it's far from perfect and not really perfomant.
However, nothing else works for me.
The trick is to load the website once, determine width and height of the site. The second time I set the preferred size of the Webview to the determined values and load the website again with the new size. I think it's because the first time only the visible part is rendered.
The width and height can be determined with javascript, e.g.:
private int getPageWidth(WebView webView) {
String script = "Math.max(" +
"document.body.scrollWidth, document.body.offsetWidth," +
"document.documentElement.clientWidth, document.documentElement.scrollWidth," +
"document.documentElement.offsetWidth );";
WebEngine engine = webView.getEngine();
int maxWidth = (int) engine.executeScript(script);
return maxWidth;
}
For some reason some websites have a funny end/bottom i.e. there is a lot of empty space.
I've recently started playing around with JavaFX :) I'm working on a little pet project and one of the issues I'm currently having is animating the resize of a VBox when a child is added or removed to/from the VBox.
I've got "the kids" fading out and then being removed from the VBox already. Once that animation completes I need the VBox height to resize preferably as an animation and not like the instant change it currently does.
The other threads that I've found are similar but I think they aren't quite exactly what I'm looking for.
Animation upon layout changes
Adding Node's animated to a VBox
Main Class:
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCombination;
import javafx.scene.paint.Color;
import application.notifier.*;
public class Main extends Application
{
#Override
public void start(Stage stage)
{
try
{
stage.setTitle("Test");
Group root = new Group();
Scene scene = new Scene(root, (Screen.getPrimary().getBounds().getWidth()-100), (Screen.getPrimary().getBounds().getHeight()-100), Color.WHITE);
Notifier notice = new Notifier();
Notifier.addNotice("Testing Add Notice");
Notifier.addNotice("Testing Add Notice again!");
root.getChildren().add(Notifier.container);
stage.setFullScreen(true);
stage.setScene(scene);
stage.setFullScreenExitHint("");
//stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
stage.show();
Button test = new Button("Remove");
test.setLayoutX(500.0);
test.setLayoutY(500.0);
test.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.removeNotice();
}
});
root.getChildren().add(test);
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Notifier Class:
import java.util.ArrayList;
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.util.Duration;
public class Notifier
{
private static int maxVisibleNotices = 5;
private static int currentVisible = 0;
private static ArrayList<String> msgOverflow;
public static VBox container;
public Notifier()
{
BackgroundFill bkgrndFill = new BackgroundFill(Color.rgb(0, 0, 0, .65), new CornerRadii(10.0), new Insets(0));
Background bkgrnd = new Background(bkgrndFill);
Notifier.container = new VBox();
Notifier.container.backgroundProperty().set(bkgrnd);
Notifier.container.setAlignment(Pos.TOP_CENTER);
Notifier.container.setMinWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setMaxWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setLayoutX((Screen.getPrimary().getBounds().getWidth() - (Screen.getPrimary().getBounds().getWidth() - 50))/2);
Notifier.container.setLayoutY(5.0);
Notifier.container.setSpacing(5.0);
Notifier.container.setPadding(new Insets(5.0));
Notifier.msgOverflow = new ArrayList<String>();
}
public static void addNotice(String msg)
{
if(Notifier.currentVisible < Notifier.maxVisibleNotices)
{
Text txt = new Text(msg);
txt.setFill(Color.rgb(255,255,255));
Notifier.container.getChildren().add(txt);
Notifier.currentVisible++;
}
else
{
Notifier.msgOverflow.add(msg);
}
}
public static void removeNotice()
{
if(Notifier.currentVisible > 0)
{
FadeTransition ft = new FadeTransition(Duration.millis(1000), Notifier.container.getChildren().get(0));
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.setCycleCount(0);
ft.setAutoReverse(false);
ft.play();
ft.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.container.getChildren().remove(0);
Notifier.currentVisible--;
}
});
}
}
}
I hope this is clear enough.
And thanks in advance for help or suggestions.
Probably not very helpful any more. I am currently working on the same thing.
You just need:
1. Add the same animation(if they all supposed to be same) to each of the remaining children in the VBox.
2. Use ParallelAnimation to make them run at the same time.
3. Use again a SequentialAnimation for you "kids" fading out animation and the parallel animation. This ensures they will not happen concurrently since multiple threads could run simultaneously.
4. To reach still the same view as what the instant update does, use animation/timeline.setonFinished(EventHandler()) to updates the screen
I just started Java and getting some GUI with Fx but I get an null pointer exception when trying to replace my scenecontent.
In the main the Login.fxml is loaded without any problem but when I click to the button for switching to another fxml I get the error.
The main:
package application;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
private Stage stage;
#Override
public void start(Stage primaryStage) {
Parent root;
try {
root = FXMLLoader.load(getClass().getResource("Login.fxml"));
} catch (IOException e) {
e.printStackTrace();
return;
}
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void gotoMainView() {
try {
MainViewController profile = (MainViewController) replaceSceneContent("MainView.fxml");
profile.setApp(this);
} catch (Exception ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
void Userlogin()
{
gotoMainView();
}
private void gotoLogin() {
try {
LoginController login = (LoginController) replaceSceneContent("Login.fxml");
login.setApp(this);
} catch (Exception ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
private Initializable replaceSceneContent(String fxml) throws Exception {
FXMLLoader loader = new FXMLLoader();
InputStream in = Main.class.getResourceAsStream(fxml);
loader.setBuilderFactory(new JavaFXBuilderFactory());
loader.setLocation(Main.class.getResource(fxml));
AnchorPane page;
try {
page = (AnchorPane) loader.load(in);
} finally {
in.close();
}
Scene scene = new Scene(page, 800, 600);
stage.setScene(scene);
stage.sizeToScene();
return (Initializable) loader.getController();
}
}
The Logincontroller class:
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
public class LoginController extends AnchorPane implements Initializable {
#FXML
static private Button btntext;
#FXML
static private TextField loginusr;
#FXML
static private PasswordField loginpwd;
private Main application;
public void setApp(Main application){
this.application = application;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
btntext.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
System.out.println(loginusr.getText());
System.out.println(loginpwd.getText());
application.Userlogin();
}
});
}
The error occurs at:
application.Userlogin();
Many thanks in advance
The initialize() method is called as soon as the FXML is loaded. Have a look here
Your initialize() is called before setApp(Main application). At this point of time, the application object has not been initialized and is null !
This means the initialize() method is called at
MainViewController profile = (MainViewController) replaceSceneContent("MainView.fxml");
whereas application object which is used inside the initialize() is instantiated after this line, when you are using
profile.setApp(this);
So when you are trying the code application.Userlogin(); inside initalize(), at the time of execution, application is null !
I am attempting to make a simple editor in JavaFX 2.2. I downloaded Ace Editor repository and created a test page which works fine when double clicked.
However, it does not work when I try to embed it into the WebView.
SSCCE:
package web;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.StackPaneBuilder;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.scene.web.WebViewBuilder;
import javafx.stage.Stage;
public class EditorTrial extends Application{
WebView webView;
WebEngine engine;
StackPane stack;
Scene scene;
#Override
public void start(Stage primStage) throws Exception {
stack = StackPaneBuilder
.create()
.alignment(Pos.CENTER)
.build();
webView = WebViewBuilder
.create()
.build();
engine = webView.getEngine();
engine.setJavaScriptEnabled(true);
engine.load("/home/little/Downloads/AceEditor/ace-builds-master/MyTrial.html");
stack.getChildren().add(webView);
scene = SceneBuilder
.create()
.root(stack)
.build();
primStage.setScene(scene);
primStage.sizeToScene();
primStage.show();
}
public static void main(String[] args) {
Application.launch("web.EditorTrial");
}
}
Can someone please tell me what is wrong?
It works for me when the absolute file path :
try
File f = new File("/home/little/Downloads/AceEditor/ace-builds-master/MyTrial.html");
String StrURL = f.toURI().toURL().toString();
engine.load(StrURL);