I am trying to browse web pag1es using a listbox, I have added three links to it. All three links are loading well but when third link finishes loading I got this exception.
Exception is:
InvelidArguement = value of '3' is not valid for 'SelectedIndex'. Parameter name:
SelectedIndex
Warning is:
`The result of the exception is always 'true' since a value of type 'int' is
never equal to 'null' of type 'int?'
this is my program image:
this is my program code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listBox1.Items.Add("www.google.com");
listBox1.Items.Add("www.facebook.com");
listBox1.Items.Add("www.yahoo.com");
listBox1.SelectedIndex = 0;
listBox1.DataSource = listBox1.Items;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
do
{
webBrowser1.Navigate(listBox1.SelectedItem.ToString());
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
if(webBrowser1.ReadyState == WebBrowserReadyState.Complete)
{
listBox1.SelectedIndex = listBox1.SelectedIndex+1;
}
}
} while (listBox1.SelectedIndex != null);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
This statement here is causing an issue. When the selected index (in your example 0 - 2) reaches 3, it will throw an exception because there isn't an index of 3 available.
listBox1.SelectedIndex = listBox1.SelectedIndex+1;
Your loop will also never end because the SelectedIndex returns an integer which can never return null. You'll want to modify your code to check for length instead using an new index integer. Keep in mind the count will always return one value higher than the index (count starts at 1, index starts at 0).
Related
Consider:
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//int[] val = { 0, 0};
int val;
if (textBox1.Text == "")
{
MessageBox.Show("Input any no");
}
else
{
val = Convert.ToInt32(textBox1.Text);
Thread ot1 = new Thread(new ParameterizedThreadStart(SumData));
ot1.Start(val);
}
}
private static void ReadData(object state)
{
System.Windows.Forms.Application.Run();
}
void setTextboxText(int result)
{
if (this.InvokeRequired)
{
this.Invoke(new IntDelegate(SetTextboxTextSafe), new object[] { result });
}
else
{
SetTextboxTextSafe(result);
}
}
void SetTextboxTextSafe(int result)
{
label1.Text = result.ToString();
}
private static void SumData(object state)
{
int result;
//int[] icount = (int[])state;
int icount = (int)state;
for (int i = icount; i > 0; i--)
{
result += i;
System.Threading.Thread.Sleep(1000);
}
setTextboxText(result);
}
delegate void IntDelegate(int result);
private void button2_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
}
Why is this error occurring?
An object reference is required for the nonstatic field, method, or property 'WindowsApplication1.Form1.setTextboxText(int)
It looks like you are calling a non static member (a property or method, specifically setTextboxText) from a static method (specifically SumData). You will need to either:
Make the called member static also:
static void setTextboxText(int result)
{
// Write static logic for setTextboxText.
// This may require a static singleton instance of Form1.
}
Create an instance of Form1 within the calling method:
private static void SumData(object state)
{
int result = 0;
//int[] icount = (int[])state;
int icount = (int)state;
for (int i = icount; i > 0; i--)
{
result += i;
System.Threading.Thread.Sleep(1000);
}
Form1 frm1 = new Form1();
frm1.setTextboxText(result);
}
Passing in an instance of Form1 would be an option also.
Make the calling method a non-static instance method (of Form1):
private void SumData(object state)
{
int result = 0;
//int[] icount = (int[])state;
int icount = (int)state;
for (int i = icount; i > 0; i--)
{
result += i;
System.Threading.Thread.Sleep(1000);
}
setTextboxText(result);
}
More info about this error can be found on MSDN.
For this case, where you want to get a Control of a Form and are receiving this error, then I have a little bypass for you.
Go to your Program.cs and change
Application.Run(new Form1());
to
public static Form1 form1 = new Form1(); // Place this var out of the constructor
Application.Run(form1);
Now you can access a control with
Program.form1.<Your control>
Also: Don't forget to set your Control-Access-Level to Public.
And yes I know, this answer does not fit to the question caller, but it fits to googlers who have this specific issue with controls.
You start a thread which runs the static method SumData. However, SumData calls SetTextboxText which isn't static. Thus you need an instance of your form to call SetTextboxText.
Your method must be static
static void setTextboxText(int result)
{
if (this.InvokeRequired)
{
this.Invoke(new IntDelegate(SetTextboxTextSafe), new object[] { result });
}
else
{
SetTextboxTextSafe(result);
}
}
Credit to #COOLGAMETUBE for tipping me off to what ended up working for me. His idea was good but I had a problem when Application.SetCompatibleTextRenderingDefault was called after the form was already created. So with a little change, this is working for me:
static class Program
{
public static Form1 form1; // = new Form1(); // Place this var out of the constructor
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(form1 = new Form1());
}
}
I actually got this error because I was checking InnerHtml for some content that was generated dynamically - i.e. a control that is runat=server.
To solve this I had to remove the "static" keyword on my method, and it ran fine.
From my looking you give a null value to a textbox and return in a ToString() as it is a static method. You can replace it with Convert.ToString() that can enable null value.
Make the function static. This must solve your problem.
The essence, and solution, to your problem is this:
using System;
namespace myNameSpace
{
class Program
{
private void method()
{
Console.WriteLine("Hello World!");
}
static void Main(string[] args)
{
method();//<-- Compile Time error because an instantiation of the Program class doesnt exist
Program p = new Program();
p.method();//Now it works. (You could also make method() static to get it to work)
}
}
}
There doesn't appear to be a lot of people using Xamarin for Visual Studio consequently there isn't a lot of information specific to that platform out there.
Having said that, I've been trying to get a Floating Action Button (FAB) to work and it's been quite the exercise. I finally got it to appear and assign it to a variable in the activity with help from the nice folks who use StackOverflow, but cannot get the android:onClick="FabOnClick" call to work. Clicking on the FAB causes the app to crash with the error:
Unhandled Exception:
Java.Lang.IllegalStateException: Could not find method FabOnClick(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.design.widget.FloatingActionButton with id 'fab' occurred
This is the code in my activity:
public void FabOnClick(View v)
{
int x = 1;
}
It doesn't really do anything because I'm just trying to capture the click event for now. I set a breakpoint on the int x = 1 line to see when it's is executed. So what am I missing?
* Update *
I updated my activity code based on #Digitalsa1nt's answer below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Acr.UserDialogs;
using Android.Net;
using System.Net;
using Android.Support.Design.Widget;
using System.Threading.Tasks;
using Android.Views.InputMethods;
using static Android.Views.View;
namespace OML_Android
{
[Activity(Label = "CreateAccount")]
public class CreateAccount : Activity
{
public string result = "";
public EditText aTextboxUsername;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.CreateAccount);
RequestedOrientation = Android.Content.PM.ScreenOrientation.Portrait;
aTextboxUsername = FindViewById<EditText>(Resource.Id.aTextboxUsername);
EditText aTextboxPassword = FindViewById<EditText>(Resource.Id.aTextboxPassword);
EditText aTextboxPassword2 = FindViewById<EditText>(Resource.Id.aTextboxPassword2);
EditText txtEmailAddress = FindViewById<EditText>(Resource.Id.txtEmailAddress);
EditText txtEmailAddress2 = FindViewById<EditText>(Resource.Id.txtEmailAddress2);
EditText txtFirstName = FindViewById<EditText>(Resource.Id.first_name);
EditText txtMI = FindViewById<EditText>(Resource.Id.mi);
EditText txtLastName = FindViewById<EditText>(Resource.Id.last_name);
EditText txtAddress = FindViewById<EditText>(Resource.Id.address);
EditText txtCity = FindViewById<EditText>(Resource.Id.city);
Spinner spnState = FindViewById<Spinner>(Resource.Id.state);
EditText txtZip = FindViewById<EditText>(Resource.Id.zip);
MaskedEditText.MaskedEditText txtPhone = FindViewById<MaskedEditText.MaskedEditText>(Resource.Id.phone);
Spinner spnCompany = FindViewById<Spinner>(Resource.Id.company_spinner);
Spinner spnDept = FindViewById<Spinner>(Resource.Id.department_spinner);
Spinner spnSection = FindViewById<Spinner>(Resource.Id.section_spinner);
Button ButtonSubmit = FindViewById<Button>(Resource.Id.button_submit);
ScrollView sv = FindViewById<ScrollView>(Resource.Id.scrollView1);
ButtonSubmit.SetBackgroundColor(Android.Graphics.Color.YellowGreen);
// Hide the keyboard (also doesn't work)
InputMethodManager board = (InputMethodManager)GetSystemService(Context.InputMethodService);
board.HideSoftInputFromWindow(aTextboxUsername.WindowToken, 0);
// get the floating action button.
FloatingActionButton myFab = FindViewById< FloatingActionButton>(Resource.Id.fab);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
DataInterfaceWeb.DataInterface myService = new DataInterfaceWeb.DataInterface();
myFab.Click += FabButton_Click(); // <-- get error here
try
{
ConnectivityManager connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService);
NetworkInfo activeConnection = connectivityManager.ActiveNetworkInfo;
bool isOnline = (activeConnection != null) && activeConnection.IsConnected;
if (!isOnline)
{
showMessage("There is no internet or cell phone connection. Connect to a network or connect to a cellular network.", "ERROR");
}
}
catch (Exception ex)
{
showMessage("Connectivity Manager failed to create a connection due to error: " + ex.Message, "ERROR");
};
// Create your application here
ButtonSubmit.Click += async (sender, e) =>
{
try
{
result = myService.CheckForUser(Master.username, Master.password, aTextboxUsername.Text);
if (result.ToUpper() == "Y")
{
await showMessage("Username " + aTextboxUsername.Text + " is already in use. Please choose another", "ERROR");
// aTextboxUsername.SetSelectAllOnFocus(true);
aTextboxUsername.RequestFocus();
View insideView = FindViewById<EditText>(Resource.Id.aTextboxUsername);
sv.ScrollTo(0, (int)insideView.GetY());
aTextboxUsername.SelectAll();
}
}
catch (Exception ex)
{
showMessage("Account creation attempt failed due to error: " + ex.Message, "ERROR");
}
};
}
public async Task showMessage(string message, string messageType)
{
var result = await UserDialogs.Instance.ConfirmAsync(new ConfirmConfig
{
Message = messageType + System.Environment.NewLine + message,
OkText = "Ok",
});
}
public void FabButton_Click()
{
int x = 1;
}
}
}
The error I get now is:
Cannot implicitly convert 'void' to 'SystemEventHandler' on the line myFab.Click += FabButton_Click();.
#Digitalsa1nt did point me in the right direction. Instead of
fabButton.Click += FabButton_Click;
I just wired up an event, as the error suggested (duh):
myFab.Click += (sender, e) =>
{
FabButton_Click();
};
It now works as I would expect.
So I'm making a couple of assumptions in this answer. Firstly that you are working with a Xamarin.Native project and not a Xamarin.Forms project.
Secondly I am assuming you are using the FloatingActionButton from one of the support libraries such as: Android.Support.Design.Widget (base / V4 / V7).
Once you've defined your FAB within the AXML Layout page:
<android.support.design.widget.FloatingActionButton
app:backgroundTint="#color/colourPrimary"
android:id="#+id/fabButton"
android:src="#drawable/image"
app:fabSize="normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:elevation="16dp"
android:translationZ="12dp"
app:rippleColor="#ffa9a9a9" />
You can get it from within your activity as such:
using Android.Support.Design.Widget;
// declare variable
private FloatingActionButton fabButton;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// call base
base.OnCreateView(inflater, container, savedInstanceState);
// inflate our view
var view = inflater.Inflate(Resource.Layout.MainTabWishlistPage, container, false);
// get our instance of the button using the resource ID.
fabButton = view.FindViewById<FloatingActionButton>(Resource.Id.fabButton);
// assign to click event
fabButton.Click += FabButton_Click;
}
private void FabButton_Click(object sender, EventArgs e)
{
int x = 1;
}
The above example is based on it being a fragment rather than an activity, but the methodology is the same.
Official Git Repo:
Xamarin/monodroid-samples - Floating Action Button Basic
Random online guide:
android-material-design-floating-action
In case this is a Xamarin.Forms project, look into James Montemagno's library (p.s one of the developers that works on Xamarin and creates tons of libraries to help make your life easier, definitely look through his other repos.)
jamesmontemagno/FloatingActionButton-for-Xamarin.Android
I am working with an attached behavior for logging user actions on a ScrollBar.
my code:
class ScrollBarLogBehavior : Behavior<ScrollBar>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
...
var track = (Track)AssociatedObject.Template.FindName("PART_Track", AssociatedObject);
// ** HERE is the problem: track is null ! **
...
}
How can I detect that the template has loaded and I can find the Track?
(when I call AssociatedObject.Template.LoadContent() the result containt the requested Track, so it i a matter of timing and not a matter of wrong template or naming)
Override the method OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBox = Template.FindName("PART_Textbox", this) as TextBox;
}
I did not find any good way to detect when the template was loaded. However, I did find a way to find the Track:
in OnAttached() - register to Scroll event fo the ScrollBar (this can only happen after the entire template is loaded, of course):
protected override void OnAttached()
{
base.OnAttached();
_scrollHandler = new ScrollEventHandler(AssociatedObject_Scroll);
AssociatedObject.AddHandler(ScrollBar.ScrollEvent, _scrollHandler, true);
}
When handling the Scroll event, remove registration and find the Thumb:
void AssociatedObject_Scroll(object sender, ScrollEventArgs e)
{
var track = (Track)AssociatedObject.Template.FindName("PART_Track", Associated
if (track == null)
return;
AssociatedObject.RemoveHandler(ScrollBar.ScrollEvent, _scrollHandler);
// do my work with Track
...
}
If I understand correctly, you wish to create an attached behavior that will reference a template part after the ScrollBar has been loaded.
The following should work:
internal static class ScrollBarLogBehavior
{
public static readonly DependencyProperty LogUserActionProperty = DependencyProperty.RegisterAttached(
"LogUserAction",
typeof(bool),
typeof(ScrollBarLogBehavior),
new UIPropertyMetadata(default(bool), LogUserActionChanged));
public static bool GetLogUserAction(DependencyObject obj)
{
return (bool)obj.GetValue(LogUserActionProperty);
}
public static void SetLogUserAction(DependencyObject obj, bool value)
{
obj.SetValue(LogUserActionProperty, value);
}
public static void LogUserActionChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
if (s is ScrollBar scrollBar)
{
scrollBar.Loaded += OnScrollBarLoaded;
}
}
private static void OnScrollBarLoaded(object sender, RoutedEventArgs e)
{
if (sender is ScrollBar scrollBar)
{
if (scrollBar.Template != null)
{
// I'm not sure, but the `name` in the following method call might be case sensitive.
if (scrollBar.Template.FindName("PART_Track", scrollBar) is Track track)
{
// do work with `track` here
}
}
}
}
}
where you would "attach" the behavior in your XAML with:
<ScrollBar guiControls:ScrollBarLogBehavior.LogUserAction="True">
<!-- more here -->
</ScrollBar>
BE ADVISED: this implementation completely ignores the bool value that is being set for LogUserAction
I have an external dll written in C# and I studied from the assemblies documentation that it writes its debug messages to the Console using Console.WriteLine.
this DLL writes to console during my interaction with the UI of the Application, so i don't make DLL calls directly, but i would capture all console output , so i think i got to intialize in form load , then get that captured text later.
I would like to redirect all the output to a string variable.
I tried Console.SetOut, but its use to redirect to string is not easy.
As it seems like you want to catch the Console output in realtime, I figured out that you might create your own TextWriter implementation that fires an event whenever a Write or WriteLine happens on the Console.
The writer looks like this:
public class ConsoleWriterEventArgs : EventArgs
{
public string Value { get; private set; }
public ConsoleWriterEventArgs(string value)
{
Value = value;
}
}
public class ConsoleWriter : TextWriter
{
public override Encoding Encoding { get { return Encoding.UTF8; } }
public override void Write(string value)
{
if (WriteEvent != null) WriteEvent(this, new ConsoleWriterEventArgs(value));
base.Write(value);
}
public override void WriteLine(string value)
{
if (WriteLineEvent != null) WriteLineEvent(this, new ConsoleWriterEventArgs(value));
base.WriteLine(value);
}
public event EventHandler<ConsoleWriterEventArgs> WriteEvent;
public event EventHandler<ConsoleWriterEventArgs> WriteLineEvent;
}
If it's a WinForm app, you can setup the writer and consume its events in the Program.cs like this:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (var consoleWriter = new ConsoleWriter())
{
consoleWriter.WriteEvent += consoleWriter_WriteEvent;
consoleWriter.WriteLineEvent += consoleWriter_WriteLineEvent;
Console.SetOut(consoleWriter);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
static void consoleWriter_WriteLineEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "WriteLine");
}
static void consoleWriter_WriteEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "Write");
}
It basically amounts to the following:
var originalConsoleOut = Console.Out; // preserve the original stream
using(var writer = new StringWriter())
{
Console.SetOut(writer);
Console.WriteLine("some stuff"); // or make your DLL calls :)
writer.Flush(); // when you're done, make sure everything is written out
var myString = writer.GetStringBuilder().ToString();
}
Console.SetOut(originalConsoleOut); // restore Console.Out
So in your case you'd set this up before making calls to your third-party DLL.
You can also call SetOut with Console.OpenStandardOutput, this will restore the original output stream:
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
Or you can wrap it up in a helper method that takes some code as an argument run it and returns the string that was printed. Notice how we gracefully handle exceptions.
public string RunCodeReturnConsoleOut(Action code)
{
string result;
var originalConsoleOut = Console.Out;
try
{
using (var writer = new StringWriter())
{
Console.SetOut(writer);
code();
writer.Flush();
result = writer.GetStringBuilder().ToString();
}
return result;
}
finally
{
Console.SetOut(originalConsoleOut);
}
}
Using solutions proposed by #Adam Lear and #Carlo V. Dango I created a helper class:
public sealed class RedirectConsole : IDisposable
{
private readonly Action<string> logFunction;
private readonly TextWriter oldOut = Console.Out;
private readonly StringWriter sw = new StringWriter();
public RedirectConsole(Action<string> logFunction)
{
this.logFunction = logFunction;
Console.SetOut(sw);
}
public void Dispose()
{
Console.SetOut(oldOut);
sw.Flush();
logFunction(sw.ToString());
sw.Dispose();
}
}
which can be used in the following way:
public static void MyWrite(string str)
{
// print console output to Log/Socket/File
}
public static void Main()
{
using(var r = new RedirectConsole(MyWrite)) {
Console.WriteLine("Message 1");
Console.WriteLine("Message 2");
}
// After the using section is finished,
// MyWrite will be called once with a string containing all messages,
// which has been written during the using section,
// separated by new line characters
}
I'm trying to print XPS documents from a windows service on the .net framework. Since Microsoft does not support printing by using System.Drawing.Printing nor by using System.Printing (WPF), I'm using the native XPSPrint API.
This is suggested to me by Aspose in http://www.aspose.com/documentation/.net-components/aspose.words-for-.net/howto-print-a-document-on-a-server-via-the-xpsprint-api.html.
When I try to print an XPS document from a windows service, the result contains strange characters instead of the text I want.
I tried with different printers (including virtual printers like for instance PDFCreator), different users and user-privileges for the service, different xps generators (aspose, word 2007, word 2010), different platforms (windows 7, windows 2008 R2) but all have the same result.
Does anybody knows how to solve this? Any help would be appreciated!
For those who want to try it, I shared some files via:
https://docs.google.com/leaf?id=0B4J93Ly5WzQKNWU2ZjM0MDYtMjFiMi00NzM0LTg4MTgtYjVlNDA5NWQyMTc3&hl=nl
document.xps: the XPS document to print
document_printed_to_pdfcreator.pdf: the printed document that demonstrates what is going wrong
XpsPrintTest.zip: a sample VS2010 solution with the sample code
The sample code for the managed windows service is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
namespace PrintXpsService
{
public partial class XpsPrintService : ServiceBase
{
// Change name of printer here
private String f_printerName = "PDFCreator";
// path to some file where logging is done
private String f_logFile = #"C:\temp\testdoc\xps_printing_service_log.txt";
// path to xps file to print
private String f_xpsFile = #"C:\temp\testdoc\document.xps";
public XpsPrintService()
{
InitializeComponent();
}
private void Log(String fmt, params Object[] args)
{
try
{
DateTime now = DateTime.Now;
using (StreamWriter wrt = new StreamWriter(f_logFile, true))
{
wrt.Write("{0} {1} - ", now.ToShortDateString(), now.ToShortTimeString());
wrt.WriteLine(fmt, args);
}
}
catch (Exception ex)
{
}
}
protected override void OnStart(string[] args)
{
// uncomment to allow to connect debugger
//int i = 0;
//while (i == 0)
//{
// if (i == 0)
// {
// Thread.Sleep(1000);
// }
//}
Log("Starting Service");
try
{
Log("Printing xps file {0}", f_xpsFile);
using (Stream stream = new FileStream(f_xpsFile, FileMode.Open, FileAccess.Read))
{
Log("Starting to print on printer {0}", f_printerName);
String jobName = f_xpsFile;
this.Print(stream, jobName);
}
Log("Document printed");
}
catch (Exception ex)
{
Log("Exception during execution: {0}", ex.Message);
Log(" {0}", ex.StackTrace);
Exception inner = ex.InnerException;
while (inner != null)
{
Log("=== Inner Exception: {0}", inner.Message);
Log(" {0}", inner.StackTrace);
inner = inner.InnerException;
}
}
}
protected override void OnStop()
{
}
public void Print(Stream stream, String jobName)
{
String printerName = f_printerName;
IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
try
{
IXpsPrintJob job;
IXpsPrintJobStream jobStream;
StartJob(printerName, jobName, completionEvent, out job, out jobStream);
CopyJob(stream, job, jobStream);
WaitForJob(completionEvent, -1);
CheckJobStatus(job);
}
finally
{
if (completionEvent != IntPtr.Zero)
CloseHandle(completionEvent);
}
}
private void StartJob(String printerName,
String jobName, IntPtr completionEvent,
out IXpsPrintJob job,
out IXpsPrintJobStream jobStream)
{
int result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
null, 0, out job, out jobStream, IntPtr.Zero);
if (result != 0)
throw new Win32Exception(result);
}
private void CopyJob(Stream stream, IXpsPrintJob job, IXpsPrintJobStream jobStream)
{
try
{
byte[] buff = new byte[4096];
while (true)
{
uint read = (uint)stream.Read(buff, 0, buff.Length);
if (read == 0)
break;
uint written;
jobStream.Write(buff, read, out written);
if (read != written)
throw new Exception("Failed to copy data to the print job stream.");
}
// Indicate that the entire document has been copied.
jobStream.Close();
}
catch (Exception)
{
// Cancel the job if we had any trouble submitting it.
job.Cancel();
throw;
}
}
private void WaitForJob(IntPtr completionEvent, int timeout)
{
if (timeout < 0)
timeout = -1;
switch (WaitForSingleObject(completionEvent, timeout))
{
case WAIT_RESULT.WAIT_OBJECT_0:
// Expected result, do nothing.
break;
case WAIT_RESULT.WAIT_TIMEOUT:
// timeout expired
throw new Exception("Timeout expired");
case WAIT_RESULT.WAIT_FAILED:
throw new Exception("Wait for the job to complete failed");
default:
throw new Exception("Unexpected result when waiting for the print job.");
}
}
private void CheckJobStatus(IXpsPrintJob job)
{
XPS_JOB_STATUS jobStatus;
job.GetJobStatus(out jobStatus);
switch (jobStatus.completion)
{
case XPS_JOB_COMPLETION.XPS_JOB_COMPLETED:
// Expected result, do nothing.
break;
case XPS_JOB_COMPLETION.XPS_JOB_IN_PROGRESS:
// expected, do nothing, can occur when printer is paused
break;
case XPS_JOB_COMPLETION.XPS_JOB_FAILED:
throw new Win32Exception(jobStatus.jobStatus);
default:
throw new Exception("Unexpected print job status.");
}
}
[DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
private static extern int StartXpsPrintJob(
[MarshalAs(UnmanagedType.LPWStr)] String printerName,
[MarshalAs(UnmanagedType.LPWStr)] String jobName,
[MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
IntPtr progressEvent, // HANDLE
IntPtr completionEvent, // HANDLE
[MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
UInt32 printablePagesOnCount,
out IXpsPrintJob xpsPrintJob,
out IXpsPrintJobStream documentStream,
IntPtr printTicketStream); // This is actually "out IXpsPrintJobStream", but we don't use it and just want to pass null, hence IntPtr.
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
[DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
}
/// <summary>
/// This interface definition is HACKED.
///
/// It appears that the IID for IXpsPrintJobStream specified in XpsPrint.h as
/// MIDL_INTERFACE("7a77dc5f-45d6-4dff-9307-d8cb846347ca") is not correct and the RCW cannot return it.
/// But the returned object returns the parent ISequentialStream inteface successfully.
///
/// So the hack is that we obtain the ISequentialStream interface but work with it as
/// with the IXpsPrintJobStream interface.
/// </summary>
[Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")] // This is IID of ISequenatialSteam.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJobStream
{
// ISequentualStream methods.
void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
// IXpsPrintJobStream methods.
void Close();
}
[Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJob
{
void Cancel();
void GetJobStatus(out XPS_JOB_STATUS jobStatus);
}
[StructLayout(LayoutKind.Sequential)]
struct XPS_JOB_STATUS
{
public UInt32 jobId;
public Int32 currentDocument;
public Int32 currentPage;
public Int32 currentPageTotal;
public XPS_JOB_COMPLETION completion;
public Int32 jobStatus; // UInt32
};
enum XPS_JOB_COMPLETION
{
XPS_JOB_IN_PROGRESS = 0,
XPS_JOB_COMPLETED = 1,
XPS_JOB_CANCELLED = 2,
XPS_JOB_FAILED = 3
}
enum WAIT_RESULT
{
WAIT_OBJECT_0 = 0,
WAIT_ABANDONED = 0x80,
WAIT_TIMEOUT = 0x102,
WAIT_FAILED = -1 // 0xFFFFFFFF
}
}
Note: some links for more information:
MS not supporting printing from managed code: http://support.microsoft.com/kb/324565 , http://msdn.microsoft.com/en-us/library/system.drawing.printing.aspx and http://msdn.microsoft.com/en-us/library/bb613549.aspx
XPSPrint API: http://msdn.microsoft.com/en-us/library/dd374565(VS.85).aspx
I talked with microsoft about this issue and we discovered the problem is related to incorrect font substitution in the printer-spooler. When the printer is set to not spool the documents, they are printed correctly, also from a windows service. Otherwise, all fonts, except arial (and maybe some others), are substituted by another font. In the sample I provided, calibri is substituted by wingdings.
So, they acknowledge this to be a bug but at the moment they will not resolve it. It will depend on how many people will suffer from this bug in order for them to decide whether are not they are willing to fix it...