我想使用WebEngine(JavaFX)在网页上执行一些JavaScript(和jQuery)并处理结果(使用Java代码)。
我有回调函数的问题,它们自己执行一些脚本。
为了尽可能简单地说明我的问题,我制作了一个显示不良结果的最小化代码(它只是一个更大项目的一部分)。
所以,我创建了三个类:
浏览器
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class Browser extends Application {
private static WebEngine webEngine;
private static JSObject window;
@Override
public void start(Stage primaryStage) throws Exception {
WebView browser = new WebView();
webEngine = browser.getEngine();
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> observable, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
window = (JSObject) webEngine.executeScript("window;");
// The following lines inject jQuery (for pages that don't use already)
webEngine.executeScript("var script = document.createElement(\"script\");");
webEngine.executeScript("script.src = \"http://code.jquery.com/jquery-1.12.0.min.js\";");
webEngine.executeScript("document.getElementsByTagName(\"body\")[0].appendChild(script);");
}
}
});
primaryStage.setScene(new Scene(browser));
primaryStage.show();
//Platform.setImplicitExit(false);
}
public static JSObject getWindow() {
return window;
}
public static Object executeScript(String script) throws InterruptedException, ExecutionException {
FutureTask<Object> task = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return webEngine.executeScript(script);
}
});
Platform.runLater(task);
return task.get();
}
public static void load(String url) {
Platform.runLater(new Runnable() {
@Override
public void run() {
webEngine.load(url);
}
});
}
public static void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Application.launch(Browser.class);
} catch (IllegalStateException e) {}
}
}).start();
}
}
JQueryFunction
import java.util.concurrent.ExecutionException;
public class JQueryFunction {
public String function1(int index, String text) {
return (index+1) + ": " + text;
}
public String function2(int index, String text) throws InterruptedException, ExecutionException {
return (String) Browser.executeScript("$($(\".BlueArrows:eq(2) li\").find(\"a:first\")[index]).text();");
}
}
测试
public class Test {
public static void main(String[] args) throws Exception {
Browser.start();
Thread.sleep(1000); // Only used here to simplify the code
Browser.load("https://docs.oracle.com/javase/tutorial/");
Thread.sleep(3500); // Only used here to simplify the code
Browser.getWindow().setMember("javaApp", new JQueryFunction());
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function1(index, text); });");
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function2(index, text); });");
}
}
当我运行测试时,第一个executeScript
按预期运行并更改某些元素的文本(添加索引编号)。
但第二个 executeScript
永远卡住并坚持使用GUI,实际上是整个JavaFX应用程序。
我理解为什么会发生这种情况......
executeScript
方法调用WebEngine(通过 Platform.runLater
)来执行jQuery迭代元素并调用Java函数(每次使用不同的参数)。
第一次执行(调用function1)从function1获取返回的String并将其应用于元素的文本。 (完全符合预期)!
第二次执行(调用function2)执行调用Java函数的jQuery函数,但Java函数需要执行更多的JavaScript(或jQuery),但WebEngine在第一次执行之前不会执行完了。
但是第一次执行不会完成,因为它取决于第二次执行的结果。
WebEngine的编程方式是,他只在一个线程中执行(在FX线程内)一个接一个的任务(串行)。
有什么方法可以解决吗?
为什么WebEngine仅限于在JavaFX应用程序下工作?
为什么它只能用一个线程?
答案 0 :(得分:3)
避免&#34;死锁的一种可能方法&#34;您正在体验的是当您已经在GUI线程(JavaFX应用程序线程)中运行时,不会调用 Private Sub SelectClinic_Load(sender As Object, e As EventArgs) Handles MyBase.Load
clNameDGV.DefaultCellStyle.Font = New Font("Calibri", 15, FontStyle.Regular, GraphicsUnit.Point)
clNameDGV.RowHeadersVisible = False
clNameDGV.ColumnHeadersVisible = False
clNameDGV.ReadOnly = True
clNameDGV.Location = New Point((Me.Width - clNameDGV.Width) / 2, (Me.Height - clNameDGV.Height) / 2)
ChClinic()
End Sub
Private Sub ChClinic()
conn = New SqlConnection("Server=(Local);Database=DrDB;user=Tarak;Trusted_Connection=True;")
conn.Open()
Dim comStr As String = "Select Count(*) from Clinicinfo"
Dim comm As New SqlCommand(comStr, conn)
Dim i As Integer = comm.ExecuteScalar
'CHECKING IF ANY CLINIC INFORMATION IS FOUND
If i = 0 Then
If MessageBox.Show("No clinic information found." + vbNewLine + "Will you like to create new clinic info right now ?", "No Clinic Info Found", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = vbYes Then
Dim nf As New ClinicDetails
nf.TopMost = True
nf.ShowDialog(Me)
Else
Me.Close()
End If
End If
'LOADING CLINIC NAMES INTO DATAGRIDVIEW
Dim comStr3 As String = "Select Count(*) from Login_Detail"
Dim comm3 As New SqlCommand(comStr3, conn)
Dim iCh As Integer = comm3.ExecuteScalar
'MsgBox(iCh)
Dim comStr2 As String
If iCh = 0 Then
comStr2 = "Select Clinic_Name from Clinicinfo"
Else
comStr2 = "Select DISTINCT Clinic_Name from Clinicinfo Where Clinic_Status = 'Active'"
End If
Dim comm2 As New SqlCommand(comStr2, conn)
Dim rd As SqlDataReader
Dim dt As New DataTable
rd = comm2.ExecuteReader
dt.Load(rd)
'clNameDGV.Columns.Clear()
clNameDGV.AllowUserToAddRows = False
clNameDGV.AutoGenerateColumns = True
clNameDGV.DataSource = dt
clNameDGV.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells)
clNameDGV.Refresh()
clNameDGV.Width = clNameDGV.Columns(0).Width
clNameDGV.Location = New Point((Me.Width - clNameDGV.Width) / 2, (Me.Height - clNameDGV.Height) / 2)
rd.Close()
conn.Close()
End Sub
。类似的东西:
Platform.runLater