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:
I use the following code for export to excel,it works fine,but how can I change it to xport directly to .xlsx file not xml file,also I do not want to use automation because it works very slow.
Thanks.
public static class DataGridxtensions
{
public static void Export(this DataGrid dg)
{
ExportDataGrid(dg);
}
public static void ExportDataGrid(DataGrid dGrid)
{
SaveFileDialog objSFD = new SaveFileDialog() { DefaultExt = "xml", Filter = "Excel XML (*.xml)|*.xml", FilterIndex = 1 };
if (objSFD.ShowDialog() == true)
{
string strFormat = objSFD.SafeFileName.Substring(objSFD.SafeFileName.IndexOf('.') + 1).ToUpper();
StringBuilder strBuilder = new StringBuilder();
if (dGrid.ItemsSource == null) return;
List<string> lstFields = new List<string>();
if (dGrid.HeadersVisibility == DataGridHeadersVisibility.Column || dGrid.HeadersVisibility == DataGridHeadersVisibility.All)
{
foreach (DataGridColumn dgcol in dGrid.Columns)
lstFields.Add(FormatField(dgcol.Header.ToString(), strFormat, false));
BuildStringOfRow(strBuilder, lstFields, strFormat);
}
foreach (object data in dGrid.ItemsSource)
{
lstFields.Clear();
foreach (DataGridColumn col in dGrid.Columns)
{
string strValue = "";
Binding objBinding = null;
if (col is DataGridBoundColumn)
objBinding = (col as DataGridBoundColumn).Binding;
if (col is DataGridTemplateColumn)
{
//This is a template column... let us see the underlying dependency object
DependencyObject objDO = (col as DataGridTemplateColumn).CellTemplate.LoadContent();
FrameworkElement oFE = (FrameworkElement)objDO;
FieldInfo oFI = oFE.GetType().GetField("TextProperty");
if (oFI != null)
{
if (oFI.GetValue(null) != null)
{
if (oFE.GetBindingExpression((DependencyProperty)oFI.GetValue(null)) != null)
objBinding = oFE.GetBindingExpression((DependencyProperty)oFI.GetValue(null)).ParentBinding;
}
}
}
if (objBinding != null)
{
if (objBinding.Path.Path != "")
{
PropertyInfo pi = data.GetType().GetProperty(objBinding.Path.Path);
if (pi != null) strValue = pi.GetValue(data, null).ToString();
}
if (objBinding.Converter != null)
{
if (strValue != "")
strValue = objBinding.Converter.Convert(strValue, typeof(string), objBinding.ConverterParameter, objBinding.ConverterCulture).ToString();
else
strValue = objBinding.Converter.Convert(data, typeof(string), objBinding.ConverterParameter, objBinding.ConverterCulture).ToString();
}
}
lstFields.Add(FormatField(strValue, strFormat, true));
}
BuildStringOfRow(strBuilder, lstFields, strFormat);
}
StreamWriter sw = new StreamWriter(objSFD.OpenFile());
if (strFormat == "XML")
{
//Let us write the headers for the Excel XML
sw.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
sw.WriteLine("<?mso-application progid=\"Excel.Sheet\"?>");
sw.WriteLine("<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\">");
sw.WriteLine("<DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">");
sw.WriteLine("<Author>Arasu Elango</Author>");
sw.WriteLine("<Created>" + DateTime.Now.ToLocalTime().ToLongDateString() + "</Created>");
sw.WriteLine("<LastSaved>" + DateTime.Now.ToLocalTime().ToLongDateString() + "</LastSaved>");
sw.WriteLine("<Company>Atom8 IT Solutions (P) Ltd.,</Company>");
sw.WriteLine("<Version>12.00</Version>");
sw.WriteLine("</DocumentProperties>");
sw.WriteLine("<Worksheet ss:Name=\"Export\" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">");
sw.WriteLine("<Table>");
}
sw.Write(strBuilder.ToString());
if (strFormat == "XML")
{
sw.WriteLine("</Table>");
sw.WriteLine("</Worksheet>");
sw.WriteLine("</Workbook>");
}
sw.Close();
}
}
private static void BuildStringOfRow(StringBuilder strBuilder, List<string> lstFields, string strFormat)
{
switch (strFormat)
{
case "XML":
strBuilder.AppendLine("<Row>");
strBuilder.AppendLine(String.Join("\r\n", lstFields.ToArray()));
strBuilder.AppendLine("</Row>");
break;
case "CSV":
strBuilder.AppendLine(String.Join(",", lstFields.ToArray()));
break;
}
}
private static string FormatField(string data, string format, bool isNumber)
{
switch (format)
{
case "XML":
if (isNumber)
{
return String.Format("<Cell><Data ss:Type=\"Number\">{0}</Data></Cell>", data);
}
else
{
return String.Format("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", data);
}
case "CSV":
return String.Format("\"{0}\"", data.Replace("\"", "\"\"\"").Replace("\n", "").Replace("\r", ""));
}
return data;
}
}
I can think of two options.
First, there are several component vendors who have solved this problem. There's SyncFusion's XlsIo, and Infragistics, and Telerik (looks like they have .xls only).
If the financial cost is too high, then you might consider OpenXml. The learning curve would be a bit steeper, and you can't use it directly from Silverlight -- you'd have to make the server convert your data to an .xlsx file and then your client would save the results. Also, you'll need to be aware of this issue.
But, it is free. I have had some success reading Excel files with it. I think this might be the NuGet package you'd need, or you can download an installer.
Fortunately for me, my employer paid for the first option above when we needed to create an .xlsx file. :-)
Yup office Automation way to slow! I had to change to, I used code from
http://www.codeproject.com/Articles/45731/Export-Silverlight-DataGrid-to-Excel-XML-CSV?msg=4829021#xx4829021xx
I am creating resource file at runtime in WPF.
It is being created and displayed in resource folder, but it is not showing in solution, even after refreshing the folder.
When i do it manually (add exsiting item), then it is being added.
How to add it to solution whenever it is being created?
My code is:
public void LaguageCulture(string languageid)
{
try
{
SqlParameter[] param = new SqlParameter[1];
param[0] = new SqlParameter("#lagid", languageid);
//languageid =3
var dsResources = SqlHelper.ExecuteDataset(_objSqlConnection, "sproc_GetResourceNames", param);
string culturecode = string.Empty;
if (dsResources.Tables[1].Rows.Count > 0)
{
culturecode = dsResources.Tables[1].Rows[0][0].ToString();
}
FileInfo file = new FileInfo(System.Windows.Forms.Application.StartupPath.Replace("\\bin\\Debug", "") + "\\Resources\\EnglishResource." + culturecode + ".resx"); // Culture code is en-US
if (!file.Exists)
{
var resx = new ResXResourceWriter(System.Windows.Forms.Application.StartupPath.Replace("\\bin\\Debug", "") + "\\Resources\\EnglishResource." + culturecode + ".resx");
if (dsResources.Tables[0].Rows.Count > 0)
{
for (var i = 0; i < dsResources.Tables[0].Rows.Count; i++)
{
resx.AddResource(dsResources.Tables[0].Rows["ResourceName"].ToString(), dsResources.Tables[0].Rows["ResourceValue"].ToString());
}
}
resx.Generate();
//Application.Current.Resources.Add(culturecode + ".resx", resx);
resx.Close();
}
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culturecode);
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(culturecode);
}
catch (Exception)
{
throw;
}
}
You have to programmatically add some lines in your .csproj (which is a xml file).
Maybe something like :
<EmbeddedResource Include="Resources\EnglishResource.en-US.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
How can I print the contents of a DataGrid. I have checked out the following post
How can I produce a "print preview" of a FlowDocument in a WPF application? and it generates only the visible portion of the grid not the scrollable parts. I would need the preview to have multiple pages and I thiink I should be using the FlowDocument but I am not sure how to go about it. Any ideas would be appreciated.
I had this issue some time ago. I wrote a method that generates a System.Windows.Documents.Table from the DataGrid. I put it in a FlowDocument and generated a fixed document thanks to an XpsDocumentWriter. You will then have a full paginated view of your DataGrid that you can visualize in a DocumentViewer
Use this:
it works fine.
public class UIPrinter
{
#region Properties
public Int32 VerticalOffset { get; set; }
public Int32 HorizontalOffset { get; set; }
public String Title { get; set; }
public UIElement Content { get; set; }
#endregion
#region Initialization
public TimelinePrinter()
{
HorizontalOffset = 20;
VerticalOffset = 20;
Title = "Print " + DateTime.Now.ToMyStringWithTime();
}
#endregion
#region Methods
public Int32 Print()
{
var dlg = new PrintDialog();
if (dlg.ShowDialog() == true)
{
//---FIRST PAGE---//
// Size the Grid.
Content.Measure(new Size(Double.PositiveInfinity,
Double.PositiveInfinity));
Size sizeGrid = Content.DesiredSize;
//check the width
if (sizeGrid.Width > dlg.PrintableAreaWidth)
{
MessageBoxResult result = MessageBox.Show(Properties.Resources.s_EN_Question_PrintWidth, "Print", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.No)
throw new PrintAborted(Properties.Resources.s_EN_Info_PrintingAborted);
}
// Position of the grid
var ptGrid = new Point(HorizontalOffset, VerticalOffset);
// Layout of the grid
Content.Arrange(new Rect(ptGrid, sizeGrid));
//print
dlg.PrintVisual(Content, Title);
//---MULTIPLE PAGES---//
double diff;
int i = 1;
while ((diff = sizeGrid.Height - (dlg.PrintableAreaHeight - VerticalOffset*i)*i) > 0)
{
//Position of the grid
var ptSecondGrid = new Point(HorizontalOffset, -sizeGrid.Height + diff + VerticalOffset);
// Layout of the grid
Content.Arrange(new Rect(ptSecondGrid, sizeGrid));
//print
int k = i + 1;
dlg.PrintVisual(Content, Title + " (Page " + k + ")");
i++;
}
return i;
}
throw new PrintAborted(Properties.Resources.s_EN_Info_PrintingAborted);
}
#endregion
}
It prints the Datagrid or any other Control with the selected printer on multiple pages...
Usage:
MessageBoxResult result = MessageBox.Show(Properties.Resources.s_EN_Question_Print, "Print", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
try
{
var border = VisualTreeHelper.GetChild(MyDataGrid, 0) as Decorator;
if (border != null)
{
var scrollViewer = border.Child as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToTop();
scrollViewer.ScrollToLeftEnd();
}
}
Title = _initialTitle + " - " + Properties.Resources.s_EN_Info_Printing;
var myPrinter = new UIPrinter{ Title = Title, Content = PrintGrid };
int nbrOfPages = myPrinter.Print();
Title = _initialTitle + " - " + Properties.Resources.s_EN_Info_PrintingDone + " (" + nbrOfPages + " Pages)";
}
catch (PrintAborted ex)
{
Title = _initialTitle + " - " + ex.Message;
}
}
EDIT:
I placed my Datagrid on a simple grid containing a header control to have a header on my paper.
I use a custom textblock in my WPF Application, when I use it in WPF Windows it worked good but when I use it in a WPF Page it make a problem. When I click on a link in my Custom Control it browse the link and show in browser but the WPF page navigate back to another WPF Page too (first page)
namespace Dtwitter.Controls
{
public class TweetTextBlock : TextBlock
{
public TweetTextBlock()
{
}
#region Dependency properties
public string TweetText
{
get { return (string)GetValue(TweetTextProperty); }
set { SetValue(TweetTextProperty, value); }
}
// Using a DependencyProperty as the backing store for TweetText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TweetTextProperty =
DependencyProperty.Register("TweetText", typeof(string), typeof(TweetTextBlock),
new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnTweetTextChanged)));
#endregion
private static void OnTweetTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
string text = args.NewValue as string;
if (!string.IsNullOrEmpty(text))
{
TweetTextBlock textblock = (TweetTextBlock)obj;
textblock.Inlines.Clear();
textblock.Inlines.Add(" ");
string[] words = Regex.Split(text, #"([ \(\)\{\}\[\]])");
string possibleUserName = words[0].ToString();
if ((possibleUserName.Length > 1) && (possibleUserName.Substring(1, 1) == "#"))
{
textblock = FormatName(textblock, possibleUserName);
words.SetValue("", 0);
}
foreach (string word in words)
{
// clickable hyperlinks
if (UrlShorteningService.IsUrl(word))
{
try
{
Hyperlink link = new Hyperlink();
link.NavigateUri = new Uri(word);
link.Inlines.Add(word);
link.Click += new RoutedEventHandler(link_Click);
link.ToolTip = "Open link in the default browser";
textblock.Inlines.Add(link);
}
catch
{
//TODO:What are we catching here? Why? Log it?
textblock.Inlines.Add(word);
}
}
// clickable #name
else if (word.StartsWith("#"))
{
textblock = FormatName(textblock, word);
}
// clickable #hashtag
else if (word.StartsWith("#"))
{
string hashtag = String.Empty;
Match foundHashtag = Regex.Match(word, #"#(\w+)(?<suffix>.*)");
if (foundHashtag.Success)
{
hashtag = foundHashtag.Groups[1].Captures[0].Value;
Hyperlink tag = new Hyperlink();
tag.Inlines.Add(hashtag);
string hashtagUrl = "http://search.twitter.com/search?q=%23{0}";
// The main application has access to the Settings class, where a
// user-defined hashtagUrl can be stored. This hardcoded one that
// is used to set the NavigateUri is just a default behavior that
// will be used if the click event is not handled for some reason.
tag.NavigateUri = new Uri(String.Format(hashtagUrl, hashtag));
tag.ToolTip = "Show statuses that include this hashtag";
tag.Tag = hashtag;
tag.Click += new RoutedEventHandler(hashtag_Click);
textblock.Inlines.Add("#");
textblock.Inlines.Add(tag);
textblock.Inlines.Add(foundHashtag.Groups["suffix"].Captures[0].Value);
}
}
else
{
textblock.Inlines.Add(word);
}
}
textblock.Inlines.Add(" ");
}
}
public static TweetTextBlock FormatName(TweetTextBlock textblock, string word)
{
string userName = String.Empty;
string firstLetter = word.Substring(0, 1);
Match foundUsername = Regex.Match(word, #"#(\w+)(?<suffix>.*)");
if (foundUsername.Success)
{
userName = foundUsername.Groups[1].Captures[0].Value;
Hyperlink name = new Hyperlink();
name.Inlines.Add(userName);
name.NavigateUri = new Uri("http://twitter.com/" + userName);
name.ToolTip = "View #" + userName + "'s recent tweets";
name.Tag = userName;
name.Click += new RoutedEventHandler(name_Click);
if (firstLetter != "#")
textblock.Inlines.Add(firstLetter);
textblock.Inlines.Add("#");
textblock.Inlines.Add(name);
textblock.Inlines.Add(foundUsername.Groups["suffix"].Captures[0].Value);
}
return textblock;
}
static void link_Click(object sender, RoutedEventArgs e)
{
try
{
System.Diagnostics.Process.Start(((Hyperlink)sender).NavigateUri.ToString());
}
catch
{
//TODO: Log specific URL that caused error
MessageBox.Show("There was a problem launching the specified URL.", "Error", MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
}
}
change your link click method to
static void link_click(Object sender, RequestNavigateEventArgs e) {
try {
System.Diagnostics.Process.Start(e.Uri.ToString());
} catch {
//TODO: Log specific URL that caused error
MessageBox.Show("There was a problem launching the specified URL.", "Error", MessageBoxButton.OK, MessageBoxImage.Exclamation);
} finally {
e.Handled = true;
}
}
change your
link.Click+=new RoutedEventHandler(link_Click);
to
link.RequestNavigate+=new RequestNavigateEventHandler(link_Click);
Set e.Handled=true in link_click to mark you've dealt with the link click to prevent the framework from additionally processing your link click further.
Alternatively you may be able to just set the TargetName property of Hyperlink to "_blank" and not need the process start command
The code below should make it work the same way in both cases (Page and Window)....
try this to open the hyperlink in web browser in MouseDown of the Hyperlink object.
Process.Start((e.OriginalSource as Hyperlink).NavigateUri.ToString());
e.Handled = true;
Let me know if this helps.