I'm unable to capture a custom Javascript event in a C# WPF mshtml WebBrowser control. I've created the example code below. A button click raises a custom event. I realise that there are easy ways to capture a button click event. I need to use a custom event. I've used a button just for this example and testing. What am I doing wrong?
XAML file:
<Window x:Class="WebEvent2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<WebBrowser Name="webBrowser1" Loaded="webBrowser1_Loaded" LoadCompleted="webBrowser1_LoadCompleted"></WebBrowser>
</Grid>
</Window>
C# file:
namespace WebEvent2{
public partial class MainWindow : Window{
public MainWindow(){
InitializeComponent();
}
private void webBrowser1_Loaded(object sender, RoutedEventArgs e{
webBrowser1.Navigate(#"file:///D:/test.html");
}
private void webBrowser1_LoadCompleted(object sender, NavigationEventArgs e){
try{
var evtListener = new EventListener();
var window = ((IHTMLDocument2)webBrowser1.Document).parentWindow as IHTMLWindow3;
window.attachEvent("MyCustomEvent", evtListener);
// Also not working:
// ((HTMLDocument)webBrowser1.Document).attachEvent("MyCustomEvent", evtListener);
}catch (UnauthorizedAccessException err){
Console.WriteLine("OOPS: " + err);
}
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class EventListener{
[DispId(0)]
public void handler(IHTMLEventObj evt){
MessageBox.Show("message received");
}
}
}
}
HTML file:
<html>
<head>
<title>Test page</title>
<script>
function sendCustomEvent() {
var event;
if (typeof(Event) === 'function') {
event = new Event('MyCustomEvent');
} else {
event = document.createEvent('HTMLEvents');
event.initEvent('MyCustomEvent', true, false);
}
document.dispatchEvent(event);
}
</script>
</head>
<body>
<button onclick="sendCustomEvent()">Send custom event</button>
</body>
</html>
I was able to achieve the required functionality by using the WebBrowser controls 'ObjectForScripting' property. Nice and simple.
XAML remains the same as above.
WebInterface.cs:
namespace WebEvent2
{
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class WebInterface
{
public void callMe()
{
MessageBox.Show("hello");
}
}
}
MainWindow.xaml.cs:
namespace WebEvent2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
WebInterface wi = new WebInterface();
private void webBrowser1_Loaded(object sender, RoutedEventArgs e)
{
webBrowser1.ObjectForScripting = wi;
webBrowser1.Navigate(#"file:///D:/test.html");
}
}
}
test.html:
<html>
<head>
<title>Test page</title>
</head>
<body>
<button onclick="window.external.callMe()">Send custom event</button>
</body>
</html>
Related
So I got a problem, where there is a button that will call a webview function
The button is the one with id play in the index.html that will call the javascript playVideo function in src.js, where the playVideo function will notify the webview that the button has been pressed to check the condition in the Java Function.
How do I achieve this?
The codes are below to help getting context
index.html
<!DOCTYPE html>
<html>
<head>
<title> Video</title>
<script type="text/javascript" src="src.js"></script>
</head>
<body bgcolor="#333">
<center>
<video preload="auto" width="480" height="270"
#Embed-video
</video>
<br>
<br>
<div>
<a class="first" href="#" id="play">Play</a>
<a class="second" href="#" id="pause">Pause</a>
</div>
</center>
Javascript code
window.onload = function(){
var video = document.getElementById('my-video');
var play = document.getElementById('play');
var pause = document.getElementById('pause');
// associate functions with the 'onclick' events
play.onclick = playVideo;
pause.onclick = pauseVideo;
function playVideo(e) {
//call java function
}
function pauseVideo(e) {
}
}
Java Function
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(x,y) {
if (js.playVideo()) {
//do something
return true;
}else{
return false;
}
}
});
You should use annotation for Javascript like the following example
// Java File example
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, "show toast from web: " + toast, Toast.LENGTH_SHORT).show();
}
// Js file example
function showAndroidToast(msg) {
Android.showToast(msg);
}
Thus, you should try like the following code.
function playVideo(e) {
//call java function
Android.doSomething();
}
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(x,y) {
// implment your code with doSomething func here
if (doSomething()) {
//do something
return true;
}else{
return false;
}
}
});
#JavascriptInterface
public boolean doSomething() {
// do something
if(isPlaying) return true;
return false;
}
I am able to get HTML source code using following code. but when I am trying with https://marriott.medallia.com/sso/marriott/homepage.do?v=bnAaQvo3*lVHsqtnwluPh_CMCsIHyFkti&alreftoken=6d0d31c7eb7583b964d0ecb89b55e12b
The page URL is getting changed dynamically and on next generated page when I see source view I only get the following code in the HTML body:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IdP Selection</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="style.min.css">
</head>
<body>
<div id="app-container" class="app-container"></div>
<script>
AppContext = {
idps: '[{"entityId":"MI-PROD-SAML2-IDP-MEDALLIA","name":"Marriott International (any associate w/ EID)"},{"entityId":"https://identity.starwoodhotels.com","name":"Starwood Hotels"}]'
};
</script>
<script src="main.min.js"></script>
</body>
</html>
when I inspect on generated radio button I am able to get HTML element in browser developer elements tab.
My C# code is as follows:
public Form1()
{
InitializeComponent();
this.webBrowser1.ObjectForScripting = new MyScript();
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
webBrowser1.Navigate("javascript: window.external.CallServerSideCode();");
}
[ComVisible(true)]
public class MyScript
{
public void CallServerSideCode()
{
var doc = ((Form1)Application.OpenForms[0]).webBrowser1.Document;
var renderedHtml = doc.GetElementsByTagName("HTML")[0].OuterHtml;
var marelement = doc.GetElementById("MI-PROD-SAML2-IDP-MEDALLIA");
HtmlElementCollection eCollections = doc.GetElementsByTagName("HTML");
string strDoc = eCollections[0].OuterHtml;
}
}
I think It's because of ajax! After ajax previous handler of element is not updates and you should attach handler on OnPropertyChanged event:
var element = webBrowser.Document.GetElementsByTagName("HTML")[0];
element != null ? element.AttachEventHandler("onpropertychange", handler) : return;
private string renderedHtml;
private void handler(Object sender, EventArgs e)
{
var element = webBrowser.Document.GetElementsByTagName("HTML")[0];
if (element != null)
renderedHtml = element.OuterHtml;
}
So the page is rendered by ReactJS so you are going to have a bad time getting this to work. The best thing I can think of is creating a something that 'waits' for the element to be created in the WebBrowserControl....
!(function() {
function check(){
if(!document.getElementById("MI-PROD-SAML2-IDP-MEDALLIA")) {
setTimeout(check, 100);
} else {
window.external.CallServerSideCode();
}
}
check();
}());
Which can then be minified to something you can use...
webBrowser1.Navigate(#"javascript:!(function(){function c(){if(!document.getElementById('MI-PROD-SAML2-IDP-MEDALLIA')){setTimeout(c, 100);}else{window.external.CallServerSideCode();}}c();}());");
I want to dynamically load and interact with SVG image into Android WebView using javascript and HTML5. The below is the html source code
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="obj_idset.js"></script>
<script type="text/javascript" src="global.js"></script>
<script type="text/javascript" src="map_zoom_pan_effects.js"></script>
<script type="text/javascript" src="client-menu_position.js"></script>
<script type="text/javascript" src="client-menu_routing.js"></script>
<script type="text/javascript" src="obj_positionpoint.js"></script>
<script type="text/javascript" src="obj_borderline.js"></script>
<script type="text/javascript" src="obj_borderpoint.js"></script>
<script type="text/javascript" src="obj_edge.js"></script>
<script type="text/javascript" src="obj_polygon.js"></script>
<script type="text/javascript" src="obj_stepmarker.js"></script>
<script type="text/javascript" src="obj_vertex.js"></script>
<script type="text/javascript" src="obj_category.js"></script>
<script type="text/javascript" src="obj_gpsmarker.js"></script>
<script type="text/javascript" src="menu_import.js"></script>
<script type="text/javascript" src="routing.js"></script>
<script type="text/javascript" src="affiliation_match.js"></script>
<script type="text/javascript" src="android.js"></script>
<script type="text/javascript" src="interface.js"></script>
<script type="text/javascript" src="demowalk.js"></script>
<script type="text/javascript" src="convertCoordinates.js"></script>
<script>
$("embed").ready(function() {
// Replaces onload () for initialization may take place only after the DOM has been built completely.
console.log('version 24');
//G.loadMaps(false);
G.init();
G.loadMaps(0);
console.log("???");
});
</script>
</head>
<body>
<div id="location"></div>
<!-- Inside this div the map will be rendered -->
<div id="map_container"></div>
<!-- <div id="page-wrapper">
<h1>Text File Reader</h1>
<div>
Select a text file:
<input type="file" id="fileInput">
</div>
<pre id="fileDisplayArea"><pre>
</div>
-->
<div id="mainmenu">
<p>Set the position</p>
<!-- <button onclick="menu('top');">top</button> -->
<button onclick="Interface.position_setSVG (125,125,0);">Set postion</button>
</div>
</body>
</html>
As above html script, the html web page includes a render SVG map which is dynamically load into element and a "Set position" button as below in desktop Chrome.
Now I want to display the same in Android WebView (all java script files and svg files are placed in Android assests folder). The below is Android code in main Fragment
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
private SvgWebView mWebView;
private Button positionBtn;
private Button destinationBtn;
private Button routingBtn;
private Button clearBtn;
public PlaceholderFragment() {
}
#Override
#SuppressLint({ "JavascriptInterface", "SetJavaScriptEnabled" })
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
mWebView = new SvgWebView((WebView) rootView.findViewById(R.id.webView1));
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setBuiltInZoomControls(false);
mWebView.getSettings().setUseWideViewPort(true);
mWebView.getSettings().setSupportZoom(true);
mWebView.getWebView().setInitialScale(0);
mWebView.getWebView().setWebChromeClient(new WebChromeClient());
// http://stackoverflow.com/questions/2378800/clicking-urls-opens-default-browser
mWebView.getWebView().setWebViewClient(new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
CustomJavaScriptHandler js = new CustomJavaScriptHandler(getActivity());
mWebView.getWebView().addJavascriptInterface(js, "svgapp");
js.addInstructor(new CustomJavaScriptHandler.JSInstructor() {
#Override
public void jsinstruct(String s) {
String parts[] = s.split(":");
if (parts[0].equals("position")) {
//nodeSelected(Integer.valueOf(parts[1]));
}
}
});
CopyFilesAsyncTask copyTask = new CopyFilesAsyncTask();
copyTask.execute();
positionBtn = (Button) rootView.findViewById(R.id.positionBtn);
positionBtn.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
mWebView.svgPositionBySvg(100.0, 200.0, 0);
}
});
destinationBtn = (Button) rootView.findViewById(R.id.destinationBtn);
routingBtn = (Button) rootView.findViewById(R.id.routingBtn);
clearBtn = (Button) rootView.findViewById(R.id.clearBtn);
return rootView;
}
/* the function is called after copying files from assest folder to external device */
public void copyFinished() {
try {
Toast.makeText(getActivity(), "Copy done", Toast.LENGTH_LONG).show();
String htmlPath = Utils.getSdDir(Utils.SVG_MAP_DATA_EXT_FOLDER).toURI().toURL().toString() + File.separator + Utils.ANDROID_HTML;
// load the android.html
mWebView.loadUrl(htmlPath);
} catch (MalformedURLException e) {
Log.e(TAG, "Error ", e);
}
}
class CopyFilesAsyncTask extends AsyncTask<Void, Void, Void> {
public CopyFilesAsyncTask() {
}
#Override
protected Void doInBackground(Void... arg0) {
AssetManager assetManager = getActivity().getAssets();
copyAssestsToExtDir(assetManager, Utils.SVG_MAP_DATA_ASSEST_FOLDER);
return null;
}
#Override
protected void onPostExecute(Void arg0) {
copyFinished();
}
}
/* copy files in assest to external dir when the application start */
private void copyAssestsToExtDir(AssetManager assetManager, String subfolder) {
String[] files = null;
try {
files = assetManager.list(subfolder);
} catch (IOException e) {
Log.e(TAG, "Failed to get asset file list.", e);
}
for(String filename : files) {
Log.i(TAG, "File: " + filename);
// this is file, hence copy
InputStream instr = null;
OutputStream outstr = null;
try {
instr = assetManager.open(subfolder + File.separator + filename);
File out_dir = Utils.getSdDir(Utils.SVG_MAP_DATA_EXT_FOLDER);
File outFile = new File(out_dir, filename);
outstr = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = instr.read(buffer)) != -1) {
outstr.write(buffer, 0, read);
}
outstr.flush();
instr.close();
outstr.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
The thing is that when running in Android, the WebView does not load svg image into its element. It calls default Android Web Browser to display the svg image as below pictures
As above pictures, the WebView only display the "Set postion" button, it call Android web Browser to display SVG. Hence, I could not interact with loaded SVG because SVG does not include any javascript itself (javascript files are embeded into html file not svg).
Am I missing something? Could anyone give me the solution to solve this problem?
The problem is Android 4.4 KitKat.
If the above application run on 4.2 or below, it works well. But if running in 4.4, Android will call Android browser to display the embed svg image.
Due to some security feature, KitKat does not allow to load file stored in internal device. You must place the file on a external server.
I'm trying to start an activity from a javascript interface in my webview.
The example shows a toast. How could i call a class instead of a toast?
public class JavaScriptInterface {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
this for the html page.
<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
<script type="text/javascript">
function showAndroidToast(toast) {
Android.showToast(toast);
}
You have to first register the JavaScriptInterface on your webview.
JavaScriptInterFace can be a inner class as shown below. This class will have a function that you can call from html page( via javaScript ) and inside this function you can write code to change activity.
Here is the working solution for you:
public class JavascriptInterfaceActivity extends Activity {
/** Called when the activity is first created. */
WebView wv;
JavaScriptInterface JSInterface;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
wv = (WebView)findViewById(R.id.webView1);
wv.getSettings().setJavaScriptEnabled(true);
// register class containing methods to be exposed to JavaScript
JSInterface = new JavaScriptInterface(this);
wv.addJavascriptInterface(JSInterface, "JSInterface");
wv.loadUrl("file:///android_asset/myPage.html");
}
public class JavaScriptInterface {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
#android.webkit.JavascriptInterface
public void changeActivity()
{
Intent i = new Intent(JavascriptInterfaceActivity.this, nextActivity.class);
startActivity(i);
finish();
}
}
}
Here is the html page
<html>
<head>
<script type="text/javascript">
function displaymessage()
{
JSInterface.changeActivity();
}
</script>
</head>
<body>
<form>
<input type="button" value="Click me!" onclick="displaymessage()" />
</form>
</body>
</html>
Hope this helps...
You also need to add the #android.webkit.JavascriptInterface annotation on top of your changeActivity method in your android code, should you run on Android 4.2 or higher.
See this link for more.
This is the entire Java code I've used. I will explain in more detail below...
public class Test7 extends Activity {
//debug
private final static String TAG = "JSInterface";
private WebView wv;
private class JSInterface {
private WebView wv;
// Variables to manage interfacing with JS
private String returnValue;
private boolean canReadReturnValue;
private Lock lockOnJS;
private Condition condVarOnJS;
public JSInterface (WebView wv) {
this.wv = wv;
this.canReadReturnValue = false;
this.lockOnJS = new ReentrantLock();
this.condVarOnJS = lockOnJS.newCondition();
}
public void setReturnValue(String ret) {
lockOnJS.lock();
returnValue = ret;
canReadReturnValue = true;
condVarOnJS.signal();
lockOnJS.unlock();
Log.d(TAG, "returnValue = " + returnValue);
}
public String getReturnValue() {
Log.d(TAG, "enter in getReturnValue");
lockOnJS.lock();
while (!canReadReturnValue) {
try {
Log.d(TAG, "get wait...");
condVarOnJS.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lockOnJS.unlock();
Log.d(TAG, "returnValue: " + returnValue);
return returnValue;
}
public String getNewString() {
wv.loadUrl("javascript:JSInterface.setReturnValue(createNewString())");
return getReturnValue();
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
wv = (WebView) findViewById(R.id.webView1);
wv.getSettings().setJavaScriptEnabled(true);
wv.addJavascriptInterface(new JSInterface(wv), "JSInterface");
wv.loadUrl("file:///android_asset/prova7.html");
}
public void button1(View v) {
wv.loadUrl("javascript:func('1')");
}
}
And it seems work fine.
You can see that I've got a button (that we can call button1), and clicking on it, it tries to execute a JS method, called func().
public void button1(View v) {
wv.loadUrl("javascript:func('1')");
}
Inside this JS method, I have to call another Java method. This is the code:
function func(id) {
document.getElementById(id).innerHTML = JSInterface.getNewString();
}
I need to return the result of JSInterface.getNewString() to the innerHTML variable.
The code of JSInterface.getNewString() is this:
public String getNewString() {
wv.loadUrl("javascript:JSInterface.setReturnValue(createNewString())");
return getReturnValue();
}
You can see that I use the method setReturnValue and getReturnValue to return the value returned by another JS method. This is the code:
function createNewString() {
return "my New String";
}
The problem is that when I try to set the returnValue, the function createNewString is never executed! If I add a console.log() line, my logCat display nothing!
I cannot understand why this happens.
All the javascript and your JSInterface methods called from javascript are running on the single thread in Android WebView. So while you are waiting in condVarOnJS.await() no javascript can be executed, just because it is executed on the same thread.
Moreover, all the webview instances in your application share the same javascript thread.
In Internet Explorer I found the same problem. You can use setTimeout like this:
function func(id) {
setTimeout(
function(){
document.getElementById(id).innerHTML = JSInterface.getNewString();
},
500);
}
I did the functionality what you intend in that code,for me createNewString() is called,
I will show up the code i used,
In java
,
public String videoPlay(){
System.out.println("videoPlay");
mWebView.loadUrl("javascript:window.demo.setReturnValue(createNewString())");
return getReturnValue();}
public void setReturnValue(String test){
rotValue=test;
System.out.println(test);
}
public String getReturnValue(){
System.out.println("get"+rotValue);
return rotValue;
}
in HTML,
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script>
function inform(){
alert('et');
document.getElementById('myText').value=window.demo.videoPlay();
alert('et');
}
function createNewString() {
return "my New String";
}
</script>
</head>
<body onload="init()">
<form>
<input type='text' id='myText' />
<input type="button" name="test" value="Click me" onclick="inform()">
</form>
</body>
</html>
The function getter and setter called and values also set, but i have the output log as..
11-08 19:18:17.155: INFO/System.out(847): videoPlay
11-08 19:18:17.165: INFO/System.out(847): getnull
11-08 19:18:17.875: INFO/System.out(847): my New String
videoPlay called from JS and createnewString() also called from java to JS , but it returns the value before it set , because i don`t what is the purpose to use lock , even i tried using lock as you did for that it will print
11-08 19:18:17.155: INFO/System.out(847): videoPlay
11-08 19:18:17.165: INFO/System.out(847): getnull
using lock also the function callback works in wrong manner, you need work on locks.