在 JavaFX 的
WebView
中,我正在努力检测 URL 的变化。
我在课堂上有这个方法:
public Object urlchange() {
engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
@Override
public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == Worker.State.SUCCEEDED) {
return engine.getLocation());
}
}
});
}
我正在尝试将它用于一个名为 loginbrowser 的对象,例如:
System.out.print(loginbrowser.urlchange());
你能看出我做错了什么吗?
(部分)你做错了什么
您在问题中提供的代码甚至无法编译。 ChangeListener 的更改方法是一个 void 函数,它不能返回任何值。
无论如何,在 Web 视图中加载内容是一个异步过程。如果您想要在 Web 视图加载后获取 Web 视图位置的值,则需要等待加载完成(在 JavaFX 应用程序线程上不建议这样做,因为这会挂起您的应用程序,直到加载完成),或者在回调中通知加载已完成(这就是您的侦听器正在执行的操作)。
(可能)你想做什么
将某些属性绑定到 Web 引擎的位置属性。例如:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.*;
import javafx.stage.Stage;
public class LocationViewer extends Application {
@Override
public void start(Stage stage) throws Exception {
Label location = new Label();
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load("http://www.fxexperience.com");
location.textProperty().bind(engine.locationProperty());
Scene scene = new Scene(new VBox(10, location, webView));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
只要 Web 视图的位置发生变化,上面的代码就会更新位置标签(通过运行代码然后单击一些链接来尝试)。如果您希望仅在页面成功加载后更新标签,那么您需要一个基于 WebView 状态的侦听器,例如:
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.*;
import javafx.stage.Stage;
public class LocationAfterLoadViewer extends Application {
@Override
public void start(Stage stage) throws Exception {
Label location = new Label();
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load("http://www.fxexperience.com");
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
if (Worker.State.SUCCEEDED.equals(newValue)) {
location.setText(engine.getLocation());
}
});
Scene scene = new Scene(new VBox(10, location, webView));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
如果您运行最后一个程序并单击某些链接,您会注意到它会延迟位置标签的更新,直到您单击的页面完全完成加载之后,而不是第一个程序一旦位置就更新标签变化,无论负载是否需要一段时间或确实有效。
其他问题的解答
如何在条件语句中使用标签中的 url 值?如果操作与原始操作发生了变化,我希望执行该操作。
location.textProperty().addListener((observable, oldValue, newValue) -> {
// perform required action.
});
2023 年更新
对于大多数用途,我认为上面原始答案中的信息今天仍然有效,并且足以满足大多数用途。这个答案只是添加了一些附加信息来解决巴勃罗的评论:
上面的代码不是完整的解决方案。由于 JavaScript 和 HTML5 历史记录功能,URL 可能会更改,而状态属性不会更改。
我相信这里可能指的是以下情况下位置发生的情况,历史API的推送状态用法:
但它也可能只是对历史对象的标准导航调用,例如 back() 或forward。
WebView 有一个 WebHistory Java 对象,可以深入了解当前会话的导航历史记录。它类似于 JavaScript/HTML 中的历史 API,并且 Java 对象可用于执行其中一些功能,例如在历史记录中向后或向前导航 Web 引擎。 WebHistory API 没有像 JavaScript 那样的
pushState()
API,但您可以从 Java 调用 JavaScript 历史记录 API 来调用它。
所以我组装了一个测试工具来尝试所有这些,看看它的行为和交互方式。
测试工具显示
pushState
调用操作历史记录的按钮。在示例屏幕截图中,您可以看到当页面加载到引擎中时,历史对象中的“上次访问”字段会更新。但如果导航没有导致加载,则不会更新。一个示例是导航到页面上的片段或使用 PushState() API。
在这种情况下,如果您根据成功的页面加载来监控位置,则此时您将不会收到位置更改的通知。
但是,如果您侦听 WebEngine 的位置属性的更改,则对 WebEngine 历史记录的操作即使通过 JavaScript 也将反映在 WebEngine 的位置属性的更改中,并且您将收到这些更改的通知。这可以在示例屏幕截图中看到,其中屏幕顶部的位置属性能够通过历史记录
pushState()
调用反映 JavaScript 中设置的位置。
请注意,即使屏幕顶部的位置字段显示
pushState()
结果,Web 引擎实际上并不会在 pushState()
调用上导航到该 URL,它只是 Web 应用程序操作历史记录和 url 的一种方式浏览器中的位置信息,以便它可以反映重要的 Web 应用程序状态更改(例如,通过单页应用程序中的 Ajax 调用更新页面内容,SPA)。因此,如果希望您的 Java 应用程序收到此类更改的通知,那么您的 Java 应用程序可以通过侦听 WebEngine 的 location 属性来实现。
测试线束代码
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper;
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.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;
import java.util.Date;
public class LocationWithHistoryViewer extends Application {
@Override
public void start(Stage stage) throws Exception {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load("https://openjfx.io/javadoc/20/");
HBox controls = createNavigationControls(engine);
HistoryView historyView = new HistoryView(
engine.getHistory()
);
Scene scene = new Scene(new VBox(10, controls, webView, historyView));
stage.setScene(scene);
stage.show();
}
@NotNull
private static HBox createNavigationControls(WebEngine engine) {
WebHistory history = engine.getHistory();
Button back = new Button("<");
back.setOnAction(e -> {
if (history.getCurrentIndex() > 0) {
// history navigation can be performed via either the Java WebHistory object or JavaScript calls.
// in either case the outcome is the same -> if the navigation results in a page load then
// monitoring the location on a successful load state will pick up the location change,
// if the navigation does not generate a page load, monitoring the location property
// of the webview will pick up the navigation.
//
// Most history navigations will generate a page load,
// with a counterexample being navigating forward to a location created by history.pushState.
// history.go(-1);
engine.executeScript("history.back()");
}
});
back.disableProperty().bind(
history.currentIndexProperty().lessThan(1)
);
Button forward = new Button(">");
forward.setOnAction(e -> {
if (history.getCurrentIndex() < history.getEntries().size()) {
// history.go(+1);
engine.executeScript("history.forward()");
}
});
forward.disableProperty().bind(
history.currentIndexProperty().isEqualTo(
Bindings.size(history.getEntries()).subtract(1)
)
);
// If you try to manipulate the url after navigate off of the openjfx.io site, it won't let you do that
// you will receive:
// Exception in thread "JavaFX Application Thread" netscape.javascript.JSException:
// SecurityError: Blocked attempt to use history.pushState()
// to change session history URL from https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveAction.html
// to https://openjfx.io/javadoc/20/javafx.web/javafx/scene/web/WebHistory.html#fake.
// Protocols, domains, ports, usernames, and passwords must match.
// This is an expected cross-domain security enforcement system built into WebView.
Button manipulateURL = new Button("Manipulate URL");
final String MANIPULATE_SCRIPT = // language = javascript
"""
document.title = "Manipulated title";
history.pushState({}, "", "https://openjfx.io/javadoc/20/javafx.web/javafx/scene/web/WebHistory.html#fake");
""";
manipulateURL.setOnAction(e ->
engine.executeScript(MANIPULATE_SCRIPT)
);
Label location = new Label();
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
HBox controls = new HBox(10,
back,
forward,
location,
spacer,
manipulateURL
);
controls.setAlignment(Pos.BASELINE_LEFT);
controls.setPadding(new Insets(3));
// monitoring load state will not pick up history manipulations which do not generate page loads (e.g. history.pushState JavaScript calls).
// engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
// if (Worker.State.SUCCEEDED.equals(newValue)) {
// location.setText(engine.getLocation());
// }
// });
// monitoring the location property will pick up history manipulations which do not generate page loads (e.g. history.pushState JavaScript calls).
location.textProperty().bind(engine.locationProperty());
return controls;
}
public static void main(String[] args) {
launch(args);
}
}
class HistoryView extends TableView<WebHistory.Entry> {
public HistoryView(WebHistory history) {
setItems(history.getEntries());
TableColumn<WebHistory.Entry, Date> lastVisitedColumn = new TableColumn<>("Last visited");
lastVisitedColumn.setCellValueFactory(param ->
param.getValue().lastVisitedDateProperty()
);
lastVisitedColumn.setStyle("-fx-font-family: monospace;");
lastVisitedColumn.setPrefWidth(240);
TableColumn<WebHistory.Entry, String> titleColumn = new TableColumn<>("Title");
titleColumn.setCellValueFactory(param ->
param.getValue().titleProperty()
);
titleColumn.setPrefWidth(300);
TableColumn<WebHistory.Entry, String> urlColumn = new TableColumn<>("URL");
urlColumn.setCellValueFactory(param ->
new ReadOnlyObjectWrapper<>(param.getValue().getUrl())
);
urlColumn.setPrefWidth(1_000);
//noinspection unchecked
getColumns().addAll(lastVisitedColumn, titleColumn, urlColumn);
}
}