Negara JavaFX docs bahwa WebView
siap ketika Worker.State.SUCCEEDED
tercapai Namun, kecuali jika Anda menunggu beberapa saat (yaitu Animation
, Transition
, PauseTransition
, dll), halaman kosong yang diberikan.
Ini menunjukkan bahwa ada peristiwa yang terjadi di dalam WebView yang menyiapkannya untuk ditangkap, tetapi apakah itu?
Ada lebih dari 7.000 cuplikan kode di GitHub yang menggunakanSwingFXUtils.fromFXImage
tetapi sebagian besar dari mereka tampaknya tidak berhubungan dengan WebView
, bersifat interaktif (topeng manusia kondisi ras) atau menggunakan Transisi sewenang-wenang (di mana saja dari 100 ms ke 2.000 ms).
Saya sudah mencoba:
Mendengarkan
changed(...)
dari dalamWebView
dimensi (DoubleProperty
implementasi properti tinggi dan lebarObservableValue
, yang dapat memonitor hal-hal ini)- 🚫Tidak layak. Terkadang, nilainya tampaknya berubah secara terpisah dari rutinitas cat, mengarah ke konten parsial.
Secara membabi buta mengatakan apa saja
runLater(...)
tentang FX Application Thread.- TechniquesBanyak teknik menggunakan ini, tetapi unit test saya sendiri (serta beberapa umpan balik yang bagus dari pengembang lain) menjelaskan bahwa acara sering kali berada di utas yang benar, dan panggilan ini berlebihan. Yang terbaik yang bisa saya pikirkan adalah menambahkan cukup banyak penundaan melalui antrian yang berfungsi untuk beberapa orang.
Menambahkan pendengar / pemicu DOM atau pendengar / pemicu JavaScript ke
WebView
- OthKedua JavaScript dan DOM tampaknya dimuat dengan benar saat
SUCCEEDED
dipanggil meskipun tangkapan kosong. Pendengar DOM / JavaScript sepertinya tidak membantu.
- OthKedua JavaScript dan DOM tampaknya dimuat dengan benar saat
Menggunakan
Animation
atauTransition
untuk "tidur" secara efektif tanpa memblokir utas FX utama.- Approach Pendekatan ini berhasil dan jika penundaannya cukup lama, dapat menghasilkan hingga 100% dari pengujian unit, tetapi waktu Transisi tampaknya menjadi beberapa saat di masa depan yang kami duga dan desainnya buruk. Untuk aplikasi berkinerja atau misi-kritis, ini memaksa programmer untuk membuat tradeoff antara kecepatan atau keandalan, baik pengalaman yang berpotensi buruk bagi pengguna.
Kapan waktu yang tepat untuk menelepon WebView.snapshot(...)
?
Pemakaian:
SnapshotRaceCondition.initialize();
BufferedImage bufferedImage = SnapshotRaceCondition.capture("<html style='background-color: red;'><h1>TEST</h1></html>");
/**
* Notes:
* - The color is to observe the otherwise non-obvious cropping that occurs
* with some techniques, such as `setPrefWidth`, `autosize`, etc.
* - Call this function in a loop and then display/write `BufferedImage` to
* to see strange behavior on subsequent calls.
* - Recommended, modify `<h1>TEST</h1` with a counter to see content from
* previous captures render much later.
*/
Cuplikan Kode:
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.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
public class SnapshotRaceCondition extends Application {
private static final Logger log = Logger.getLogger(SnapshotRaceCondition.class.getName());
// self reference
private static SnapshotRaceCondition instance = null;
// concurrent-safe containers for flags/exceptions/image data
private static AtomicBoolean started = new AtomicBoolean(false);
private static AtomicBoolean finished = new AtomicBoolean(true);
private static AtomicReference<Throwable> thrown = new AtomicReference<>(null);
private static AtomicReference<BufferedImage> capture = new AtomicReference<>(null);
// main javafx objects
private static WebView webView = null;
private static Stage stage = null;
// frequency for checking fx is started
private static final int STARTUP_TIMEOUT= 10; // seconds
private static final int STARTUP_SLEEP_INTERVAL = 250; // millis
// frequency for checking capture has occured
private static final int CAPTURE_SLEEP_INTERVAL = 10; // millis
/** Called by JavaFX thread */
public SnapshotRaceCondition() {
instance = this;
}
/** Starts JavaFX thread if not already running */
public static synchronized void initialize() throws IOException {
if (instance == null) {
new Thread(() -> Application.launch(SnapshotRaceCondition.class)).start();
}
for(int i = 0; i < (STARTUP_TIMEOUT * 1000); i += STARTUP_SLEEP_INTERVAL) {
if (started.get()) { break; }
log.fine("Waiting for JavaFX...");
try { Thread.sleep(STARTUP_SLEEP_INTERVAL); } catch(Exception ignore) {}
}
if (!started.get()) {
throw new IOException("JavaFX did not start");
}
}
@Override
public void start(Stage primaryStage) {
started.set(true);
log.fine("Started JavaFX, creating WebView...");
stage = primaryStage;
primaryStage.setScene(new Scene(webView = new WebView()));
// Add listener for SUCCEEDED
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
// Prevents JavaFX from shutting down when hiding window, useful for calling capture(...) in succession
Platform.setImplicitExit(false);
}
/** Listens for a SUCCEEDED state to activate image capture **/
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
capture.set(SwingFXUtils.fromFXImage(snapshot, null));
finished.set(true);
stage.hide();
}
};
/** Listen for failures **/
private static ChangeListener<Throwable> exceptListener = new ChangeListener<Throwable>() {
@Override
public void changed(ObservableValue<? extends Throwable> obs, Throwable oldExc, Throwable newExc) {
if (newExc != null) { thrown.set(newExc); }
}
};
/** Loads the specified HTML, triggering stateListener above **/
public static synchronized BufferedImage capture(final String html) throws Throwable {
capture.set(null);
thrown.set(null);
finished.set(false);
// run these actions on the JavaFX thread
Platform.runLater(new Thread(() -> {
try {
webView.getEngine().loadContent(html, "text/html");
stage.show(); // JDK-8087569: will not capture without showing stage
stage.toBack();
}
catch(Throwable t) {
thrown.set(t);
}
}));
// wait for capture to complete by monitoring our own finished flag
while(!finished.get() && thrown.get() == null) {
log.fine("Waiting on capture...");
try {
Thread.sleep(CAPTURE_SLEEP_INTERVAL);
}
catch(InterruptedException e) {
log.warning(e.getLocalizedMessage());
}
}
if (thrown.get() != null) {
throw thrown.get();
}
return capture.get();
}
}
Terkait:
- Cuplikan layar halaman web lengkap dimuat ke komponen JavaFX WebView, tidak hanya bagian yang terlihat
- Bisakah saya mengambil snapshot pemandangan secara terprogram?
- Tangkapan layar seluruh halaman, Java
- JavaFX 2.0+ WebView / WebEngine merender halaman web menjadi sebuah gambar
- Atur Tinggi dan Lebar Panggung dan Adegan di javafx
- JavaFX: cara mengubah ukuran panggung saat menggunakan tampilan web
- Ukuran Webview yang benar tertanam dalam Tabelcell
- https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/add-browser.htm#CEGDIBBI
- http://docs.oracle.com/javafx/2/swing/swing-fx-interoperability.htm#CHDIEEJE
- https://bugs.openjdk.java.net/browse/JDK-8126854
- https://bugs.openjdk.java.net/browse/JDK-8087569
Platform.runLater
telah diuji dan tidak memperbaikinya. Silakan coba sendiri jika Anda tidak setuju. Saya akan senang salah, itu akan menutup masalah.
SUCCEEDED
menyatakan status (yang pendengarnya ditembakan melalui thread FX) adalah teknik yang tepat. Jika ada cara untuk menampilkan acara yang antri, saya akan gembira untuk mencoba. Saya telah menemukan saran yang jarang melalui komentar di forum Oracle dan beberapa pertanyaan SO yang WebView
harus dijalankan dengan desain sendiri, jadi setelah beberapa hari pengujian saya memfokuskan energi di sana. Jika anggapan itu salah, bagus. Saya terbuka untuk saran yang masuk akal yang memperbaiki masalah tanpa menunggu waktu sewenang-wenang.
loadContent
metode atau saat memuat URL file.