В JavaFX DOCS состояние , что WebView
готова , когда Worker.State.SUCCEEDED
будет достигнуто , однако, если вы будете ждать некоторое время (то есть Animation
, Transition
, PauseTransition
и т.д.), пустая страница отображается.
Это говорит о том, что внутри WebView происходит событие, готовящее его к записи, но что это?
На GitHubSwingFXUtils.fromFXImage
есть более 7000 фрагментов кода, которые используют, но большинство из них, по-видимому, либо не связаны, либо WebView
интерактивны (человек маскирует состояние гонки), либо используют произвольные переходы (где-то от 100 мс до 2000 мс).
Я пробовал:
Прослушивание
changed(...)
изнутриWebView
измерений (DoubleProperty
реализуются свойства высоты и шириныObservableValue
, которые могут отслеживать эти вещи)- VНе жизнеспособно. Иногда кажется, что значение меняется отдельно от процедуры рисования, что приводит к частичному содержанию.
Слепо рассказывать все и вся в
runLater(...)
теме приложений FX.- TechniquesМногие методы используют это, но мои собственные модульные тесты (а также некоторые отличные отзывы от других разработчиков) объясняют, что события часто уже находятся в нужном потоке, и этот вызов избыточен. Лучшее, что я могу придумать, - это добавить лишь достаточную задержку в очереди, чтобы она работала для некоторых.
Добавление прослушивателя / триггера DOM или прослушивателя / триггера JavaScript к
WebView
- JavaScriptОдно JavaScript и DOM загружаются должным образом при
SUCCEEDED
вызове, несмотря на пустой захват. Слушатели DOM / JavaScript, похоже, не помогают.
- JavaScriptОдно JavaScript и DOM загружаются должным образом при
Использование
Animation
илиTransition
для эффективного "сна", не блокируя основной поток FX.- Approach Этот подход работает, и, если задержка достаточно велика, может дать до 100% модульных тестов, но время перехода, похоже, будет неким будущим моментом, о котором мы только догадываемся, и плохим дизайном. Для высокопроизводительных или критически важных приложений это вынуждает программиста находить компромисс между скоростью и надежностью, которые потенциально могут быть плохими для пользователя.
Когда хорошее время для звонка WebView.snapshot(...)
?
Применение:
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.
*/
Фрагмент кода:
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();
}
}
Связанные с:
- Снимок экрана полной веб-страницы, загруженной в компонент JavaFX WebView, а не только видимой части
- Могу ли я сделать снимок сцены программно?
- Скриншот всей страницы, Java
- JavaFX 2.0+ WebView / WebEngine отображает веб-страницу в изображение
- Установите высоту и ширину сцены и сцены в javafx
- JavaFX: как изменить размер сцены при использовании веб-просмотра
- Правильный размер Webview, встроенного в 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
был проверен и не исправляет это. Пожалуйста, попробуйте сами, если вы не согласны. Я был бы рад ошибаться, это закрыло бы проблему.SUCCEEDED
состояние (из которого слушатель запускает поток FX) является надлежащей техникой. Если есть способ показать события в очереди, я был бы рад попробовать. Я нашел редкие предложения в комментариях на форумах Oracle и некоторых SO-вопросах, которыеWebView
должны разрабатываться в своей собственной ветке, поэтому после нескольких дней тестирования я сосредоточился на них. Если это предположение неверно, отлично. Я открыт для любых разумных предложений, которые решат проблему без произвольного времени ожидания.loadContent
метода или при загрузке файла URL.Ответы:
Кажется, это ошибка, которая возникает при использовании
loadContent
методов WebEngine . Это также происходит при использованииload
для загрузки локального файла, но в этом случае вызов reload () компенсирует это.Кроме того, поскольку сценарий должен отображаться при создании снимка, необходимо позвонить
show()
перед загрузкой содержимого. Поскольку контент загружается асинхронно, вполне возможно, что он будет загружен до оператора, следующего за вызовомload
или после егоloadContent
завершения.Обходной путь заключается в том, чтобы поместить содержимое в файл и вызвать метод WebEngine
reload()
ровно один раз. Во второй раз, когда содержимое загружается, снимок может быть успешно получен из прослушивателя свойства состояния загрузчика.Обычно это было бы легко:
Но поскольку вы используете
static
для всего, вам придется добавить несколько полей:И вы можете использовать их здесь:
И тогда вам придется сбрасывать его каждый раз, когда вы загружаете контент:
Обратите внимание, что существуют лучшие способы выполнения многопоточной обработки. Вместо использования атомарных классов, вы можете просто использовать
volatile
поля:(логические поля по умолчанию имеют значение false, а объектные поля по умолчанию равны нулю. В отличие от программ на C, это жесткая гарантия, предоставляемая Java; не существует такой вещи, как неинициализированная память.)
Вместо того, чтобы опрашивать в цикле изменения, сделанные в другом потоке, лучше использовать синхронизацию, блокировку или класс более высокого уровня, например CountDownLatch, который использует эти вещи внутренне:
reloaded
не объявляется как volatile, так как доступ к нему осуществляется только в потоке приложения JavaFX.источник
volatile
переменными. К сожалению, звонокWebEngine.reload()
и ожидание последующегоSUCCEEDED
не работает. Если я помещаю счетчик в контент HTML, я получаю:0, 0, 1, 3, 3, 5
вместо того0, 1, 2, 3, 4, 5
, чтобы предположить, что он на самом деле не исправляет основное состояние гонки.CountDownLatch
». Upvoting, потому что эту информацию было нелегко найти, и она помогает ускорить и упростить код при первом запуске FX.Чтобы приспособиться к изменению размера, а также к базовому поведению снимка, я (мы) разработали следующее рабочее решение. Обратите внимание, что эти тесты были проведены в 2000 раз (Windows, macOS и Linux), обеспечивая случайные размеры WebView с 100% успехом.
Сначала я процитирую одного из разработчиков JavaFX. Это цитата из частного (спонсируемого) сообщения об ошибке:
600
если высота точно0
. ПовторноеWebView
использование кода следует использоватьsetMinHeight(1)
,setPrefHeight(1)
чтобы избежать этой проблемы. Этого нет в приведенном ниже коде, но стоит упомянуть любого, кто адаптирует его к своему проекту.источник