I have bound the required data in my UserControl
. This UserControl
is the Print Layout
of what I want to print. Great, now I can print it using PrintVisual
.
private async void Print(){
await Task.Run(() => {
var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
PrintDialog pd = new(){
PrintQueue = new PrintQueue(new PrintServer(), printerName)
};
pd.PrintVisual(PrintLayout, "Print Job Description");
});
}
...
await Print(); //Call the async method to Print.
The above code will work. However, it will block the UI and I wouldn't say I like it.
I was thinking of converting the layout into an image
and then printing it using PrintDocument
. But then the quality won't be that good and I will need to handle the size. In my case, whatever the size of the layout should be the output. I am also thinking of converting the layout into XPS
file or PDF
and then maybe printing it using PrintDocument
too but I'm not sure yet since this will require another IO processing and could lead to performance issues.
What could be the best approach here?
CodePudding user response:
Disregard the conversion to XPS or PDF file. Instead, you can transform your UserControl
into a FixedDocument and directly print it with PrintDialog.PrintDocument(); But still, any use of PrintDialog
will block the UI.
The solution is to create a Thread
inside your await/Task
and set its apartment state into STA (single-threaded apartment).
Here is a more detailed example.
private async void Print(){
await Task.Run(()=>{
// Create a thread within task and set single-threaded apartment state to avoid blocking the UI.
Thread newThread = new(() =>{
PrintLayout controlToPrint = new(){ //I don't know how you use your UserControl PrintLayout or set it's DataContext but you can do it like this;
DataContext = YourViewModel
};
FixedDocument fixedDocument = new();
//fixedDocument.DocumentPaginator.PageSize = new Size(YourOutputWith, YourOutputHeight); //you can set the size here, the value should be double.
FixedPage fixedPage = new();
fixedPage.Children.Add(controlToPrint); //Add your UserControl into FixedPage
fixedPage.Width = fixedDocument.DocumentPaginator.PageSize.Width; //Make your FixPage size same as FixedDocument.
fixedPage.Height = fixedDocument.DocumentPaginator.PageSize.Height; //Make your FixPage size same as FixedDocument.
PageContent pageContent = new();
((IAddChild)pageContent).AddChild(fixedPage); //You can add more if you want.
fixedDocument.Pages.Add(pageContent); //Finally add the PageContent into your FixedDocument which contain your UserControl
var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
PrintDialog pd = new(){
PrintQueue = new PrintQueue(new PrintServer(), printerName)
};
pd.PrintTicket.CopyCount = 1; //set the number of copies
pd.PrintDocument(fixedDocument.DocumentPaginator, "Print Job Description"); //Print it
});
// Set the thread to single-threaded apartment state.
newThread.SetApartmentState(ApartmentState.STA);
// Start the thread.
newThread.Start();
// Wait for thread completion. Blocks the calling thread,
// which is a ThreadPool thread.
newThread.Join();
});
}
You may also use your original code using the PrintDialog.PrintVisual(), just move it inside a thread with a single-threaded apartment
to avoid blocking the UI.
Forget the PrintDialog and STA (Single-Threaded-Apartment). For performance reasons, and much better approach, it is recommended to use either of AddJob or Write/WriteAsync of XpsDocumentWriter.
Here is an example of using WriteAsync of XpsDocumentWriter to avoid blocking the UI on print.
private void Print(){
PrintLayout controlToPrint = new(){ //I don't know how you use your UserControl PrintLayout or set it's DataContext but you can do it like this;
DataContext = YourViewModel
};
FixedDocument fixedDocument = new();
//fixedDocument.DocumentPaginator.PageSize = new Size(YourOutputWith, YourOutputHeight); //you can set the size here, the value should be double.
FixedPage fixedPage = new();
fixedPage.Children.Add(controlToPrint); //Add your UserControl into FixedPage
fixedPage.Width = fixedDocument.DocumentPaginator.PageSize.Width; //Make your FixPage size same as FixedDocument.
fixedPage.Height = fixedDocument.DocumentPaginator.PageSize.Height; //Make your FixPage size same as FixedDocument.
PageContent pageContent = new();
((IAddChild)pageContent).AddChild(fixedPage); //You can add more if you want.
fixedDocument.Pages.Add(pageContent); //Finally add the PageContent into your FixedDocument which contain your UserControl
var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
PrintQueue printQueue = new(new PrintServer(), printerName); //create a new PrintQueue with the selected printer
PrintTicket printTicket = printQueue.DefaultPrintTicket; //get the default PrintTicket, you can use this to define the settings of a print job, for example, setting the number of copies to print.
printTicket.CopyCount = 1; //Set the number of copies.
XpsDocumentWriter xpsDocumentWriter = PrintQueue.CreateXpsDocumentWriter(printQueue); //create a new XpsDocumentWriter for print queue
void OnPrintCompleted(object sender, WritingCompletedEventArgs e){ //trigger this method once the print is completed.
Trace.WriteLine("Printing Done.");
xpsDocumentWriter.WritingCompleted -= OnPrintCompleted; //always unregistered this event when done to avoid a potential memory leak.
}
xpsDocumentWriter.WritingCompleted = OnPrintCompleted; //handled the event when print is completed.
//xpsDocumentWriter.WritingCancelled = OnWritingCancelled; //you can also handle the event when print is cancelled. PrintProgress is also possible.
xpsDocumentWriter.WriteAsync(fixedDocument, printTicket); //print async without blocking the UI.
}
Refer to the documentation of XpsDocumentWriter Class for more information.