SO community
I'm currently writing a project to integrate some custom views and forms in Outlook.
The idea is to create an Outlook extension connected to our ERP.
As discovered in this sample project (http://msdn.microsoft.com/en-us/library/aa479346.aspx), one technique is to build an ActiveX component and load it from a lightweight local html page, like this :
<html>
<body rightmargin = '0' leftmargin ='0' topmargin ='0' bottommargin = '0' onload='OnBodyLoad()'>
<object classid='clsid:f746a8b6-3659-4f4c-8518-6336187854f2' ID='MyView' VIEWASTEXT width='100%' height='100%'/>
<script>
function OnBodyLoad() {
try {
var oApp = window.external.OutlookApplication;
var view = document.getElementById('MyView');
view.Initialize(oApp);
}
catch(err) {
alert(err.description);
}
}
</script>
</body>
</html>
As you can see the Outlook application instance is passed to the Initialize() method for further access from the ActiveX (this is the important point for me).
The C# class looks like this :
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.Guid("F746A8B6-3659-4F4C-8518-6336187854F2")]
public partial class MyView : UserControl
{
public MyView()
:base()
{
Application.EnableVisualStyles();
InitializeComponent();
}
public String Str { get; set; }
public void Initialize(Object app)
{
MessageBox.Show("Initialized");
//MessageBox.Show(app.Version);
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(this, "It works!");
}
}
It's signed and registered with RegAsm.
When I load it in IE, it works like a charm. I get the message "Initialized" at startup, and when I click on button1, the message "It works" pops up too.
BUT (there is of course a 'but' :) when I load this page in Outlook (folder properties / home page), the ActiveX component gets loaded and visible, but the Initialize call throws this error: "Object doesn't support this property or method". Clicking the button1 works.
Trying to assign something to the property from Str with javascript fails in Outlook too, and works in IE.
Did anyone here face the same problem, and knows what I'm missing?
Many thanks in advance,
Nicolas
Related
Recently I've had the assignment to create a bi-directional interop bridge between a shell app and a webpage in .NET MAUI. Not finding any way to solve this I had the idea of creating it in Xamarin.Forms first seeing as MAUI is a continuation on it.
After having created this app, I've tried to convert it over to MAUI using Microsoft's instructions on the dotnet/maui github wiki.
The main problem i'm encountering right now is that I've been using extensions on Android's WebViewRenderer, WebViewClient and Java.Lang.Object to be able to send and receive javascript to and from the WebView.
public class ExtendedWebViewRenderer : WebViewRenderer
{
private const String JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public ExtendedWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((ExtendedWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
}
}
}
public class JavascriptWebViewClient : WebViewClient
{
private readonly String _javascript;
public JavascriptWebViewClient(String javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, String url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JsBridge : Java.Lang.Object
{
private readonly WeakReference<ExtendedWebViewRenderer> _extendedWebViewMainRenderer;
public JsBridge(ExtendedWebViewRenderer extendedRenderer)
{
_extendedWebViewMainRenderer = new WeakReference<ExtendedWebViewRenderer>(extendedRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(String data)
{
if (_extendedWebViewMainRenderer != null && _extendedWebViewMainRenderer.TryGetTarget(out var extendedRenderer))
{
((ExtendedWebView)extendedRenderer.Element).InvokeAction(data);
}
}
}
All three of these are either not available right now, or will not be implemented in MAUI, since a lot of platform dependent code has been automated now. Which leaves me with the problem that I can't seem to figure out how to change my current code to accomplish the same thing in MAUI.
Seeing as MAUI is currently not even fully released, I was wondering if this is currently just not possible or if there is some workaround to make it possible.
Any help would be greatly appreciated.
Calling C# from a webview is actually extremely simple. Just navigate and then intercept that in c#.
the xaml:
<WebView WidthRequest="400" HeightRequest="400" Navigating="WebView1_Navigating">
<WebView.Source>`enter code here`
<HtmlWebViewSource>
<HtmlWebViewSource.Html>
<![CDATA[
<HTML>
<script>
function callCsharp(){
window.location.href = 'http://poc.MyFunction?name=john&country=DK';
}
</script>
<BODY
A link that triggers C#
<br>
<button onclick="callCsharp()" type="button">A button calling javascript</button>
</BODY>
</HTML>
]]>
</HtmlWebViewSource.Html>
</HtmlWebViewSource>
</WebView.Source>
The C#:
private async void WebView1_Navigating(object sender, WebNavigatingEventArgs e)
{
var urlParts = e.Url.Split(".");
if (urlParts[0].ToLower().Equals("http://poc"))
{
var funcToCall = urlParts[1].Split("?");
var methodName = funcToCall[0];
var funcParams = funcToCall[1];
Debug.WriteLine("Calling " + methodName);
// prevent the navigation to complete
e.Cancel = true;
// TODO smart parsing and type casting of parameters and then some reflection magic
}
}
MAUI's default WebView has the Eval and EvaluateJavaScriptAsync functions to call JavaScript code from C#:
Eval just executes the script string you pass in a fire-and-forget way.
EvaluateJavaScriptAsync needs to be awaited but also returns a string with a stringified result of the data that the script returned.
If you want to use callback/bridge methods to automatically receive data from the JavaScript side without any input from the C# side of the app, you will have to extend the default per-platform renderers to add that functionality. The good news is that there is an official tutorial on how to do it for Xamarin Forms at Customizing a WebView which is almost straightforward to port to .NET MAUI - you only have to change how renderers are registered.
We have an Outlook VSTO component that adds a panel that contains a WebBrowser component, which in turn opens a web page.
We want to call back from the web page using WebBrowser.ObjectForScripting, but the guidance provided by MS here doesn't work.
So in the C# VSTO we have something like:
[ComVisible(true),
PermissionSet(SecurityAction.Demand, Name="FullTrust")]
public class MyComponent { ...
webBrowser1.ObjectForScripting = this;
webBrowser1.Document.InvokeScript("test");
...
public void HandleResult() { ...
And in the JS we have something like:
function test() {
doSomethingAsync().then(function(result) {
window.external.HandleResult();
});
}
However HandleResult is never called.
I think the issue it due to the PermissionSet being denied permissions in a VSTO add-in that it does get in stand alone Windows Forms apps.
Any idea how to set the appropriate permissions?
I have a web-service In that I have added bar-code reader for android
So with the Help of JavaScript I am calling my bar-code reader from web-view
So for that I followed this
and designed on server side...
I have Given this
at JavaScript
function mybarcd() {
MyApp.mybarcdt();
}
function actfromAnd(msg){
document.getElementById("brcd").value = msg;
}
at HTML/PHP
<div class="data">
<input id="brcd" type="text" value=""/>
<button type="button" onClick="mybarcd()">SCAN</button>
</div>
On Android side
In webview
webView.addJavascriptInterface(new WebAppInterface(this), "MyApp");
and new js interface
#JavascriptInterface
public void mybarcdt() {
IntentIntegrator intentIntegrator = new IntentIntegrator(Main_A.this);
intentIntegrator.setBeepEnabled(true);
intentIntegrator.setOrientationLocked(true);
intentIntegrator.setPrompt("Scan Barcode");
intentIntegrator.initiateScan();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (result != null) {
if (result.getContents() == null) {
Log.d("ScanActivity", "Cancelled scan");
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Log.d("ScanActivity", "Scanned");
String bcval = result.getContents();
if (android.os.Build.VERSION.SDK_INT < 19) {
webView.loadUrl("javascript:actfromAnd(\""+bcval+"\")");
}else{
webView.evaluateJavascript("javascript:actfromAnd(\""+bcval+"\")", null);
}
System.out.println("javascript:actfromAnd(\""+bcval+"\")");
}
} else
super.onActivityResult(requestCode, resultCode, data);
}
My Problem is that Its working fine in a single Html/PHP file with Js on same page or separate page I have tested its scanning and Updating the value in input box...
But its not working since I have using multiple pages or frame in one webview...
its missing JS value... How ever form server its opening scanner at on-click... but after scanning the value is not passing to the input box with JS I am getting this error.....
I/chromium: [INFO:CONSOLE(1)] "Uncaught ReferenceError: actfromAnd is not defined", source: (1)
Update
1)I hava Tried this in Static Page with JS in side that PHP/HTML page
2)I also tried with same in a static Page with JS seperate page
On the above two conditions its worked fine
But In my web-service I have Given Same JS file which is running successfully in Static page I have a single JS file for My Webservice and Static page its working fine in static but not working in MY webservice live.. How ever JS is loading Because on click its wokring from that JS and its opening Camera
But responce after scanning its not going to web input
I understand that I am getting Error Because...
In my Live Page I have a MainMenu Inside that menu when I select a application its loading in iframe So my Android Activity responce after scanning Is pinging to that Mainmenu page But for menu there is no Js function named actfromAnd So I am getting Error...
Here I can't give URL of that particular page(iframe) Because of depending on the menus it will change I can Give Only Login or MainMenu link directly.but not for a particular page inside the menu
Can Any one suggest me on this kind...
Add this script in Web-service at your Parent Menu which has your iframe or iframes
<script>
function actfromAnd(msg){
window.frames['yourExactframe'].document.getElementById("brcd").value = msg;
}
</script>
If you are using same in more than one frame then declare your frame name globally
I tried your Code Working Fine... In my example Frame Hope It works fine for you
Nice question....
You should execute the javascript when the page is loaded
mWebView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
webview.loadUrl("javascript:myFunction()");
}
});
The code will be executed and will find your javascript function. The way you are doing now does not wait.
I know this has been already asked here.
But even after following the accepted answer here (and similar other questions on SO) - I am unable to link my client side Android code to my JavaScript due to the Uncaught ReferenceError.
My Android code snippet -
public class WebActivityExample extends Activity {
WebView mWebView;
WebAppInterface WAInterface;
#Override
public void onCreate(Bundle savedInstanceState) {
this.getWindow().requestFeature(Window.FEATURE_PROGRESS);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
WAInterface = new WebAppInterface(this);
mWebView.addJavascriptInterface(WAInterface, "Android");
mWebView.loadUrl("http://myurlhere");
}
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
System.out.println("toast " + toast);
}
}
}
My website is running AngularJS on the client side and PHP on the server side. Hence I have a index.php file that holds all my JavaScript files as well as my CSS files that need to be included for the entire application.
This is my HTML code -
(It is an Angular template view that includes the HTML button) -
<input type ="button" value="Say Hello!" onClick="showAndroidToast('Hello Android!')" />
This is my index.php file that holds all the JS/CSS files -
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="myapp">
<head>
(various scripts and stylesheets included here)
</head>
<body>
....
....
<script type="text/javascript">
function showAndroidToast(toast) {
alert(toast);
Android.showToast(toast);
}
</script>
</body>
</html>
The onClick is definitely calling the showAndroidToast function since the alert pops up the message 'Hello Android' - but I am just not able to link it to the Android function 'showToast'. I've followed the docs on Android and various answers on Stackoverflow and have done exactly the same thing as asked - but still end up getting the Uncaught Reference error! What am I missing out on/ or doing wrong????
This is how the logcat error looks -
I/chromium: [INFO:CONSOLE(243)] "Uncaught ReferenceError: Android is not defined", source: ..
Update 1 :
After referring the following SO answers :
1. Can a java file have more than one class?
2. Android 4.2.1, WebView and javascript interface breaks
I created a new Java class called WebAppInterface and shifted the public class from this code snippet (within my main class) into the WebAppInterface.java class.
I also changed my Minimum APK version to 17.
Still facing the same issue. Any help would be appreciated.
while using webbrowser control, I need to programmatically auto close a javascript confirm box.
I used below user32.dll approach and it is working fine on OS which are based english language.
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
But if the computer running non-english OS, it is not working fine as I am using "OK" as text in above method call.
One approach which I suppose can work is I should detect OS language and then use translated "OK" text to use above method.
Here my question is can I change language of the current thread and so webbrowser control so that it show confirm box in English language? This way it would be easy and fast solution in my opinion.
Please suggest your solutions. Thanks in advance.
I am using similar approach in my code however these solutions are working for English language software only. I am actually looking for some generic solution that can run on non-english OS as well.
A possible solution consists in injecting and immediately calling a Javascript function that hijacks the original confirm function:
function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}
This is an example in WPF application with the standard WPF WebBrowser control, I'm quite confident that everything I do here can be adjusted to fit the WinForm control (since the underlying ActiveX is the same).
I have a UserControl that acts as an adapter of the WebBrowser, here is the XAML:
<UserControl x:Class="WebBrowserExample.WebBrowserAdapter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<WebBrowser x:Name="WebBrowserControl"></WebBrowser>
</Grid>
</UserControl>
First, in the WebBrowserAdapter class, you need a method to inject a javascript function in the current HTML document:
public void InjectScript(String scriptText)
{
HTMLDocument htmlDocument = (HTMLDocument)WebBrowserControl.Document;
var headElements = htmlDocument.getElementsByTagName("head");
if (headElements.length == 0)
{
throw new IndexOutOfRangeException("No element with tag 'head' has been found in the document");
}
var headElement = headElements.item(0);
IHTMLScriptElement script = (IHTMLScriptElement)htmlDocument.createElement("script");
script.text = scriptText;
headElement.AppendChild(script);
}
then you call InjectScript, when needed, whenever a document completes to load:
void WebBrowserAdapter_Loaded(object sender, RoutedEventArgs e)
{
WebBrowserControl.LoadCompleted += WebBrowserControl_LoadCompleted;
WebBrowserControl.Navigate("http://localhost:9080/console/page.html");
}
void WebBrowserControl_LoadCompleted(object sender, NavigationEventArgs e)
{
//HookHTMLElements();
String script =
#" function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}";
InjectScript(script);
WebBrowserControl.InvokeScript("hijackConfirm");
}
Here I navigate to http://localhost:9080/console/page.html, which is a test page hosted on my system. This works well in this simple scenario. If you find this could apply to you, you may need to tweak a little bit the code. In order to compile the code, you have to add Microsoft.mshtml in the project references
EDIT: WinForm version
To make it work, you have to use the IE 11 engine in your application. Follow the instructions found here to set it
I just tried a WinForm version of this and it works with some minor changes. Here is the code of a form that has a WebBrowser control as one of its children:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
}
void Form1_Load(object sender, EventArgs e)
{
webBrowserControl.Navigate("file:///C:/Temp/page.html");
webBrowserControl.Navigated += webBrowserControl_Navigated;
}
void webBrowserControl_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
InjectConfirmHijack();
}
private void InjectConfirmHijack()
{
String script =
#" function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}";
InjectScript(script);
webBrowserControl.Document.InvokeScript("hijackConfirm");
}
public void InjectScript(String scriptText)
{
//mshtml.HTMLDocument htmlDocument = (mshtml.IHTMLDocument) webBrowserControl.Document.get;
var headElements = webBrowserControl.Document.GetElementsByTagName("head");
if (headElements.Count == 0)
{
throw new IndexOutOfRangeException("No element with tag 'head' has been found in the document");
}
var headElement = headElements[0];
var script = webBrowserControl.Document.CreateElement("script");
script.InnerHtml = scriptText;
headElement.AppendChild(script);
}
}