Printing the contents of a WPF WebBrowser - wpf

I'm trying to print the contents of a WPF WebBrowser control so that no print dialog is shown, but am having no luck.
I have tried the following and am sure it did work:
PrintDialog printDialog = new PrintDialog();
printDialog.PrintDocument(((IDocumentPaginatorSource)browser.Document).DocumentPaginator, "My App");
but for some reason, I'm now getting the following exception:
Unable to cast COM object of type 'mshtml.HTMLDocumentClass' to interface type 'System.Windows.Documents.IDocumentPaginatorSource'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{2C0C27DF-282F-3225-ADCD-CEC68F890EEB}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
The only thing I can think has changed on my PC is that I have installed IE8 since I last tried this, but would that really break it?

// Send the Print command to WebBrowser. For this code to work: add the Microsoft.mshtml .NET reference
mshtml.IHTMLDocument2 doc = WebBrowser1.Document as mshtml.IHTMLDocument2;
doc.execCommand("Print", true, null);
Source:
http://social.msdn.microsoft.com/Forums/en/wpf/thread/63ae5345-b2ca-45a9-9113-0ddc43e9925b

For silent printing without dialogs, use this code:
private void PrintCurrentPage()
{
// document must be loaded for this to work
IOleServiceProvider sp = WebBrowser1.Document as IOleServiceProvider;
if (sp != null)
{
Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");
const int OLECMDID_PRINT = 6;
const int OLECMDEXECOPT_DONTPROMPTUSER = 2;
dynamic wb; // should be of IWebBrowser2 type
sp.QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, out wb);
if (wb != null)
{
// this will send to the default printer
wb.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, null, null);
}
}
}
[ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IOleServiceProvider
{
[PreserveSig]
int QueryService([MarshalAs(UnmanagedType.LPStruct)] Guid guidService, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IDispatch)] out object ppvObject);
}
WebBrowser silent printing

To avoid taking a dependency on MSHTML you can simply do:
browser.InvokeScript("execScript", new object[] { "window.print();", "JavaScript" });
I devised the above solution when I was having trouble with a WPF project that was migrated to .Net Core 3 and was using Tony's answer that involves MSHTML reference. Referencing MSHTML in .Net Core is not very straightforward (see this github issue)

Related

passing values between silverlight applications

I have created a Silverlight Business Application which runs as my main silverlight page. For each hyperlink button on my "menu" I launch another Silverlight Application which is created as a different project in Visual Studio. These are non-Business Applications.
Everything is working well. However I'm trying to pass a value from my main SL application to the SL application inside.
I have been googling a lot and cannot find an answer.
As I understand the InitParam is used between ASP and SL, and not between SL apps.
Since the App config is launched for the first SL app and the app config for the second application in never lauched, I'm not able to use that (thats at least my understanding)
The value I want to pass is the login name and role, which is possible to get from webcontext in the Silverlight Business application, but I'm unable to get webcontext in the non-Business application which run inside.
This is how I launch my SL app inside the main SL app:
public Customers()
{
InitializeComponent();
this.Title = ApplicationStrings.CustomersPageTitle;
if (WebContext.Current.User.IsInRole("Users") || WebContext.Current.User.IsInRole("Administrators"))
{
WebClient client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
client.OpenReadAsync(new Uri("customers.xap", UriKind.Relative));
}
}
void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
string appManifest = new StreamReader(Application.GetResourceStream(new StreamResourceInfo(e.Result, null),
new Uri("AppManifest.xaml", UriKind.Relative)).Stream).ReadToEnd();
XElement deploymentRoot = XDocument.Parse(appManifest).Root;
List<XElement> deploymentParts =
(from assemblyParts in deploymentRoot.Elements().Elements() select assemblyParts).ToList();
Assembly asm = null;
AssemblyPart asmPart = new AssemblyPart();
foreach (XElement xElement in deploymentParts)
{
string source = xElement.Attribute("Source").Value;
StreamResourceInfo streamInfo = Application.GetResourceStream(new StreamResourceInfo(e.Result, "application/binary"), new Uri(source, UriKind.Relative));
if (source == "customers.dll")
{
asm = asmPart.Load(streamInfo.Stream);
}
else
{
asmPart.Load(streamInfo.Stream);
}
}
UIElement myData = asm.CreateInstance("customers.MainPage") as UIElement;
stackCustomers.Children.Add(myData);
stackCustomers.UpdateLayout();
}
Anyone?
i agree with ChrisF ,I think that Prism or MEF can resolve you problem.
any way,do some search on the web and look for these two classes:
**
LocalMessageSender
LocalMessageReceiver
**
good luck

