So, i have a function that will load an image from disk async in an other thread ( big images will be loaded and I don't want the UI THread to be locked while loading).
Loading is done like this
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
Then I want to use the Decoder on the main thread
public List<ThumbnailModel> LoadPages()
{
var result = new List<ThumbnailModel>();
foreach (var frame in Decoder.Frames) <--// this line throws exception
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
Now here is the problem, whenever I reach the line where I try to access the Decoder.Frames it throws exception (The calling thread cannot access this object because a different thread owns it.)
Is there a way I can use my Decoder in the main thread if not, the only possible solution is to load all the image information in the other thread?
Full code version :
// this is the task, that calls the imageFactory LoadFile method - NewThread
private async Task OpenFileAsync(string strFilePath)
{
var newFile = _imageFileFactory.LoadFile(strFilePath);
if (newFile != null)
{
_imagefile = newFile;
}
}
//image factory load file - NewThread
public IImageFile LoadFile(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
return null;
}
var fileExtension = Path.GetExtension(filePath); // .tiff or .jpeg
var file = new ImageFileTiff(filePath, _metatadaFactory, _metadataVersioner);
file.LoadFile();
return file;
}
// ImageFileTiff LoadFile will create a decoder - NewThread
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new JpegBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
After we have an IImageFile we call on MainThread(UIThread)
var pages = _imagefile.LoadPages();
Where LoadPages is the place where the app breaks. also called on UIThread
public List LoadPages()
{
var result = new List();
foreach (var frame in Decoder.Frames)
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
I thought you could simply return the decoder from the thread to be able to access it but your decoder is a TiffBitmapDecoder which inherits from DispatcherObject (https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1).
So you won't be able to access it from a different thread than the one where it was created msdn:"Only the thread that the Dispatcher was created on may access the DispatcherObject directly"
What you could do instead is use the decoder in it's thread and return the final result:
I couldn't build on your sample since there was to much missing for me to test it but I built a similar project to give an exemple:
public partial class MainWindow : Window
{
public MainWindow()
{
}
public TiffBitmapDecoder LoadFile()
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "tiff files (*.tif)|*.tif|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == true && !string.IsNullOrEmpty(openFileDialog.FileName))
{
//I didn't bother to check the file extension since it's just an exemple
using (var imageStream = openFileDialog.OpenFile())
{
return new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
}
else
{
//User cancelled
return null;
}
}
public List<ThumbnailModel> LoadPages(TiffBitmapDecoder decoder)
{
//TiffBitmapDecoder" inherits from DispatcherObject/>
//https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1
var result = new List<ThumbnailModel>();
if (decoder != null)
{
try
{
foreach (var frame in decoder.Frames)
{
result.Add(new ThumbnailModel
{
//set the variables
});
}
}
catch(InvalidOperationException e)
{
MessageBox.Show(e.Message, "Error");
}
}
else
{
//Nothing to do
}
return result;
}
private async Task AsyncLoading()
{
this.thumbnailModels = await Task.Run<List<ThumbnailModel>>(() =>
{
var decoder = this.LoadFile();
return this.LoadPages(decoder);
});
}
private List<ThumbnailModel> thumbnailModels = null;
private async void AsyncLoadingButton_Click(object sender, RoutedEventArgs e)
{
await this.AsyncLoading();
}
}
public class ThumbnailModel
{
}
Content of MainWindow.xaml just in case:
<Grid>
<StackPanel Orientation="Vertical">
<Button x:Name="NoReturnButton" Margin="10" HorizontalAlignment="Center" Content="Call AsyncLoadingNoReturn" Click="AsyncLoadingButton_Click" />
</StackPanel>
</Grid>
I need to execute a function defined in wpf project, which is called from JS in a https web page.
The demo project of all codes is here: https://github.com/tomxue/WebViewIssueInWpf
JS part:
The web page link is https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=
And it contains below line:
<script src="js/index.js" type="text/javascript" charset="utf-8"></script>
And js/index.js contains below code:
setTitle(dataObject.city + weekDay(dataObject.date) +"天气" )
setTitle() is defined below: uses method of window.external.notify()
function setTitle(_str){
try{
wtjs.setTitle(_str)
}catch(e){
console.log(_str)
window.external.notify(_str);
}
}
The function window.external.notify() will call wpf function via ScriptNotify().
WPF part:
For the WebView inside of the wpf project
this.wv.IsScriptNotifyAllowed = true;
this.wv.ScriptNotify += Wv_ScriptNotify;
And
private void Wv_ScriptNotify(object sender, Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs e)
{
textBlock.Text = e.Value;
}
Problems:
(1)
The problem here is if the web page uses https://, then the above function Wv_ScriptNotify() in wpf will not be fired. But if the web page link uses http://, then the above function Wv_ScriptNotify() in wpf can be fired.
Why and how to solve it?
Update:
2020-3-2 17:25:55, tested just now, https works. I do not know what causes https does not work previously
(2)
JS in the web page uses a object wtjs (defined by ourselves and work well with an UWP project using JSBridge).
And I want to use a similiar method to UWP, using a bridge so that I can add multiple funtions/interfaces for JS to call. The disadvantage of ScriptNotify() is that only one interface is usable.
To achieve it, I make below code, which is commented out now.
wv.RegisterName("wtjs", new myBridge());
And more functions are defined as below
public class myBridge
{
public void SetTitle(string title)
{
Debug.WriteLine("SetTitle is executing...title = {0}", title);
}
public void PlayTTS(string tts)
{
Debug.WriteLine("PlayTTS is executing...tts = {0}", tts);
}
}
While in JS side, corresponding functions will be called.
wtjs.playTTS(tts)
wtjs.setTitle(_str)
But in fact wpf side did not work, while the UWP project using JSBridge works with the web link(so web page and JS script are workable). How to achieve it?
(3)
The above two problems are solved by DK Dhilip's answer already.
But a new problem is found. Please check my GitHub code, update it to latest commit.
https://github.com/tomxue/WebViewIssueInWpf
I put a TextBlock onto WebView and expect to see the text floating on the web content. But in fact, the text is covered by the WebView. Why and how to solve it?
Thanks!
For Problem (1, 2)
HTTPS link worked fine for me, maybe the page is too slow to load?
According to Microsoft (source), only ScriptNotify is supported in WebView:
Can I inject native objects into my WebViewControl content?
No.
Neither the WebBrower (Internet Explorer) ObjectForScripting property
nor the WebView (UWP) AddWebAllowedObject method are supported in
WebViewControl. As a workaround, you can use window.external.notify/
ScriptNotify and JavaScript execution to communicate between the
layers, for example:
https://github.com/rjmurillo/WebView_AddAllowedWebObjectWorkaround
But the above suggested workaround solution seems to work differently to your expectation, so I just implement my own solution to emulate the JSBridge convention you have expected.
My custom solution is not battle-tested, it might break in some edge cases but it seems to work fine in few simple tests.
What's supported:
Multiple bridge objects
JS to C# method call
JS to C# get/set property
C# Usage:
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());
// Remove
webView.RemoveWebAllowedObject("wtjs");
JS Usage:
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');
// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });
// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });
// Set C# object property
wtjs.niubility = true;
Code
WebViewExtensions.cs
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;
namespace WpfApp3
{
// Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
public static class JsonHelper
{
private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };
public static object ConvertWeaklyTypedValue(object value, Type targetType)
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
if (value == null)
return null;
if (targetType.IsInstanceOfType(value))
return value;
var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (paramType.IsEnum)
{
if (value is string)
return Enum.Parse(paramType, (string)value);
else
return Enum.ToObject(paramType, value);
}
if (paramType == typeof(Guid))
{
return Guid.Parse((string)value);
}
if (_specialNumericTypes.Contains(paramType))
{
if (value is BigInteger)
return (ulong)(BigInteger)value;
else
return Convert.ChangeType(value, paramType);
}
if (value is long || value is double)
{
return Convert.ChangeType(value, paramType);
}
return value;
}
}
public enum WebViewInteropType
{
Notify = 0,
InvokeMethod = 1,
InvokeMethodWithReturn = 2,
GetProperty = 3,
SetProperty = 4
}
public class WebAllowedObject
{
public WebAllowedObject(WebView webview, string name)
{
WebView = webview;
Name = name;
}
public WebView WebView { get; private set; }
public string Name { get; private set; }
public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();
public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }
public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
}
public static class WebViewExtensions
{
public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
{
try
{
var message = JsonConvert.DeserializeObject<dynamic>(e.Value);
if (message["___magic___"] != null)
{
return false;
}
}
catch (Exception) { }
return true;
}
public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
if (targetObject == null)
throw new ArgumentNullException(nameof(targetObject));
if (webview.Tag == null)
{
webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
}
else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
{
throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
}
var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;
var webAllowedObject = new WebAllowedObject(webview, name);
if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
{
var objectType = targetObject.GetType();
var methods = objectType.GetMethods();
var properties = objectType.GetProperties();
var jsStringBuilder = new StringBuilder();
jsStringBuilder.Append("(function () {");
jsStringBuilder.Append("window['");
jsStringBuilder.Append(name);
jsStringBuilder.Append("'] = {");
jsStringBuilder.Append("__callback: {},");
jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");
foreach (var method in methods)
{
if (!method.IsSpecialName)
{
if (method.ReturnType == typeof(void))
{
webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
}
else
{
webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
}
var parameters = method.GetParameters();
var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));
jsStringBuilder.Append(method.Name);
jsStringBuilder.Append(": function (");
jsStringBuilder.Append(parametersInString);
jsStringBuilder.Append(") {");
if (method.ReturnType != typeof(void))
{
jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
}
jsStringBuilder.Append("window.external.notify(JSON.stringify({");
jsStringBuilder.Append("source: '");
jsStringBuilder.Append(name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("target: '");
jsStringBuilder.Append(method.Name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("parameters: [");
jsStringBuilder.Append(parametersInString);
jsStringBuilder.Append("]");
if (method.ReturnType != typeof(void))
{
jsStringBuilder.Append(",");
jsStringBuilder.Append("callbackId: callbackId");
}
jsStringBuilder.Append("}), ");
jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
jsStringBuilder.Append(");");
if (method.ReturnType != typeof(void))
{
jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
jsStringBuilder.Append("});");
jsStringBuilder.Append("return promise;");
}
jsStringBuilder.Append("},");
}
}
jsStringBuilder.Append("};");
foreach (var property in properties)
{
jsStringBuilder.Append("Object.defineProperty(");
jsStringBuilder.Append("window['");
jsStringBuilder.Append(name);
jsStringBuilder.Append("'], '");
jsStringBuilder.Append(property.Name);
jsStringBuilder.Append("', {");
if (property.CanRead)
{
webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);
jsStringBuilder.Append("get: function () {");
jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
jsStringBuilder.Append("window.external.notify(JSON.stringify({");
jsStringBuilder.Append("source: '");
jsStringBuilder.Append(name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("target: '");
jsStringBuilder.Append(property.Name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("callbackId: callbackId,");
jsStringBuilder.Append("parameters: []");
jsStringBuilder.Append("}), ");
jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
jsStringBuilder.Append(");");
jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
jsStringBuilder.Append("});");
jsStringBuilder.Append("return promise;");
jsStringBuilder.Append("},");
}
if (property.CanWrite)
{
webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);
jsStringBuilder.Append("set: function ($$v) {");
jsStringBuilder.Append("window.external.notify(JSON.stringify({");
jsStringBuilder.Append("source: '");
jsStringBuilder.Append(name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("target: '");
jsStringBuilder.Append(property.Name);
jsStringBuilder.Append("',");
jsStringBuilder.Append("parameters: [$$v]");
jsStringBuilder.Append("}), ");
jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
jsStringBuilder.Append(");");
jsStringBuilder.Append("},");
}
jsStringBuilder.Append("});");
}
jsStringBuilder.Append("})();");
var jsString = jsStringBuilder.ToString();
webAllowedObject.NavigationCompletedHandler = (sender, e) =>
{
var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");
if (!isExternalObjectCustomized)
{
webview.InvokeScript("eval", new string[] { #"
(function () {
var originalExternal = window.external;
var customExternal = {
notify: function (message, type = 0) {
if (type === 0) {
originalExternal.notify(message);
} else {
originalExternal.notify(JSON.stringify({
___magic___: true,
type: type,
interop: message
}));
}
},
isCustomized: true
};
window.external = customExternal;
})();" });
}
webview.InvokeScript("eval", new string[] { jsString });
};
webAllowedObject.ScriptNotifyHandler = (sender, e) =>
{
try
{
var message = JsonConvert.DeserializeObject<dynamic>(e.Value);
if (message["___magic___"] != null)
{
var interopType = (WebViewInteropType)message.type;
var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
var source = (string)interop.source.ToString();
var target = (string)interop.target.ToString();
var parameters = (object[])interop.parameters.ToObject<object[]>();
if (interopType == WebViewInteropType.InvokeMethod)
{
if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
{
if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
{
var method = (MethodInfo)methodObject;
var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
var convertedParameters = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
}
method.Invoke(targetObject, convertedParameters);
}
}
}
else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
{
var callbackId = interop.callbackId.ToString();
if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
{
if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
{
var method = (MethodInfo)methodObject;
var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
var convertedParameters = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
}
var invokeResult = method.Invoke(targetObject, convertedParameters);
webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
}
}
}
else if (interopType == WebViewInteropType.GetProperty)
{
var callbackId = interop.callbackId.ToString();
if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
{
if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
{
var property = (PropertyInfo)propertyObject;
var getResult = property.GetValue(targetObject);
webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
}
}
}
else if (interopType == WebViewInteropType.SetProperty)
{
if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
{
if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
{
var property = (PropertyInfo)propertyObject;
property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
}
}
}
}
}
catch (Exception ex)
{
// Do nothing
}
};
webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
}
else
{
throw new InvalidOperationException("Object with the identical name is already exist.");
}
}
public static void RemoveWebAllowedObject(this WebView webview, string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;
if (allowedWebObjectsMap != null)
{
if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
{
webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;
webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
}
}
}
}
}
MainWindow.xaml.cs
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;
namespace WpfApp3
{
public partial class MainWindow : Window
{
public class MyBridge
{
private readonly MainWindow _window;
public MyBridge(MainWindow window)
{
_window = window;
}
public void setTitle(string title)
{
Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));
_window.setTitle(title);
}
public void playTTS(string tts)
{
Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
}
}
public MainWindow()
{
this.InitializeComponent();
this.wv.IsScriptNotifyAllowed = true;
this.wv.ScriptNotify += Wv_ScriptNotify;
this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));
this.Loaded += MainPage_Loaded;
}
private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
{
if (e.IsNotification())
{
Debug.WriteLine(e.Value);
}
}
private void setTitle(string str)
{
textBlock.Text = str;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
}
}
}
Result
Screenshot:
For Problem (3)
According to (1, 2, 3), it is impossible to overlay UI elements on top of WebView/WebBrowser control.
Luckily there is an alternative solution called CefSharp which is based on Chromium web browser and would be good enough for your use case, plus the background animation worked (which doesn't work in original WebView control).
However, there is no perfect solution; WPF design view is unusable with CefSharp (showing Invalid Markup error), but the program will just compile and run. Also, the project can only be built with either x86 or x64 option, AnyCPU will not work.
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
x:Class="WpfApp3.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="grid">
<cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
</Grid>
</Window>
MainWindow.xaml.cs
using CefSharp;
using System.Diagnostics;
using System.Windows;
namespace WpfApp3
{
public partial class MainWindow : Window
{
public class MyBridge
{
private readonly MainWindow _window;
public MyBridge(MainWindow window)
{
_window = window;
}
public void setTitle(string title)
{
Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));
_window.setTitle(title);
}
public void playTTS(string tts)
{
Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
}
}
public MainWindow()
{
this.InitializeComponent();
this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
this.wv.FrameLoadStart += Wv_FrameLoadStart;
this.Loaded += MainPage_Loaded;
}
private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
{
if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
{
e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
}
}
private void setTitle(string str)
{
this.Dispatcher.Invoke(() =>
{
textBlock.Text = str;
});
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
}
}
}
Screenshot:
Please check the code snippets below, this get loaded everytime i navigate to my view(user control) and it creates new CefSharp.BrowserProcess.exe on each load and renders last visited URL.
Problem with is is that it does not maintain the session storage of the site (URL) And load is incorrect with data is lost.
viewModel (main) code:
private void OnLoad()
{
IsBusy = true;
try
{
if (string.IsNullOrEmpty(TieAddress))
{
TieAddress = _serviceJournalsBaseSettings.GetTieUrl();
}
var cookieManager = Cef.GetGlobalCookieManager();
Cookie cookie = new Cookie
{
Name = BaseSettings.GetTieCookieName(),
Value = BaseSettings.GetTieCookie()
};
cookieManager.SetCookie(BaseSettings.GetTieCookieUrl(), cookie);
}
catch (Exception ex)
{
ShowErrorNotification(ex.Message);
}
finally
{
IsBusy = false;
}
}
View (User control) Code:
<wpf:ChromiumWebBrowser
Grid.Row="1" Grid.ColumnSpan="2"
x:Name="BrowserTieView"
Address="{Binding TieAddress, Mode=TwoWay}"
Title="Browser Tie View"
AllowDrop="True"/>
View.Xaml.cs
public partial class ServiceJournalsView : UserControl
{
public ServiceJournalsView()
{
InitializeComponent();
BrowserTieView.DownloadHandler = new DownloadHandler();
BrowserTieView.BrowserSettings = new BrowserSettings()
{
ApplicationCache = CefState.Enabled,
FileAccessFromFileUrls = CefState.Enabled,
Javascript = CefState.Enabled,
LocalStorage = CefState.Enabled,
WebSecurity = CefState.Disabled,
JavascriptCloseWindows = CefState.Enabled,
JavascriptDomPaste = CefState.Enabled,
};
BrowserTieView.LoadError += (sender, args) =>
{
// Don't display an error for downloaded files.
if (args.ErrorCode == CefErrorCode.Aborted)
{
return;
}
// Display a load error message.
var errorBody = string.Format(
"<html><body bgcolor=\"white\"><h2>Failed to load URL {0} with error {1} ({2}).</h2></body></html>",
args.FailedUrl, args.ErrorText, args.ErrorCode);
args.Frame.LoadHtml(errorBody, base64Encode: true);
};
Unloaded += async delegate (object sender, RoutedEventArgs args)
{
BrowserTieView.WebBrowser.Dispose();
BrowserTieView.Dispose();
await Task.Delay(10);
};
}
public ServiceJournalsViewModel VMServiceJournalsViewModel
{
get => (ServiceJournalsViewModel) DataContext;
set { DataContext = value; }
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
BrowserTieView.RegisterJsObject("serviceJournalsJsModel", VMServiceJournalsViewModel.ServiceJournalsJsModel);
}
catch (Exception ex)
{
}
}
}
As per discussion in the comments of the question posted( and as per #amaitland) Multiple instances of Cefsharp.BrowserSubprocess.exe is perfectly normal.
I have a WPF project that is part of the solution, it is "ProjectFilesSelector". A some project, named A. call ProjectFilesSelector as figure below:
!!! UPDATED:
namespace ProjectFilesSelector
{
...
public class ViewModel
{
...
public ICommand cancel
{
get
{
return new WPFExtensions.RelayCommand(_ =>
{
this.window.Visibility = Visibility.Hidden;
this.window.Close();
});
}
}
}
public partial class Window1 : Window, IDisposable
{
public Window1(ProjectTypes.Project pro)
{
InitializeComponent();
var context = new ViewModel(this, new ATChecker.ViewModel.ProjectModel(pro));
this.DataContext = context;
}
...
}
}
namespace ATCheckerView
{
public class ViewerClientExt : INotifyPropertyChanged
{
...
public ICommand CheckPrinciplies
{
get
{
var cmnd =
new RelayCommand(project =>
{
var proj = (ViewModel.ProjectModel)project;
ProjectFilesSelector.ViewModel dc;
using (var a = new ProjectFilesSelector.Window1(proj.project))
{
a.ShowDialog(); // cancel command was called
dc = (ProjectFilesSelector.ViewModel)a.DataContext;
}
....
// some code
// and I can still see the window of Window1. Why?
});
I don t think you want to exit the application. You may just want to close the window.
The cancel button just set IsCancel to true. And for the ok button the best way is to create a event in the viewmodel to get up to the view.
Im trying to make an app that can dynamically load classes that implements an interface "IPlugin", i have:
var catalog = new AssemblyCatalog(typeof(Shell).Assembly);
var externalCatalog = new DirectoryCatalog(#".\Modules");
var container = new CompositionContainer(catalog);
var a = new AggregateCatalog(externalCatalog, catalog);
But when im trying to get the exports:
CompositionContainer __container = new CompositionContainer(a);
//get all the exports and load them into the appropriate list tagged with the importmany
__container.Compose(batch);
var yyyy = __container.GetExports<IModule>();
It doesnt find my "IPlugin" in the external assembly "Rejseplan".
Implementation of "Rejseplan" plugin:(the one that does not get loaded)
namespace Rejseplan
{
[ModuleExport(typeof(IPlugin), InitializationMode = InitializationMode.WhenAvailable)]
class RejseplanModule : IModule, IPlugin
{
private readonly IRegionViewRegistry regionViewRegistry;
[ImportingConstructor]
public RejseplanModule(IRegionViewRegistry registry)
{
this.regionViewRegistry = registry;
}
public void Initialize()
{
regionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.DepartureBoard));
}
string IPlugin.Name
{
get { throw new NotImplementedException(); }
}
string IPlugin.Version
{
get { throw new NotImplementedException(); }
}
string IPlugin.TabHeader
{
get { throw new NotImplementedException(); }
}
}
}
implmentation of "Test" plugin (the one that GETS loaded):
namespace HomeSystem
{
[Export(typeof(IPlugin))]
[ModuleExport(typeof(IModule), InitializationMode = InitializationMode.WhenAvailable)]
public class Test : IModule, IPlugin
{
public void Initialize()
{
}
public string Name
{
get { return "Test"; }
}
public string Version
{
get { return "Tis"; }
}
public string TabHeader
{
get { return "Tabt"; }
}
}
}
Hope you guys can helpCheers! :)
to be honest i dont really know what you wanna achieve :)
but if you want to see your RejseplanModule within your call:
__container.GetExports<IModule>();
you have to add the right Export attribute.
RejseplanModule is not MEF marked with Export of type IModule. can you check your code if its a typo or not? at least it should be the following (see the typeof(IModule))
EDIT:
external dll
[Export(typeof(IModule))]//<-- remove this if its handled by your custom ModulExport Attribute
[ModuleExport(typeof(IModule), InitializationMode = InitializationMode.WhenAvailable)]
class RejseplanModule : IModule, IPlugin
{...}
your main app(code from fhnaseer above)
var directoryPath = "path to dll folder";
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll");
var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(asmCatalog);
aggregateCatalog.Catalogs.Add(directoryCatalog);
var container = new CompositionContainer(aggregateCatalog);
var allIModulPlugins = container.GetExports<IModule>();
try to do this.
var directoryPath = "path to dll folder";
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll");
var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(asmCatalog);
aggregateCatalog.Catalogs.Add(directoryCatalog);
var container = new CompositionContainer(aggregateCatalog);
container.ComposeParts(this);