I'm evaluating the JavaScript/Silverlight interop capabilities and have been able to create a Silverlight instance using JavaScript and call methods on it. However, I now need a way of passing a JavaScript callback function to Silverlight.
Simply passing a JavaScript function to a Silverlight method expecting an Action doesn't work although the error suggest that it's intended to. What am I missing? The exception details:
Microsoft JScript runtime error: System.ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method, Boolean throwOnBindFailure)
at System.Windows.Hosting.ScriptingInterface.GetDelegateForScriptObject(Type eventHandlerType, ScriptObject obj)
at System.Windows.Browser.ScriptObject.ConvertTo(Type targetType, Boolean allowSerialization)
at System.Windows.Hosting.ScriptingInterface.GetScriptParamValueForType(ScriptParam scriptParam, Type desiredType)
at System.Windows.Hosting.ScriptingInterface.ConvertFromScriptParams(ParameterInfo[] parameters, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.ScriptMethod.Invoke(ManagedObject obj, InvokeType invokeType, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.Invoke(ManagedObject obj, InvokeType invokeType, String memberName, ScriptParam[] args)
at System.Windows.Hosting.ManagedHost.InvokeScriptableMember(IntPtr pHandle, Int32 nMemberID, Int32 nInvokeType, Int32 nArgCount, ScriptParam[] pArgs, ScriptParam& pResult, ExceptionInfo& pExcepInfo)
Without seeing your code I cannot say what you are doing wrong but I can describe what has worked for me in the past.
On the Silverlight side you need to register a class as a scriptable object. Then create a method that is marked as a ScriptableMember and takes a string that will be your passed in JavaScript method. I also added a method called InvokeCallback which will invoke the passed in javascript callback.
[ScriptableType]
public partial class Page : UserControl
{
private string jsCallback;
public Page()
{
InitializeComponent();
HtmlPage.RegisterScriptableObject("silverlightInterop", this);
}
[ScriptableMember]
public void RegisterCallback(string callback)
{
jsCallback = callback;
}
public boid InvokeCallback(string arg)
{
if(!string.IsNullOrEmpty(jsCallback))
{
System.Windows.Browser.HtmlPage.Window.Invoke(jsCallback, arg);
}
}
}
On the JavaScript side you can call the RegisterCallback method that you defined in Silverlight by grabbing the silverlight object on the page and calling the method off of the name "silverlightInterop" which we registered as the name of our scriptable object.
function jsCallback(someArg) {
alert(someArg);
}
var silverLightControl = document.getElementById("silverlightControl");
silverLightControl.content.silverlightInterop.RegisterCallback("jsCallback");
I hope this helps. I also have some sample code the demonstrates this which I wrote a while ago here
There is a simpler way to pass Javascript function to Silverlight. All values from javascript can be represented by the ScriptObject type, this includes a function. Its for this reason that the ScriptObject has a InvokeSelf method.
You can create a property as simple as:-
[ScriptableMember]
public ScriptObject Callback { get; set; }
Now lets say in Javascript we have this function:-
function sayHello(name)
{
alert("Hello " + name);
}
We can assign this to the property (assume the RegisterScriptableObject("Page", this) has been done) with this JS:-
document.getElementById("mySL").Content.Page.Callback = sayHello;
Now in Silverlight code we can invoke this Callback with:-
Callback.InvokeSelf("Freed");
Apparently, this does work if the using a delegate type of EventHandler (or EventHandler<>), but not for other delegate types.
My Code:
<!-- language: JavaScript -->
function sendText() {
return "Hi from Javascript!";
}
<!-- language: C# -->
string obj = HtmlPage.Window.Invoke("sendText", null) as string;
txtReturnData.Text = obj;
<!-- language: VB.Net -->
Dim obj As String = TryCast(HtmlPage.Window.Invoke("sendText", Nothing), String)
txtReturnData.Text = obj
Related
I have created an android WebView, and injected javascript interface using addJavascriptInterface(mObject, "jsinterface"). It works fine until I create an object with same name (jsinterface) in JavaScript using the new operator.
My Java Code:
WebView mWebView = findViewById(R.id.myWebView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebChromeClient(new MyWebChromeClient((Activity)mContext));
mWebView.addJavascriptInterface(new testClass(), "jsinterface");
mWebView.loadUrl("UrlToLoad");
testClass.java
public class testClass{
public testClass() {
}
#JavascriptInterface
public String testNativeMethod() {
return "Java method called!!";
}
}
My Java Script Code
test.js
function test(msg){
this.message = msg;
this.testJSMethod = function(){
return this.message;
}
}
alert(jsinterface.testNativeMethod()); // prints Java method called!!
jsinterface= new test("JS method called...");
alert(jsinterface.testJSMethod()); // prints JS method called...
alert(jsinterface.testNativeMethod()); // errors "NPMethod called on non- NPObject"
Problem:
Is this possible for a javascript object to have access to both , i.e javascript methods and native JAVA methods(exposed to it via javascriptinterface) ? Is there any possibility of setting any property to webview OR executing any JS script to get this done?
Think about document in javascript. When you are in a web browser, this is a global object that you have access to at any point. If you make your own new var called document, you are going to have problems accessing the global document.
When you execute this line:
mWebView.addJavascriptInterface(new testClass(), "jsinterface");
you are adding a global object called jsinterface. This is the same situation as document. If you create a var with the same name, it will overwrite the existing global reference.
Once you add a javascript interface to the WebView, you don't need to create a new reference to the interface. addJavascriptInterface has already done that for you.
TRY
You may try to make another object, which will retranslate calls to javascript interface.Implement onPageStarted method in WebViewClient , and inject javascript in onPageStarted method, in the following way.
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageStarted (WebView view, String url, Bitmap favicon){
String jsScript= "javascript:var functions_array = ['testNativeMethod'];";
jsScript+="var jsinterface = {};"
jsScript+="functions_array.map(function(id){"
jsScript+="jsinterface[id]= function() {"
jsScript+="try{return temp_obj[id].apply(temp_obj, arguments);}"
jsScript+="catch(e) { console.log('ERROR: ' + e + ', method ' + id);"
jsScript+="return false;}}})"
view.loadUrl(jsScript);
}
});
Hope this helps :-)
I am working on a project using GWT and d3. i have used d3 in javascript files. Let me explain a bit more . i have a class name AstForm in GWT. in this class i have a function which i have called in my javascript file using following code.it the code works fine for me.
public native void PrepareFunctionsForJS() /*-{
$wnd.ExtractOFFNetWork = this.#org.valcri.asstsrchui.client.AstForm::ExtractOFFNetWork(*);
}-*/;
public void ExtractOFFNetWork(JsArrayMixed args)
{
Window.alert("thisCurrent row and Column is " +
args.getString(0) + " " + args.getString(1)+"OffenderNetwork?");
}
void testfunction ()
{
Window.alert("testfunction)
}
in java script i have used the following code
window.ExtractOFFNetWork(["GWT","JS"]);
my code works fine. i can call the ExtractOFFNetWork in javascript file. however the problem is in the ExtractOFFNetWork function when i call testfunction which is also the member function of the ASTFORM class the programe error saying testfunction is not a function. however when i changed testfunction as static than i can access this function within ExtractOFFNetWork. alternatievly i can also use the testfunction inside ExtractOFFNetWork by creating a separate object of ASTForm as
AstForm my =new AstForm();
my.testfunction();
however i do not want to use either static or separate ASTform object to access member function of ASTForm. i also used this.testfunction() within ExtractOFFNetWork but it also does not work. i would appreciate if any body can help to solve my problem i have spend full day without any success :)
already tried this when calling your PrepareFunctionsForJS(btw by java naming conventions method names start with lowercase letter..)
//assuming that you are calling the prepare function from inside the AstForm
public class AstForm() {
//...
PrepareFunctionsForJS(this);
}
public native void PrepareFunctionsForJS(AstForm instance) /*-{
$wnd.ExtractOFFNetWork = instance.#org.valcri.asstsrchui.client.AstForm::ExtractOFFNetWork(*);
}-*/;
I'm trying to write debugging tools and I would like to be able to get the class name of the caller. Basically, caller ID.
So if I have a method like so, I want to get the class name:
public function myExternalToTheClassFunction():void {
var objectFunction:String = argument.caller; // is functionInsideOfMyClass
var objectFunctionClass:Object = argument.caller.this;
trace(object); // [Class MyClass]
}
public class MyClass {
public function functionInsideOfMyClass {
myExternalToTheClassFunction();
}
}
Is there anything like this in JavaScript or ActionScript3? FYI AS3 is based on and in most cases interchangeable with JS.
For debugging purposes you can create an error then inspect the stack trace:
var e:Error = new Error();
trace(e.getStackTrace());
Gives you:
Error
at com.xyz::OrderEntry/retrieveData()[/src/com/xyz/OrderEntry.as:995]
at com.xyz::OrderEntry/init()[/src/com/xyz/OrderEntry.as:200]
at com.xyz::OrderEntry()[/src/com/xyz/OrderEntry.as:148]
You can parse out the "caller" method from there.
Note that in some non-debug cases getStackTrace() may return null.
Taken from the documentation:
Unlike previous versions of ActionScript, ActionScript 3.0 has no
arguments.caller property. To get a reference to the function that
called the current function, you must pass a reference to that
function as an argument. An example of this technique can be found in
the example for arguments.callee.
ActionScript 3.0 includes a new ...(rest) keyword that is recommended
instead of the arguments class.
Try to pass the Class name as argument:
Class Code:
package{
import flash.utils.getQualifiedClassName;
public class MyClass {
public function functionInsideOfMyClass {
myExternalToTheClassFunction( getQualifiedClassName(this) );
}
}
}
External Code:
public function myExternalToTheClassFunction(classname:String):void {
trace(classname); // MyClass
}
I'm writing a serialization of objects to QML, and want to be able to get source code of functions defined in QML object. Suppose I have the following example in QML (test.qml):
import QtQml 2.2
QtObject {
function foo() {
return 42;
}
}
I created a QObject: obj from that.
Is there any way (can be hacky) to get the source code of obj's method foo without parsing the QML file obj was created from?
It's okay to use QQmlComponent obj was created from or any other Qt classes, as long as I don't have to parse it myself. Alternatively, how to get the function's source code from the test.qml file without writing my own parser? I don't want to assume anything special about test.qml (e.g. it can be different than the one above and it doesn't have to be simple enough to use a regexp or other not full-fledged QML parser).
Assuming this works like JavaScript, I tried something like:
QQmlExpression expr(engine.rootContext(), obj, "foo.toString()");
QVariant sourceCode = expr.evaluate();
However, it doesn't work.
Edit: According to http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.2 the toString method of a function object prototype is implementation-defined. In case of QML I get the result:
QVariant(QString, "function() { [code] }")
Since there doesn't seem to be a way to get the code by JS or C++, I'm not limiting myself to public Qt API anymore.
I think it is impossible to get a source code of a function from an already created QML object. There doesn't seem to be any C++ interface for it and JavaScript doesn't return it using toSource method either.
However, it can be retrieved using QML parser. The bad news are that QML parser is a part of Qt private API, so it might not work when using different Qt library builds.
The code to parse QML using Qt 5.3.0 private API is more or less:
.pro file:
QT += qml qml-private
cpp file:
using namespace QQmlJS::AST;
class LVisitor: public QQmlJS::AST::Visitor {
public:
LVisitor(QString code): _code(code) {}
virtual bool visit(FunctionBody *fb) {
qDebug() << "Visiting FunctionBody" <<
printable(fb->firstSourceLocation(), fb->lastSourceLocation());
return true;
}
private:
QStringRef printable(const SourceLocation &start, const SourceLocation &end) {
return QStringRef(&_code, start.offset, end.offset + end.length - start.offset);
}
private:
QString _code;
};
void testQmlParser() {
QFile file(":/test.qml");
file.open(QFile::ReadOnly);
QString code = file.readAll();
file.close();
QQmlJS::Engine engine;
QQmlJS::Lexer lexer(&engine);
lexer.setCode(code, 1, true);
QQmlJS::Parser parser(&engine);
if (!parser.parse() || !parser.diagnosticMessages().isEmpty()) {
foreach (const QQmlJS::DiagnosticMessage &m, parser.diagnosticMessages()) {
qDebug() << "Parse" << (m.isWarning() ? "warning" : "error") << m.message;
}
}
UiProgram *ast = parser.ast();
LVisitor visitor(code);
ast->accept(&visitor);
}
To get more precise information about the object where the function is defined or just get more information from the AST, implement more methods of QQmlJS::AST::Visitor.
I also didn't find any way to do so. but I found a way around to do it.
reffer this firts
Now, As you knwo we can access an QML object in C++. will do following to run the function in QML.
Item {
width: 100; height: 100
Rectangle {
property bool call:true
objectName: "rect"
onCallChanged()
{
myFunction();
}
function myFunction()
{
//your code
}
}
}
and in C++ you have to do following:
QObject *rect = object->findChild<QObject*>("rect");
if (rect)
rect->setProperty("call", !(rect->property("call").toBool()));
here we are using change event of property 'call' to call the myFunction()
I have read WebBrowser Control from .Net — How to Inject Javascript, Is it possible to call Javascript method from C# winforms and many others. Those examples were returns function value or alert window (synchronous calls). I have to get result from event handler (async call):
<script type="text/javascript">
window.onload = function() {
var o = new M.Build(document.getElementById("ZID"));
M.Events.observe(o, o.Events.Success, function() {
// I have to get some value!!
});
M.Events.observe(o, o.Events.Fault, function() {
// I have to get some value!!
});
}
</script>
Calling C# from JavaScript
Simply put, you can expose a C# object
to the WebBrowser that the JavaScript
can call directly The WebBrowser
class exposes a property called
ObjectForScripting that can be set by
your application and becomes the
window.external object within
JavaScript. The object must have the
ComVisibleAttribute set true
C#:
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class ScriptInterface
{
public void callMe()
{
… // Do something interesting
}
}
webBrowser1.ObjectForScripting = new ScriptInterface();
Javascript:
window.external.callMe();
Calling JavaScript in a WebBrowser control from C#
This is code I have. In the DocumentCompleted event ('cause I'm getting a page from online)
var wb = (WebBrowser)sender
//Lots of other stuff
object obj = wb.Document.InvokeScript("MyFunctionName");
Create a function that returns whatever value you need and invoke away.
You can also inject a script into the page
string js = "function MyFunctionName(){alert('Yea!');}";
HtmlElement el = wb.Document.CreateElement("script");
IHTMLScriptElement element2 = (IHTMLScriptElement)el.DomElement;
element2.text = js;
head.AppendChild(el);
which can then be invoked. That's what I've done.
If your webBrowser control is in a form, you can do the following:
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class Form1
{
public Form1()
{
InitializeComponent();
webBrowser1.ObjectForScripting = this;
}
public void CallMe()
{
//.... this method can be called in javascript via window.external.CallMe();
}
}