I have a C# BHO which calls some JS functions in a document. Normally I did it like this (and everything worked fine):
IHTMLWindow2 wnd;
//...
wnd.execScript("testMethod(\"testData\");");
But now I need to return value from JS method to my BHO. I implemented test JS method which returns a string but when I use execScript nothing is returned. I started to read documentation about execScript method and found that now they recommend to use eval instead.
But I can't find any information on how to call this from my C# BHO. I have found this question and there is even c# example but it assumes that I host WebBrowser control and suggests to use Document.InvokeScript. And in MSHTML none of IHTMLDocument* interfaces have InvokeScript method. Am I missing something?
EDIT 1: Here is a question which kind of answers how to get return value from execScript. But its probably not smart to use execScript if MSDN says it is no longer supported.
EDIT 2:
More code for this issue. First of all I have a JS function like this (in a file called func.js):
getElemHtml = function () {
var myElem = document.getElementsByClassName("lineDiv")[0];
// A lot more code goes here...
alert(myElem.innerHTML);
return myElem.innerHTML;
}
Then in my BHO I inject this script into the page like this:
StreamReader reader = new StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("func.js"));
string scriptContent = reader.ReadToEnd();
IHTMLElement head = (IHTMLElement)((IHTMLElementCollection)ihtmlDoc2.all.tags("head")).item(null, 0);
IHTMLScriptElement scriptObject = (IHTMLScriptElement)htmlDoc2.createElement("script");
scriptObject.type = #"text/javascript";
scriptObject.text = scriptContent;
((HTMLHeadElement)head).appendChild((IHTMLDOMNode)scriptObject);
Then in another part of BHO I want to get return value from getElemHtml():
var retVal = ihtmlWindow2.execScript("getElemHtml();");
but retVal is null. I see that script is executed and I see that return value is not null because I see alert window with return value. What I want is a return value from this JS function in my C# BHO code. It looks like this can be done using this answer but as I have said MSDN says I should use eval instead of execScript. The question is how to call eval and get a return value from my JS function.
I have found some links which allow to get return value from JS in C++ BHOs but I haven't managed to convert them in C# so here is a workaround way which worked for me:
// Execute method and save return value to a new document property.
ieHtmlWindow2.execScript("document.NewPropForResponse = getElemHtml();");
// Read document property.
var property = ((IExpando)ieHtmlDocument2).GetProperty("NewPropForResponse", BindingFlags.Default);
if (property != null)
return property.GetValue(ieHtmlDocument2, null); // returns return value from getElemHtml.
Related
I have added a custom environment variable and I'm unable to get it to return in the ExpandEnvironmentVariables.
These 2 calls work fine:
string s = Environment.GetEnvironmentVariable("TEST", EnvironmentVariableTarget.Machine);
// s = "D:\Temp2"
string path = Environment.ExpandEnvironmentVariables(#"%windir%\Temp1");
// path = "C:\Windows\Temp1"
However, this call returns the same input string:
var path = Environment.ExpandEnvironmentVariables(#"%TEST%\Temp1");
// path = "%TEST%\\Temp1"
I expect to get D:\Temp2\Temp1
What am I missing to correctly get the custom EnvironmentVariable in this last call?
Hans and Evk were correct in their comments. Since no one wanted to add an answer I'll close this question out.
For whatever reason ExpandEnvironmentVariables will not get any keys which were added after an application started. I also tested this with a running Windows Service. It was only after I restarted the service that new keys were found and populated.
This behavior is not documented in the Microsoft Documentation.
I'm following the Cloud Code Guide from Parse and got to the point of successfully uploading a script with a sample function called "hello" to the cloud. The function returns string "hello world" when you call it.
Now I have to run it, and this is where I stumble: there is no example available on how to do that from Unity C# environment. There are examples for curl, iOS applications, JS and so on, but not for that environment.
The closest one is a "Windows" example, I guess, which reads like this:
var result = await ParseCloud.CallFunctionAsync<IDictionary<string, object>>("hello", new Dictionary<string, object>());
Except Unity only has .NET 3.5 features, so there is no async. And except this example is not covering how to extract the actual response like examples for other platforms do.
Parse asset package for Unity adds it's own solution for the async problem called Tasks, so I guess I should just use that, just like in Unity Guide examples on standard functions like queries. I guess I can call my "hello" function and read it's "hello world" response using this code:
var helloTask = ParseCloud.CallFunctionAsync<IDictionary<string, object>> ("hello", new Dictionary<string, object> ()).ContinueWith (t =>
{
IDictionary<string, object> result = t.Result;
if (result != null)
{
string response = (string) result["result"];
if (!string.IsNullOrEmpty (response))
Debug.Log (response);
}
});
It did not work, of course, or I would not be asking that question. No errors, but no log either. I modified the code above to warn me in the log if string above is indeed null or empty, or if result object is null, but I'm not getting any messages even in that case, which leads me to assume I'm using ParseCloud.CallFunctionAsync completely incorrectly, with anything past ContinueWith not even being called.
What would be the correct way of calling a custom Cloud Code function from Unity then?
Could anyone perhaps tell me how should I use the nsIWebBrowserFind interface in GeckoFX to find strings on a webpage?
I tried the following code, but this throws me an ArgumentNullException - parameter cannot be null (pUnk).
I have no idea what this means, I have never used interfaces before.
GeckoWebBrowser browser = getCurrentBrowser();
nsIWebBrowserFind finder = browser.GetInterface<nsIWebBrowserFind>();
finder.SetSearchStringAttribute(searchBox1.Text);
finder.FindNext();
I have also tried
nsIWebBrowserFind finder = Gecko.Xpcom.GetInterface<nsIWebBrowserFind>(browser);
With the same results:(
Please help:)
Thanks!
This works in GeckoFX 29.0:
var field = typeof(GeckoWebBrowser).GetField("WebBrowser", BindingFlags.Instance | BindingFlags.NonPublic);
nsIWebBrowser browser = (nsIWebBrowser)field.GetValue(webBrowser1);
var browserFind = Xpcom.QueryInterface<nsIWebBrowserFind>(browser);
browserFind.SetSearchStringAttribute(search);
try
{
browserFind.SetWrapFindAttribute(true);
browserFind.FindNext();
}
catch { }
Some websites with frames throws an exception after the last result however, this seems to be a bug in GeckoFX. We did a workaround by falling back to a javascript find (javascript:window.find) when the exception was thrown.
I am creating an application that interfaces with Google's Maps API v3. My current approach is using a WebBrowser control by WebBrowser.Navigate("Map.html"). This is working correctly at the moment; however, I am also aware of WebBrowser.InvokeScript(). I have seen this used to execute a javascript function, but I would like to have something like the following structure:
APICalls.js - Contains different functions that can be called, or even separated out into a file for each function if necessary.
MapInterface.cs
WebBrowser.InvokeScript("APICalls.js", args) - Or control the javascript variables directly.
I have seen the InvokeScript method used, but none of the examples gave any detail to the source of the function, so I'm not sure if it was calling it from an html file or js file. Is it possible to have a structure like this, or a similarly organized structure, rather than creating an html file with javascript in each one and using Navigate()?
Additionally, are there any easier ways to use Google Maps with WPF. I checked around, but all of the resources I found were at least 2-3 years old, which I believe is older than the newest version of the maps API.
I can't suggest a better way of using Google Maps API with WPF (although I'm sure it exists), but I can try to answer the rest of the question.
First, make sure to enable FEATURE_BROWSER_EMULATION for your WebBrowser app, so Google Maps API recognizes is it as modern HTML5-capable browser.
Then, navigate to your "Map.html" page and let it finish loading. Here's how it can be done using async/await (the code is for the WinForms version of WebBrowser control, but the concept remains the same).
You can have your APICalls.js as a separate local file, but you'd need to create and populate a <script> element for it from C#. You do it once for the session.
Example:
var scriptText = File.ReadAllText("APICalls.js");
dynamic htmlDocument = webBrowser.Document;
var script = htmlDocument.createElement("script");
script.type = "text/javascript";
script.appendChild(htmlDocument.createTextNode(scriptText));
htmlDocument.body.appendChild(script);
Then you can call functions from this script in a few different ways.
For example, your JavaScript entry point function in APICalls.js may look like this:
(function() {
window.callMeFromCsharp = function(arg1, arg2) {
window.alert(arg1 + ", " +arg2);
}
})();
Which you could call from C# like this:
webBrowser.InvokeScript("callMeFromCsharp", "Hello", "World!");
[UPDATE] If you're looking for a bit more modular or object-oriented approach, you can utilize the dynamic feature of C#. Example:
JavaScript:
(function() {
window.apiObject = function() {
return {
property: "I'm a property",
Method1: function(arg) { alert("I'm method 1, " + arg); },
Method2: function() { return "I'm method 2"; }
};
}
})();
C#:
dynamic apiObject = webBrowser.InvokeScript("apiObject");
string property = apiObject.property;
MessageBox.Show(property);
apiObject.Method1("Hello!");
MessageBox.Show(apiObject.Method2());
I am attempting to implement a Language Service in a VSPackage using the MPF, and it's not working quite as I understand it should.
I have several implementations already, such as ParseSource parsing the input file with a ParseRequest. However, when it finds an error, it adds it with AuthoringSink.AddError. The documentation for this implies it adds it to the Error List for me; it doesn't.
I also have a simple MySource class, a subclass of Source. I return this new class with an overridden LanguageService.CreateSource method. The documentation for OnCommand says it's fired 'when a command is entered'. However, it's not.
There's obviously some intermediate step which I haven't done correctly. I've already rambled enough, so I'll be glad to give any additional details by request.
Any clarification is much appreciated.
For the AuthoringSink error list question, I use this behavior in my Language Service. In ParseSource, the ParseRequest class has an AuthoringSink. You can also create a new ErrorListProvider if you want to work outside of the parser's behavior. Here is some example code:
error_list = new ErrorListProvider(this.Site);
error_list.ProviderName = "MyLanguageService Errors";
error_list.ProviderGuid = new Guid(this.errorlistGUIDstring.);
}
ErrorTask task = new ErrorTask();
task.Document = filename;
task.CanDelete = true;
task.Category = TaskCategory.CodeSense;
task.Column = column;
task.Line = line;
task.Text = message;
task.ErrorCategory = TaskErrorCategory.Error;
task.Navigate += NavigateToParseError;
error_list.Tasks.Add(task);
I hope this was helpful.
OnCommand should be firing every time there is a command, in your MySource class you can do something like this (pulled from working code):
public override void OnCommand(IVsTextView textView, VsCommands2K command, char ch)
{
if (textView == null || this.LanguageService == null
|| !this.LanguageService.Preferences.EnableCodeSense)
return;
if (command == Microsoft.VisualStudio.VSConstants.VSStd2KCmdID.TYPECHAR)
{
if (char.IsLetterOrDigit(ch))
{
//do something cool
}
}
base.OnCommand(textView, command, ch);
}
If that doesn't work double check that CodeSense = true in your ProvideLanguageService attribute when you setup your LanguageService package. A whole lot of what is cool to do in the LanguageService requires these attributes to be correctly turned on. Some even give cool behaviors for free!
Another thing to be careful of is that some behaviors like colorizer don't function correctly in the hive in my experience. I don't think these were ones that gave me trouble, but I implemented these a couple of years ago so I'm mostly just looking back at old code.
AuthoringSink.AddError only adds errors to the error list if ParseRequest.Reason is ParseReason.Check. When your ParseSource function attempts to add errors while parsing for any other ParseReason, nothing will happen.
It's possible that your language service is never calling ParseSource with this ParseReason. As far as I know, the only way to get a ParseReason of Check (outside of manually calling BeginParse or ParseSource yourself) is to proffer your service with an idle timer.