So here's my problem. I'm using WebView class from JavaFX in swing. The thing I want to do is that I want fields loaded in webview to be filled automatically with information stored in an array. Is it possible?
Thanks in advance
Here is an automated form fill example JavaFX app for WebView.
Values (login credentials) are entered into JavaFX fields in the yellow part of the screen and then automatically posted (using the w3c dom api) in the WebView (the white part of the screen) when the login page appears.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.Stage;
import org.w3c.dom.*;
import org.w3c.dom.html.*;
public class WebViewFormPost extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final TextField fxUsername = new TextField();
fxUsername.setPrefColumnCount(20);
final TextField fxPassword = new PasswordField();
final BooleanProperty loginAttempted = new SimpleBooleanProperty(false);
final WebView webView = new WebView();
webView.setPrefWidth(1000);
final WebEngine engine = webView.getEngine();
engine.documentProperty().addListener(new ChangeListener<Document>() {
#Override
public void changed(ObservableValue<? extends Document> ov, Document oldDoc, Document doc) {
if (doc != null && !loginAttempted.get()) {
if (doc.getElementsByTagName("form").getLength() > 0) {
HTMLFormElement form = (HTMLFormElement) doc.getElementsByTagName("form").item(0);
if ("/oam/server/sso/auth_cred_submit".equals(form.getAttribute("action"))) {
HTMLInputElement username = null;
HTMLInputElement password = null;
NodeList nodes = form.getElementsByTagName("input");
for (int i = 0; i < nodes.getLength(); i++) {
HTMLInputElement input = (HTMLInputElement) nodes.item(i);
switch (input.getName()) {
case "ssousername":
username = input;
break;
case "password":
password = input;
break;
}
}
if (username != null && password != null) {
loginAttempted.set(true);
username.setValue(fxUsername.getText());
password.setValue(fxPassword.getText());
form.submit();
}
}
}
}
}
});
engine.getLoadWorker().exceptionProperty().addListener(new ChangeListener<Throwable>() {
#Override
public void changed(ObservableValue<? extends Throwable> ov, Throwable oldException, Throwable exception) {
System.out.println("Load Exception: " + exception);
}
});
GridPane inputGrid = new GridPane();
inputGrid.setHgap(10);
inputGrid.setVgap(10);
inputGrid.addRow(0, new Label("Username: "), fxUsername);
inputGrid.addRow(0, new Label("Password: "), fxPassword);
Button fxLoginButton = new Button("Login to Oracle Forums");
fxLoginButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
if (notEmpty(fxPassword.getText()) && notEmpty(fxPassword.getText())) {
loginAttempted.set(false);
engine.load("https://forums.oracle.com/community/developer/english/java/javafx/login.jspa");
}
}
});
fxLoginButton.setDefaultButton(true);
ProgressIndicator fxLoadProgress = new ProgressIndicator(0);
fxLoadProgress.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty());
fxLoadProgress.visibleProperty().bind(webView.getEngine().getLoadWorker().runningProperty());
HBox loginPane = new HBox(10);
loginPane.getChildren().setAll(
fxLoginButton,
fxLoadProgress
);
final VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().addAll(
new Label("Enter your Oracle Web Account credentials"),
inputGrid,
loginPane,
webView
);
VBox.setVgrow(webView, Priority.ALWAYS);
stage.setScene(new Scene(layout));
stage.show();
fxUsername.requestFocus();
}
private boolean notEmpty(String s) {
return s != null && !"".equals(s);
}
}
The above application is adapted from a previous Oracle forum question on Submitting HTML Forms with JavaFX Webview.
If you don't have an Oracle technology network account to test the above program, you can sign up for one here: https://myprofile.oracle.com/EndUser/faces/profile/createUser.jspx.
Posting to WebView using JQuery
An alternate implementation, that I would actually prefer is to use is JavaScript jQuery to introspect the DOM and perform the post rather than using the Java DOM apis. There is a sample for using jQuery on any arbitrary webpage hosted in a WebView. So you could combine the ideas from this automated WebView form post and the jQuery hosted WebView sample to create a version which uses JQuery to perform the post.
I fixed this with JavaFX webView Javascript engine.
If anyone is intersted here's code snippet.
String setLastName = "document.getElementsByName('lastName')[0].value='" + lastName + "';";
String setName = "document.getElementsByName('firstName')[0].value='" + name + "'";
String setDateBirth = "document.getElementsByName('birthdate')[0].value='" + datebirth + "';";
String setPhone = "document.getElementsByName('phone')[0].value='" + phone + "';";
String setEmail = "document.getElementsByName('email')[0].value='" + email + "';";
String setPassport = "document.getElementsByName('passport')[0].value='" + passport + "';";
Button button = new Button("Fill the form");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
webEngine.executeScript(setLastName);
webEngine.executeScript(setName);
webEngine.executeScript(setDateBirth);
webEngine.executeScript(setPhone);
webEngine.executeScript(setEmail);
webEngine.executeScript(setPassport);
}
});
Related
I'm currently working on a password manager. Before making any changes to a certain service, the program will ask the user for a password for authorization and then proceed to show the appropriate dialog, if the password is correct.
The issue that I'm having is that if I go through the cycle of putting in my password to make the change, click "ok", and then proceeding to make changes on the shown dialog, on the next turn if instead of putting the password when prompted I close the prompt, then the program retrieves the password from the previous iteration although it has been explicitly cleared. Resulting in the concurrent dialog showing, which is only supposed to show if you put in the correct password.
private void handleEditButton(MouseEvent event) {
Optional<String> rslt = passwordConfirmDialog.showAndWait();
if (rslt.get().equals(""))
return; //Do not proceed
String userInput = rslt.get().trim();
// Complex expression, but use of && statement is necessary to avoid an
// unecessary call to db and have return statement on this IF
if (!(!userInput.isBlank() && isCorrectPassword(userInput))) {
// show dialog
AlertConfigs.invalidPasswordTransactionFailed.showAndWait();
return;
}
System.out.println("Edit Handler: Correct password. -> " + userInput);
//Proceed to show next dialog...
private void initializePasswordConfirmDialog() {
passwordConfirmDialog.setTitle("User Account Control");
passwordConfirmDialog.setHeaderText("Please enter your password to continue.");
// Set the button types.
ButtonType ok = new ButtonType("Ok", ButtonData.OK_DONE);
passwordConfirmDialog.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL);
final PasswordField psField = new PasswordField();
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Please Enter your password"), 0, 0);
grid.add(psField, 1, 0);
passwordConfirmDialog.getDialogPane().setContent(grid);
passwordConfirmDialog.setResultConverter(buttonType -> {
String rslt = "";
if (buttonType == ok) {
rslt = psField.getText();
}
psField.clear();
return rslt;
});
}
I've posted a video on YouTube to help visualize the problem. https://youtu.be/sgayh7Q7Ne8
The PasswordField in initializePasswordConfirmDialog() is cleared because whenever I run the the prompt the second time, the PasswordField is blank (visually). Nevertheless, for some reason it still grabs the result from the previous iteration.
The initializePasswordConfirmDialog() is called once inside the constructor and is responsible for set the passwordConfirmDialog variable with the adequate properties.
Some additional code:
HomeController.java
#FXML
private GridPane servicesGrid;
private Dialog<String> passwordConfirmDialog;
private Dialog<Service> editServiceDialog;
private final int NUM_COLUMNS = 7;
public HomeController() {
passwordConfirmDialog = new Dialog<>();
initializePasswordConfirmDialog();
editServiceDialog = new Dialog<>();
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
loadServicesGridpane();
}
private void loadServicesGridpane() {
ArrayList<Service> currS = acct.getServices();
// int currentRow = 1;
for (Service s : currS)
addRowToServiceGrid(s);
}
private void addRowToServiceGrid(Service s) {
int rowIdx = servicesGrid.getChildren().size() / 4;
Button editButton = new Button("Edit");
editButton.setOnMouseClicked(event -> {
handleEditButton(event);
});
Button deleteButton = new Button("Delete");
deleteButton.setOnMouseClicked(event -> {
handleDeleteButton(event);
});
deleteButton.setId(s.getServiceName());
Label currServiceName = new Label(s.getServiceName());
currServiceName.setId(s.getServiceName());
Label currUsername = new Label(s.getServiceUsername());
Label currPassword = new Label(s.getServicePassword());
Label dateCreated = new Label(s.getDateCreated());
Label lastPssdChange = new Label(s.getLastPasswordChange());
servicesGrid.addRow(rowIdx, currServiceName, currUsername, currPassword, dateCreated, lastPssdChange,
deleteButton, editButton);
}
To study the problem in isolation, I refactored this example to permit reusing the dialog. As shown below, reusing the dialog requires clearing the password field. Replace the parameter dialog with an invocation of createDialog() to see that creating the dialog each time does not require clearing the password field. Comparing the profile of each approach may help you decide which approach is acceptable; in my experiments, reuse added negligible memory overhead (~250 KB), and it protracted garbage collection slightly(~50 ms).
#!/bin/sh
java … DialogTest -reuse &
pid1=$!
java … DialogTest -no-reuse &
pid2=$!
echo $pid1 $pid2
jconsole $pid1 $pid2
Unfortunately, creating the dialog each time may only appear to solve the problem; it may have exposed a latent synchronization problem. In particular, verify that your result converter's callback executes on the JavaFX Application Thread. To illustrate, I've added a call to Platform.isFxApplicationThread() in resultsNotPresent() below.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/73328282/230513
* #see https://stackoverflow.com/a/44172143/230513
*/
public class DialogTest extends Application {
private static boolean REUSE_DIALOG = true;
private record Results(String text, String pass) {
private static Results of(String text, String pass) {
return new Results(text, pass);
}
}
#Override
public void start(Stage stage) {
var label = new Label("Reuse: " + REUSE_DIALOG);
var button = new Button("Button");
if (REUSE_DIALOG) {
var dialog = createDialog();
button.setOnAction(e -> showDialog(dialog));
} else {
button.setOnAction(e -> showDialog(createDialog()));
}
stage.setScene(new Scene(new HBox(8, label, button)));
stage.show();
}
private Dialog<Results> createDialog() {
var dialog = new Dialog<Results>();
dialog.setTitle("Dialog Test");
dialog.setHeaderText("Please authenticate…");
var dialogPane = dialog.getDialogPane();
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
var text = new TextField("Name");
var pass = new PasswordField();
dialogPane.setContent(new VBox(8, text, pass));
dialog.showingProperty().addListener((o, wasShowing, isShowing) -> {
if (isShowing) {
Platform.runLater(pass::requestFocus);
}
});
dialog.setResultConverter((ButtonType bt) -> {
if (ButtonType.OK == bt) {
var results = Results.of(text.getText(), pass.getText());
pass.clear();
return results;
}
pass.clear();
return null;
});
return dialog;
}
private void showDialog(Dialog<Results> dialog) {
var optionalResult = dialog.showAndWait();
optionalResult.ifPresentOrElse(
(var results) -> System.out.println(results),
(this::resultsNotPresent));
}
private void resultsNotPresent() {
System.out.println("Canceled on FX application thread: "
+ Platform.isFxApplicationThread());
}
public static void main(String[] args) {
if (args.length > 0) {
REUSE_DIALOG = args[0].startsWith("-r");
}
launch(args);
}
}
I'm using two WebViews to display two versions of HTML formatted text for comparison. The two display the same amount of text (same number of lines and corresponding lines have always the same length).
When the displayed text exceeds the size of the node, the WebView gets scroll bars. Of course I want these scroll bars to scroll synchronously so that always the corresponding text is displayed.
In order to supply a minimal, complete and verifiable example, I trimmed the code down to this:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
final ScrollBar actualScrollBarV = (ScrollBar)actualPane.lookup(".scroll-bar:vertical");
final ScrollBar expectedScrollBarV = (ScrollBar)expectedPane.lookup(".scroll-bar:vertical");
actualScrollBarV.valueProperty().bindBidirectional(expectedScrollBarV.valueProperty());
final ScrollBar actualScrollBarH = (ScrollBar)actualPane.lookup(".scroll-bar:horizontal");
final ScrollBar expectedScrollBarH = (ScrollBar)expectedPane.lookup(".scroll-bar:horizontal");
actualScrollBarH.valueProperty().bindBidirectional(expectedScrollBarH.valueProperty());
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
} // ---------------------------- end of DifferencePanel ----------------------------
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
I tried using adding a listener:
actualScrollBarV.onScrollFinishedProperty().addListener(event -> {
System.out.println(event);
});
But the listener is never invoked.
I'm using Java version 1.8.0_92, but with version 9.0.4 I get the same result.
Can anybody tell me, what I'm missing here?
I would post a comment, but sadly I did not have enough reputation.
Did you tried the following solution? Create listeners on value changed event, instead of binding. Synchronizing two scroll bars JavaFX
I could not get the ScrollBar approach working. It turned out that the listeners were actually invoked (breakpoints in lambdas are not always working?). Setting the scroll bar value of the other WebView did not get it inclined change the scroll bar or the view port. :-(
There is something strange going on with events in WebView; that might be because there is a native library involved...
However, the approach using the event handler of WebView works. The event handler of each WebView simply mirrors all events to the other WebView, using a synchronizing field Boolean scrolling to avoid recursion.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private Boolean scrolling = Boolean.FALSE;
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
wireViews(actualPane, expectedPane);
wireViews(expectedPane, actualPane);
}
private void wireViews(WebView webView, WebView otherWebView) {
webView.addEventHandler(Event.ANY, event -> {
if (!scrolling.booleanValue()) {
synchronized (scrolling) {
scrolling = Boolean.TRUE;
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
Point2D origin = webView.localToScreen(0, 0);
Point2D otherOrigin = otherWebView.localToScreen(0, 0);
double offsetX = otherOrigin.getX() - origin.getX();
double offsetY = otherOrigin.getY() - origin.getY();
double x = mouseEvent.getX();
double y = mouseEvent.getY();
double screenX = mouseEvent.getScreenX() + offsetX;
double screenY = mouseEvent.getScreenY() + offsetY;
MouseButton button = mouseEvent.getButton();
int clickCount = mouseEvent.getClickCount();
boolean shiftDown = mouseEvent.isShiftDown();
boolean controlDown = mouseEvent.isControlDown();
boolean altDown = mouseEvent.isAltDown();
boolean metaDown = mouseEvent.isMetaDown();
boolean primaryButtonDown = mouseEvent.isPrimaryButtonDown();
boolean middleButtonDown = mouseEvent.isMiddleButtonDown();
boolean secondaryButtonDown = mouseEvent.isSecondaryButtonDown();
boolean synthesized = mouseEvent.isSynthesized();
boolean popupTrigger = mouseEvent.isPopupTrigger();
boolean stillSincePress = mouseEvent.isStillSincePress();
MouseEvent otherMouseEvent =
new MouseEvent(otherWebView, otherWebView, mouseEvent.getEventType(), x, y, screenX,
screenY, button, clickCount, shiftDown, controlDown, altDown, metaDown,
primaryButtonDown, middleButtonDown, secondaryButtonDown, synthesized,
popupTrigger, stillSincePress, null);
otherWebView.fireEvent(otherMouseEvent);
}
else {
otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView));
}
scrolling = Boolean.FALSE;
}
}
});
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
}
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
This works for all input methods I'm interested in:
Keyboard: PageUp, PageDown, all 4 arrow keys, "space bar" (same as PageDown) and shift-"space bar" (same as PageUp), Home and End
Mouse wheel: RollDown and RollUp as well as shift-RollUp (scroll left) and shift-RollDown (scroll right)
Using the mouse to click or to drag the scroll bar.
Using the mouse to select text outside of the current view port.
Mirroring the mouse events has the added benefit that text gets selected in both WebViews.
I am using the following code with java 8 using javaFx.
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.Stage;
#SuppressWarnings("all")
public class Highlighter extends Application {
private boolean marked;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final WebView webView = new WebView();
final WebEngine engine = webView.getEngine();
engine.load("http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html");
final TextField searchField = new TextField("light");
searchField.setPromptText("Enter the text you would like to highlight and press ENTER to highlight");
searchField.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
if (engine.getDocument() != null) {
highlight(
engine,
searchField.getText()
);
}
}
});
final Button highlightButton = new Button("Highlight");
highlightButton.setDefaultButton(true);
highlightButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
searchField.fireEvent(new ActionEvent());
}
});
final Button markedButton = new Button("Mark it");
markedButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
marked = true;
}
});
markedButton.setCancelButton(true);
HBox controls = new HBox(10);
controls.getChildren().setAll(
highlightButton,
markedButton
);
VBox layout = new VBox(10);
layout.getChildren().setAll(searchField, controls, webView);
searchField.setMinHeight(Control.USE_PREF_SIZE);
controls.setMinHeight(Control.USE_PREF_SIZE);
controls.disableProperty().bind(webView.getEngine().getLoadWorker().runningProperty());
searchField.disableProperty().bind(webView.getEngine().getLoadWorker().runningProperty());
primaryStage.setScene(new Scene(layout));
primaryStage.show();
webView.requestFocus();
}
private void highlight(WebEngine engine, String text) {
engine.executeScript("$('body').removeHighlight().highlight('" + text + "')");
}
}
My problem is I want to add a label which displays the marked status of a page.
I tried simply adding a Label label = new Label("Marked: " + marked) to the controls, but this does not work.
Any recommendations how I could add a label to my code to display the marked status?
I appreciate your replies!
If you add a Label to controls with your actual code:
private boolean marked;
Label label = new Label("Marked: " + marked)
controls.getChildren().setAll(
highlightButton,
markedButton,
label
);
it will always show Marked: false, no matter if you change marked afterwards.
If you want that your control responds to changes, JavaFX has observable properties, as you can read here.
So you can replace the boolean primitive with this property that wraps the boolean value:
private final BooleanProperty marked=new SimpleBooleanProperty();
Create the label:
Label label=new Label("Marked: "+marked.get());
HBox controls = new HBox(10);
controls.setAlignment(Pos.CENTER_LEFT);
controls.getChildren().setAll(
highlightButton,
markedButton,
label
);
Change the event for markedButton:
markedButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
marked.set(true);
}
});
(this will work just once, since for now you don't have implemented a way to reset marked to false again)
And finally, add a listener for any change inmarked property:
marked.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
label.setText("Marked: "+newValue);
}
});
Instead of the listener, you can also use Bindings:
Label label=new Label();
label.textProperty().bind(Bindings.concat("Marked: ").concat(marked));
I'm doing an interface to show the progress of my simulation (elevators simulating job on a building).
The thing is, I already did this on the console with System.out.println()s and I wanted to show the exact same thing on a javaFX window. Is there any way where I can set the text of a TextArea or a Label or something to match the output of the console? Just printing the same thing but instead of printing on the console I wanted to print on a window.
I was dumb enough to try and set the Text of a TextAreato the toString() of my simulator but ofc if it is System.out.println(), it shows on the console and not in the ThextArea.
EDIT: This is what I want to print:
#Override
public String toString() {
for (int y = 0; y < 50; y++) {
System.out.println("");
}
for (int i = pisos.size() - 1; i >= 0; i--) {
System.out.print(pisos.get(i).getPiso());
System.out.print(pisos.get(i).pQueue().toString());
System.out.print(" " + percorrerElevadores2(i));
System.out.print(" " + pisos.get(i).pessoasServidas() + "\n");
}
System.out.println("Numero total de passageiros à espera:" + " " + Predio.getPredio().getNPessoasEmEspera());
System.out.println("Numero total de pessageiros servidos:" + " " + Predio.getPredio().getNPessoasServidas());
for (int z = 0; z < getElevadores().size(); z++) {
System.out.println("Distancia percorrida pelo elevador" + " " + z + ":" + " " + Predio.getPredio().getElevadores().get(z).getDistanciaPercorrida() + " " + "Pisos");
System.out.println("Piso destino do elevador" + " " + z + ":" + " " + Predio.getPredio().getElevadores().get(z).getPisoDestino());
}
return "";
}
It is better to use the MessageDialogBox to print the message on window with reference of the panel on which you are working.
I don't really understand your question but it would be easier to rename the println calls than to redirect them. Use some code like this in your main class (the one that extends Application) or really in any class, but you need to add the textArea to the scene graph somewhere.
private static final TextArea textArea = new TextArea();
//add textArea to your scene somewhere in the start method
public static void println(String s){
Platform.runLater(new Runnable() {//in case you call from other thread
#Override
public void run() {
textArea.setText(textArea.getText()+s+"\n");
System.out.println(s);//for echo if you want
}
});
}
Then just use the IDE's search and replace to rename System.out.println to MainClassName.println.
From what I understand, you just want to print text in a location to investigate things, ie the results. Everything you want is just like you print a text on the console, you also want to print this text somewhere in a JavaFX application. Despite its formatting done in their toString method, you can catch the return of the method and print to a JavaFX application (within a control node, for example), right?
If this is the case ...
I created a simple application that works with two text areas. In the middle of application, you will find buttons that manipulate both areas. Basically, the buttons sends the contents of a text area to another. Note:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class AreaTextual extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// Controles.
private Label lab_receptor;
private Label lab_emissor;
private TextArea tArea_receptor;
private TextArea tArea_emissor;
private Button bot_enviar;
private Button bot_enviarLinha;
private Button bot_substituir;
private Button bot_apagar;
// Layout.
private HBox hbox_raiz;
private VBox vbox_oeste;
private VBox vbox_centro;
private VBox vbox_leste;
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.hbox_raiz , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
/** Inicia nós FX.*/
protected void iniFX()
{
// Controles.
this.lab_receptor = new Label();
this.lab_emissor = new Label();
this.tArea_receptor = new TextArea();
this.tArea_emissor = new TextArea();
this.bot_enviar = new Button();
this.bot_enviarLinha = new Button();
this.bot_substituir = new Button();
this.bot_apagar = new Button();
// Layout.
this.hbox_raiz = new HBox();
this.vbox_oeste = new VBox();
this.vbox_centro = new VBox();
this.vbox_leste = new VBox();
}
/** Configura nós FX.*/
protected void confFX()
{
// Controles.
this.lab_receptor.setText("RECEPTOR");
this.lab_receptor.setFont(new Font(32));
this.lab_emissor.setText("EMISSOR");
this.lab_emissor.setFont(new Font(32));
this.bot_enviar.setText("<- ENVIAR");
this.bot_enviar.setPrefSize(150 , 60);
this.bot_enviarLinha.setText("<- ENVIAR+");
this.bot_enviarLinha.setPrefSize(150 , 60);
this.bot_substituir.setText("<- SUBSTITUIR");
this.bot_substituir.setPrefSize(150 , 60);
this.bot_apagar.setText("<- APAGAR TUDO ->");
this.bot_apagar.setPrefSize(150 , 60);
// Layout.
this.hbox_raiz.setSpacing(20);
this.hbox_raiz.setPadding(new Insets(30 , 30 , 30 , 30));
this.hbox_raiz.setAlignment(Pos.CENTER);
this.vbox_oeste.setSpacing(10);
this.vbox_oeste.setAlignment(Pos.CENTER);
this.vbox_centro.setSpacing(10);
this.vbox_centro.setAlignment(Pos.CENTER);
this.vbox_centro.setPrefSize(400 , 200);
this.vbox_leste.setSpacing(10);
this.vbox_leste.setAlignment(Pos.CENTER);
}
/** Adiciona e organiza em layout os nós FX.*/
protected void adFX()
{
this.vbox_leste.getChildren().addAll(this.lab_emissor , this.tArea_emissor);
this.vbox_centro.getChildren().addAll(this.bot_enviar , this.bot_enviarLinha , this.bot_substituir , this.bot_apagar);
this.vbox_oeste.getChildren().addAll(this.lab_receptor , this.tArea_receptor);
this.hbox_raiz.getChildren().addAll(this.vbox_oeste , this.vbox_centro , this.vbox_leste);
}
/** Configura eventos de nós FX.*/
protected void evFX()
{
this.bot_enviar.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.appendText(tArea_emissor.getText());
}
});
this.bot_enviarLinha.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.appendText(String.format("%n%s" , tArea_emissor.getText()));
}
});
this.bot_substituir.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.replaceText(0 , tArea_receptor.getLength() , tArea_emissor.getText());
}
});
this.bot_apagar.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.setText("");
tArea_emissor.setText("");
}
});
}
}
OBS (PT-BR): Eu notei que você fala português, portanto deixei o código na linguagem para que você entenda-o melhor.
This class has nothing exceptional. It just shows you how you can manipulate the text of a TextArea. You can find other types of handlers of a TextArea right here, and also here.
Regarding your problem seen where you call the JavaFX methods from another thread, this can be happening just because you're not using the JavaFX Application Thread. Like the Swing library has the Event Dispatch Thread (EDT), JavaFX also has its own thread responsible for handling the JavaFX elements. Whenever you need to manipulate any JavaFX element, be to setup something or to obtain some data, you need to do this using the JavaFX Application Thread, and not another.
For you to call methods of the JavaFX Application Thread, use the Platform runLater method. For more information about the JavaFX threads system, visit the following links:
http://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
In the first link, read the part that talks about threads. If you have any more questions, come back here and ask.
Good luck.
I need a workflow like below:
// load xyz.com in the browser window
// the browser is live, meaning users can interact with it
browser.load("http://www.google.com");
// return the HTML of the initially loaded page
String page = browser.getHTML();
// after some time
// user might have navigated to a new page, get HTML again
String newpage = browser.getHTML();
I am surprised to see how hard this is to do with Java GUIs such as JavaFX (http://lexandera.com/2009/01/extracting-html-from-a-webview/) and Swing.
Is there some simple way to get this functionality in Java?
Here is a contrived example using JavaFX that prints the html content to System.out - it should not be too complicated to adapt to create a getHtml() method. (I have tested it with JavaFX 8 but it should work with JavaFX 2 too).
The code will print the HTML content everytime a new page is loaded.
Note: I have borrowed the printDocument code from this answer.
public class TestFX extends Application {
#Override
public void start(Stage stage) throws Exception {
try {
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
Scene scene = new Scene(webView);
stage.setScene(scene);
stage.setWidth(1200);
stage.setHeight(600);
stage.show();
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends State> ov, State t, State t1) {
if (t1 == Worker.State.SUCCEEDED) {
try {
printDocument(webEngine.getDocument(), System.out);
} catch (Exception e) { e.printStackTrace(); }
}
}
});
webView.getEngine().load("http://www.google.com");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void printDocument(Document doc, OutputStream out) throws IOException, TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out, "UTF-8")));
}
public static void main(String[] args) {
launch(args);
}
}
Below you will find a SimpleBrowser component which is a Pane containing a WebView.
Source code at gist.
Sample usage:
SimpleBrowser browser = new SimpleBrowser()
.useFirebug(true);
// ^ useFirebug(true) option - will enable Firebug Lite which can be helpful for
// | debugging - i.e. to inspect a DOM tree or to view console messages
Scene scene = new Scene(browser);
browser.load("http://stackoverflow.com", new Runnable() {
#Override
public void run() {
System.out.println(browser.getHTML());
}
});
browser.getHTML() is put inside a Runnable because one needs to wait for a web page to download and render. Trying to invoke this method before page loading will return an empty page, so wrapping this into a runnable is a simple way I came up with to wait for a page to load.
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.layout.*;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class SimpleBrowser extends Pane {
protected final WebView webView = new WebView();
protected final WebEngine webEngine = webView.getEngine();
protected boolean useFirebug;
public WebView getWebView() {
return webView;
}
public WebEngine getEngine() {
return webView.getEngine();
}
public SimpleBrowser load(String location) {
return load(location, null);
}
public SimpleBrowser load(String location, final Runnable onLoad) {
webEngine.load(location);
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State t1) {
if (t1 == Worker.State.SUCCEEDED) {
if(useFirebug){
webEngine.executeScript("if (!document.getElementById('FirebugLite')){E = document['createElement' + 'NS'] && document.documentElement.namespaceURI;E = E ? document['createElement' + 'NS'](E, 'script') : document['createElement']('script');E['setAttribute']('id', 'FirebugLite');E['setAttribute']('src', 'https://getfirebug.com/' + 'firebug-lite.js' + '#startOpened');E['setAttribute']('FirebugLite', '4');(document['getElementsByTagName']('head')[0] || document['getElementsByTagName']('body')[0]).appendChild(E);E = new Image;E['setAttribute']('src', 'https://getfirebug.com/' + '#startOpened');}");
}
if(onLoad != null){
onLoad.run();
}
}
}
});
return this;
}
public String getHTML() {
return (String)webEngine.executeScript("document.getElementsByTagName('html')[0].innerHTML");
}
public SimpleBrowser useFirebug(boolean useFirebug) {
this.useFirebug = useFirebug;
return this;
}
public SimpleBrowser() {
this(false);
}
public SimpleBrowser(boolean useFirebug) {
this.useFirebug = useFirebug;
getChildren().add(webView);
webView.prefWidthProperty().bind(widthProperty());
webView.prefHeightProperty().bind(heightProperty());
}
}
Demo Browser:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.stage.Stage;
public class FXBrowser {
public static class TestOnClick extends Application {
#Override
public void start(Stage stage) throws Exception {
try {
SimpleBrowser browser = new SimpleBrowser()
.useFirebug(true);
final TextField location = new TextField("http://stackoverflow.com");
Button go = new Button("Go");
go.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
browser.load(location.getText(), new Runnable() {
#Override
public void run() {
System.out.println("---------------");
System.out.println(browser.getHTML());
}
});
}
});
HBox toolbar = new HBox();
toolbar.getChildren().addAll(location, go);
toolbar.setFillHeight(true);
VBox vBox = VBoxBuilder.create().children(toolbar, browser)
.fillWidth(true)
.build();
Scene scene = new Scene( vBox);
stage.setScene(scene);
stage.setWidth(1024);
stage.setHeight(768);
stage.show();
VBox.setVgrow(browser, Priority.ALWAYS);
browser.load("http://stackoverflow.com");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
}
There is not a simple solution. In fact, there might not even be a solution at all short of building your own browser.
The key issue is interaction. If you want to display content only, then JEditorPane and many third party libs make that a more attainable goal. If you really need a user interacting with a webpage then either:
Have the user use a normal browser to interact
Build a GUI that makes calls to web services/urls to do the interaction, but the display is up to you.
On the returning the HTML side of things, it sounds like you are trying to capture history or refresh the page. In either case, it sounds like you are in the wrong technology. Either modify the original site, or add in some java script in the browser with Greasemonkey or something similar.
You may want to see to djproject. But possibly you'll find JavaFX usage easier.
Depending on stuff I don't know about your project this is either genious or moronic, but you could use a real browser in stead and instrument it with Selenium Webdriver. Only suggesting this as it appears from the other answer that you are going down a difficult path.
There's another question about extracting html with webdriver here. It's about using python, but webdriver has a java api as well.
I was able to get the executed html. I kept the alert statement after the html is loaded in JavaScript. I used webEngine.setOnAlert method to check if the alert was executed and then printed the html. I got the correct response. Below is the code
HTML
alert("ready");
JavaFx Application
webEngine.setOnAlert(new EventHandler<WebEvent<String>>(){
#Override
public void handle(WebEvent<String> event) {
//labelWebTitle.setText(webEngine.getTitle());
if("ready".equals(event.getData())){
//TODO: initialize
System.out.println("HTML Ready");
WebEngine engine = (WebEngine)event.getSource();
String html = (String) engine.executeScript("document.documentElement.outerHTML");
org.jsoup.nodes.Document doc = Jsoup.parse(html);
Element image = doc.getElementById("canvasImage");
System.out.println(image.attr("src"));
}
}
});