well i have an api running on Kestrel server locally,i needed to run it without showing the console so i used the vbs answer from superuser and it worked, now my problem is that i want to stream out the messages that were shown on console to somewhere else like a file or richtextbox of windowsform ,if there is any why ?
thank you
You can redirect the Kestrel's output to a local file, then you can watch the file changes.
Example to redirect the Kestrel output:
dotnet run > d:\kestrel-output.txt
Here is a code snip to watch a text file's changes. This approach uses time interval but you can try the FileSystemWatcher.
...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var cancellationTokenSource = new CancellationTokenSource();
Load += (s, e) =>
{
const string targetFile = #"d:\kestrel-output.txt";
_ = WatchTextFileAsync(targetFile,
TimeSpan.FromSeconds(0.5),
change =>
{
Invoke(new Action(() =>
{
richTextBox1.AppendText(change);
richTextBox1.ScrollToCaret();
})
);
},
cancellationTokenSource.Token
);
};
Closing += (s, e) =>
{
if (!e.Cancel)
cancellationTokenSource.Cancel();
};
}
public async Task WatchTextFileAsync(
string path,
TimeSpan checkInterval,
Action<string> onChange,
CancellationToken token = default)
{
var fs = File.Open(path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
fs.Seek(fs.Length, SeekOrigin.Begin); //ignore current content
var sr = new StreamReader(fs);
try
{
while (!token.IsCancellationRequested)
{
if (fs.Position > fs.Length) //new file? replaced? if so read from the beginning
{
fs.Seek(0, SeekOrigin.Begin);
}
if (fs.Position < fs.Length) //check offset
{
var change = await sr.ReadToEndAsync();
onChange?.Invoke(change);
}
await Task.Delay(checkInterval, token);
}
}
finally
{
sr.Dispose();
fs.Dispose();
}
}
}
...
I am using Identity Server 4 for authentication on my WPF MVVM app. Currently, when the user wants to log in, a popup window appears with the login screen and the info gets passed back through the OidcClientOptions. However, instead of a popup window, I want the login page to show up on a WebBrowser control in the actual application. How can I accomplish this?
My Login code:
public class Login
{
private OidcClient _oidcClient = null;
LoginResult result = null;
AccessToken accessToken = new AccessToken();
public async void LoginPage()
{
var options = new OidcClientOptions()
{
Authority = "https://localhost:5001/",
ClientId = "wpf",
ClientSecret = "secret",
Scope = "openid WebAPI",
RedirectUri = "http://localhost/signin-oidc",
Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode,
Browser = new WpfEmbeddedBrowser();
};
_oidcClient = new OidcClient(options);
try
{
result = await _oidcClient.LoginAsync();
}
catch (Exception ex)
{
return;
}
if (result.IsError)
{
}
else
{
accessToken.WriteToken(result.AccessToken);
App.Current.Properties["AccessToken"] = result.AccessToken;
}
}
}
Current WpfEmbeddedBrowser I'm using:
public class WpfEmbeddedBrowser : IBrowser
{
private BrowserOptions _options = null;
public WpfEmbeddedBrowser()
{
}
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
{
_options = options;
var window = new Window()
{
Width = 900,
Height = 625,
Title = "IdentityServer Login"
};
// Note: Unfortunately, WebBrowser is very limited and does not give sufficient information for
// robust error handling. The alternative is to use a system browser or third party embedded
// library (which tend to balloon the size of your application and are complicated).
var webBrowser = new WebBrowser();
var signal = new SemaphoreSlim(0, 1);
var result = new BrowserResult()
{
ResultType = BrowserResultType.UserCancel
};
webBrowser.Navigating += (s, e) =>
{
if (BrowserIsNavigatingToRedirectUri(e.Uri))
{
e.Cancel = true;
result = new BrowserResult()
{
ResultType = BrowserResultType.Success,
Response = e.Uri.AbsoluteUri
};
signal.Release();
window.Close();
}
};
window.Closing += (s, e) =>
{
signal.Release();
};
window.Content = webBrowser;
window.Show();
webBrowser.Source = new Uri(_options.StartUrl);
await signal.WaitAsync();
return result;
}
private bool BrowserIsNavigatingToRedirectUri(Uri uri)
{
return uri.AbsoluteUri.StartsWith(_options.EndUrl);
}
}
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:
In my Mainpage.xaml.cs file I have a function that creates an instance of another class and tries to download a webpage using a HttpWebRequest from that instance. The problem is, once I've managed to download the webpage I can't send it back to the main UI thread. I've tried using Deployment.Current.Dispatcher.BeginInvoke to send the webpage back to a TextBlock I have waiting, but when I try I get an error telling me that I can't access the TextBlock from the other class. Is there any way to pass data between two threads without using LocalStorage?
EDIT: code below:
MainPage:
private void button1_Click(object sender, RoutedEventArgs e)
{
Member m = new Member(name, id);
}
Member class:
public Member(String Member, String API)
{
APIKey = API;
MemberName = Member;
this.super = super;
DoSend(method, string, "", null);
}
public void DoSend(string method, string url, string body, string mimetype)
{
if (WebRequest.RegisterPrefix("https://",System.Net.Browser.WebRequestCreator.ClientHttp)) {
HttpWebRequest request = WebRequest.Create(makeURI(url)) as HttpWebRequest;
request.Method = method;
request.Headers["X-NFSN-Authentication"] = MakeAuthHeader(url,body);
if (body != "")
{
byte[] bodyData = Encoding.UTF8.GetBytes(body);
request.ContentType = mimetype;
//Stuff Should Happen Here
}
else
doStuff(request);
}
public void doStuff(HttpWebRequest httpReq)
{
httpReq.BeginGetResponse(r =>
{
var httpRequest = (HttpWebRequest)r.AsyncState;
var httpResponse = (HttpWebResponse)httpRequest.EndGetResponse(r);
using (var reader = new StreamReader(httpResponse.GetResponseStream()))
{
var response = reader.ReadToEnd();
ResponseBlock.Text = response; //Invalid cross-thread reference
}
}, httpReq);
}
MainPage:
customClass.DownloadPage((result) =>
{
textBlock.Text = result;
},
(exception) =>
{
MessageBox.Show(exception.Message);
});
CustomClass:
public void DownloadPage(Action<string> callback, Action<Exception> exception)
{
WebClient webClient = new WebClient();
webClient.DonwloadStringCompleted += (s, e) =>
{
if (e.Error == null)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
callback(e.Result);
});
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
exception(e.Error);
});
}
};
webClient.DonwloadStringAsync();
}
I'm trying to use the mvvm-light messenger capability to open a custom confirm password dialog in my view, triggered by a command in my viewmodel.
I think I understand the usage of Messenger.Default.Register and Messenger.Default.Send.
But how do I get the dialog results back in my viewmodel?
To me the sending seems to be a one way street...
Could someone help a beginner with a small C#/WPF code sample?
Thanks for any help
IMHO it is better to use the NotificationMessageAction<T> as it is cut out for this task.
On the sender side:
var msg = new NotificationMessageAction<MessageBoxResult>(this, "GetPassword", (r) =>
{
if (r == MessageBoxResult.OK)
{
// do stuff
}
});
Messenger.Default.Send(msg);
And on the receiver side:
Messenger.Default.Register<NotificationMessageAction<MessageBoxResult>>(this, (m) =>
{
if (m.Notification == "GetPassword") {
var dlg = new PasswordDialog();
var result = dlg.ShowDialog();
m.Execute(result);
}
});
I believe that this approach is cleaner as it does not create an unnecessary dependency from the View to the ViewModel (although this way round is not so bad). For better readability consider sub-classing the NodificationMessageAction<MessageResult>. I.e.
public class ShowPasswordMessage : NotificationMessageAction<MessageBoxResult>
{
public ShowPasswordMessage(object Sender, Action<MessageBoxResult> callback)
: base(sender, "GetPassword", callback)
{
}
}
Then the sender
var msg = new ShowPasswordMessage(this, (r) =>
{
if (r == MessageBoxResult.OK)
{
// do stuff
}
});
Messenger.Default.Send(msg);
and receiver side
Messenger.Default.Register<ShowPasswordMessage>(this, (m) =>
{
var dlg = new PasswordDialog();
var result = dlg.ShowDialog();
m.Execute(result);
});
becomes a lot clearer.
And verry important unregister the recipient as else you might create a memory leak.
In Register method you can show a dialog and pass the YourViewModel reference.
Messenger.Default.Register<YourViewModel>(this, "showDialog", viewModel=>
{
var dlg = new Dialog();
viewModel.Result = dlg.ShowDialog();
});
somewhere in your code you can throw Send() message with a reference to YourViewModel like this:
Messenger.Default.Send(viewModel, "showDialog");
In order to achieve the above using DialogMessage as the title suggests,
one may use the following:
sender side:
void SendMessage(String msgText)
{
DialogMessage messege = new DialogMessage(msgText, res =>
{
callback(res);
})
//set more dialog properties using the Initializer
{ Button = MessageBoxButton.OKCancel, Caption = "" };
Messenger.Default.Send(messege, "mb1");
}
public void callback(MessageBoxResult res)
{
if (res == MessageBoxResult.OK)
{ /*do something*/ }
}
receiver side:
void Rec()
{
Messenger.Default.Register<DialogMessage>(
this, "mb1", msg => ShowDialog(msg));
Unloaded += Unreg;
}
void ShowDialog(DialogMessage msg)
{
var result = MessageBox.Show(
msg.Content,
msg.Caption,
msg.Button);
msg.Callback(result);
}
note the explicit call to the Callback method in the last line of the receiver.
msg.Callback(result);