Launching a CustomAction UI written with WPF from a WIX installer: Fail on STAThread issue

My WIX installer launches an immediate custom action.
The custom action starts a WPF dialog prompting the user for a BD connection parameters (I basically wrote the 'new connection' DB prompter dialog in WPF, to get me a connection string that the custom action can inject in the installed application's configuration file).
The WIX code is fairly simple to figure out, and I know I execute the custom action just fine - I put there a MessageBox and a MmsiBreak on the call to my custom action method. I get there without a problem.
When the custom action instantiates my WPF dialog window, I get an InvaliOperationException: "The calling thread must be STA, because many UI components require this".
The same code runs fine when I put it in a standard WPF application, because VisualStudio generates boiler plate code with Main() that has a STAThreadAttribute on it.
I can't tack that attribute on the msiexec caller, and if I try to set the thread apartment state in my custom action, it fails:
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
Is not supposed to work for framework past 2.0.
Is there any way to do what I'm trying to do here? I'd appreciate some pointers.
EDIT
I even tried to run the dialog in its own thread, e.g. the code is like this:
// Static class members
static ManualResetEvent _done = new ManualResetEvent(false);
static ActionResult _caResult;
static Session _session;
static Thread _worker;
[CustomAction]
static public ActionResult PromptForDB(Session session)
{
_session = session;
_worker = new Thread(WorkerThread);
_worker.Start();
_done.WaitOne();
return _caResult;
}
[STAThread]
static void WorkerThread()
{
try
{
Prompter wnd = new Prompter();
if (!(bool)wnd.ShowDialog())
{
_caResult = ActionResult.SkipRemainingActions;
}
else
{
// Harvest our properties (omitted from this post)
_caResult = ActionResult.Success;
}
catch (Exception ex)
{
_caResult = ActionResult.Failure;
_session.Log("Error: " + ex.Message);
}
finally
{
_done.Set();
}
}
That does not work either.
Before starting your new thread, set its ApartmentState as follows:
_worker.SetApartmentState(ApartmentState.STA);
See this:
The calling thread must be STA, because many UI components require this in WPF

How do I load an icon using GetResourceStream in WPF?

I've added an icon to my WPF project (BuildAction set to Resource) and I'm now trying to load that icon into a stream:
using(Stream iconStream = Application.GetResourceStream(new Uri("red.ico")).Stream)
{
// use the stream
}
This gives me the following error:
Invalid URI: The format of the URI could not be determined.
I've tried altering the Uri construction to include UriKind.Relative. This gives:
Cannot locate resource 'red.ico'.
I've looked at various articles on this For Example (from SO) and I can't see what I'm doing wrong.
Any help greatly appreciated.
Try something like new Uri("pack://application:,,,/red.ico"), see this page for more info on URIs in WPF.
Ultimately my issue boiled down to the fact that I had created a custom entry point for my WPF application and I was attempting to create a URI before the Application's static constructions had been called.
The code changed from something like this:
public static void Main()
{
var myUri = new Uri("/red.ico", UriKind.Relative);
var app = new AppMain();
app.Run();
}
to something like this:
public static void Main()
{
var app = new AppMain();
var myUri = new Uri("/red.ico", UriKind.Relative);
app.Run();
}

silverlight 4, dynamically loading xap modules

I know that it is possible to load xap modules dynamically using Prism or MEF framework. However, I'd like not to use those frameworks; instead load my xap files manually. So, I created the following class (adapted from internet):
public class XapLoader
{
public event XapLoadedEventHandler Completed;
private string _xapName;
public XapLoader(string xapName)
{
if (string.IsNullOrWhiteSpace(xapName))
throw new ArgumentException("Invalid module name!");
else
_xapName = xapName;
}
public void Begin()
{
Uri uri = new Uri(_xapName, UriKind.Relative);
if (uri != null)
{
WebClient wc = new WebClient();
wc.OpenReadCompleted += onXapLoadingResponse;
wc.OpenReadAsync(uri);
}
}
private void onXapLoadingResponse(object sender, OpenReadCompletedEventArgs e)
{
if ((e.Error == null) && (e.Cancelled == false))
initXap(e.Result);
if (Completed != null)
{
XapLoadedEventArgs args = new XapLoadedEventArgs();
args.Error = e.Error;
args.Cancelled = e.Cancelled;
Completed(this, args);
}
}
private void initXap(Stream stream)
{
string appManifest = new StreamReader(Application.GetResourceStream(
new StreamResourceInfo(stream, null), new Uri("AppManifest.xaml",
UriKind.Relative)).Stream).ReadToEnd();
XElement deploy = XDocument.Parse(appManifest).Root;
List<XElement> parts = (from assemblyParts in deploy.Elements().Elements()
select assemblyParts).ToList();
foreach (XElement xe in parts)
{
string source = xe.Attribute("Source").Value;
AssemblyPart asmPart = new AssemblyPart();
StreamResourceInfo streamInfo = Application.GetResourceStream(
new StreamResourceInfo(stream, "application/binary"),
new Uri(source, UriKind.Relative));
asmPart.Load(streamInfo.Stream);
}
}
}
public delegate void XapLoadedEventHandler(object sender, XapLoadedEventArgs e);
public class XapLoadedEventArgs : EventArgs
{
public Exception Error { get; set; }
public bool Cancelled { get; set; }
}
The above code works fine; I can load any xap the following way:
XapLoader xapLoader = new XapLoader("Sales.xap");
xapLoader.Completed += new XapLoadedEventHandler(xapLoader_Completed);
xapLoader.Begin();
Now, I have a UserControl called InvoiceView in the Sales.xap project, so I would like to instantiate the class. In the current project (Main.xap) I added reference to Sales.xap project, however, since I load it manually I set "Copy Local = False". But when executed, the following code throws TypeLoadException:
Sales.InvoiceView view = new Sales.InvoiceView();
It seems the code can't find InvoiceView class. But I checked that XapLoader's initXap() method was successfully executed. So why the code can't find InvoiceView class? Can someone help me with this problem?
This is based on the asker's self-answer below, rather than the question.
If you delete a project/module the output DLLs/XAP files do hang around. If you click the "show all files" button you will see some these left-over output files in your clientbin, bin and obj folders of related projects.
You can delete them individually from the project, or, when in doubt, search for all BIN and OBJ (e.g. using desktop explorer) and delete all those folders. The BIN/CLIENTBIN/OBJ folders will be recreated when needed (this the job that the "clean" option in Visual Studio should have done!)
Hope this helps.
Ok, I found the cause. The above code works. After creating a new silverlight project (Sales.xap) I happened to compile my solution once. Then I deleted App class in the Sales.xap and renamed default MainPage class to SalesView. However, no matter how many times I compile my solution, Visual Studio's development web server was loading the first version of Sales.xap (where from?), so my code couldn't find SalesView. In my host Asp.Net project I set development server's port to a different port number, and the problem gone. So the problem was with Visual Studio's development server. Apparently it is keeping compiled xap files in some temporary folder, and doesn't always update those xap files when source code changed.
What I do to avoid such problems when executing freshly compiled Silverlight is clear the browser cache, chrome even has a clear silverlight cache ;)
this XAP Cache phenomena is often due to the visual studio embedded web server (ASP.NET Development Server).
Just stop the occurence of this server and the cache will be cleared.
Start again your project and the latest build of your xap is called.

Silverlight communication with XML RPC console server

I want to comunicate with Console XML RPC server from my silvelight application. Is it possibile?
Steps:
1. Start the Console XML RPC server
Code for Console XML RPC server is this:
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using CookComputing.XmlRpc;
public class StateNameServer : MarshalByRefObject, IStateName
{
public string GetStateName(int stateNumber)
{
return "whatever";
}
}
class _
{
static void Main(string[] args)
{
IDictionary props = new Hashtable();
props["name"] = "MyHttpChannel";
props["port"] = 5678;
HttpChannel channel = new HttpChannel(props,null,new XmlRpcServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel,false);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(StateNameServer),"statename.rem",WellKnownObjectMode.Singleton);
Console.WriteLine("Press <ENTER> to shutdown");
Console.ReadLine();
}
}
Run Silverlight application
I used the code from http://code.google.com/p/xmlrpc-silverlight/
I created new Silverlight application to which I have attached the code from that link. When I start web site (in localhost with port 1139) which executes my SL app happens SecurityException.
void ResponseResponse(IAsyncResult result)
{
XmlRpcHelperRequestState state = result.AsyncState as XmlRpcHelperRequestState;
try
{
state.Response = (HttpWebResponse)state.Request.EndGetResponse(result);
...
}
catch (Exception e)
{
// comes here with SecurityException
}
finally
{
...
}
}
I am using VS2008 Professional,XP Professional, .net 3.5, Silverlight3. I will gladly provide any additional information (or code) which is needed.
I suspect that this is a case of a missing clientaccesspolicy.xml file.
Since your silverlight app will have been launched from another authority it will attempt to access this file the http://localhost:5678/. Since you little test doesn't support that file Silverlight blocks this cross "domain" activity.

Resources