WPF Print canvas to fit page - c#

I am trying to print a canvas to both printer and file with the help of PrintDialog. I want the canvas to fit the page. I was able to achieve it using the following code
private void Print(Visual v)
{
System.Windows.FrameworkElement e = v as System.Windows.FrameworkElement ;
if (e == null)
return;
PrintDialog pd = new PrintDialog();
if (pd.ShowDialog() == true)
{
//store original scale
Transform originalScale = e.LayoutTransform;
//get selected printer capabilities
System.Printing.PrintCapabilities capabilities = pd.PrintQueue.GetPrintCapabilities(pd.PrintTicket);
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / e.ActualWidth, capabilities.PageImageableArea.ExtentHeight /
e.ActualHeight);
//Transform the Visual to scale
e.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
System.Windows.Size sz = new System.Windows.Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
e.Measure(sz);
e.Arrange(new System.Windows.Rect(new System.Windows.Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
//now print the visual to printer to fit on the one page.
pd.PrintVisual(v, "My Print");
//apply the original transform.
e.LayoutTransform = originalScale;
}
}
The above code seems to be working as expected but when I am writing it to a PDF file using a PDF writer, the canvas will be resized when the save dialog box shows up and it will be set back to normal. So there is a resizing happening in the UI as well.
This canvas is already a cloned one and this cannot be printed without showing it in the UI because there are some background operations happening to fill the elements in the canvas which can be started only after it's loaded. Hence the cloned canvas is shown as a print preview.
Does anyone know of a good solution, or perhaps improve on the existing one to solve the UI resizing issue?

Related

How to print WPF layout to not be dependent on the screen resolution?

I've been trying to use the following method to print a page on A4 paper. If my application runs on a laptop where the resolution is 1366x768 the printout scales down and I have no idea why? If I run it on a PC where the monitor resolution is higher, it prints as it should. I have tried to change PageMediaSize, ScaleTransform, LayoutTransform, Windows.Size with no success. What am I doing wrong?
The first 4 lines are there because I am sometimes using a 'print preview' where I'm scaling the page using a Viewbox, so the user can resize the preview. To not to affect the printing I am setting the Height and Width right before printing but outside of the PC's visible desktop area so it is not visible for the user.
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
this.Top = desktopWorkingArea.Bottom + 100;
this.Height = 1250;
this.Width = 815;
System.Windows.Controls.PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
printDlg.PrintQueue = new System.Printing.PrintQueue(new System.Printing.PrintServer(), printerName);
System.Printing.PrintCapabilities capabilities = printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / this.ActualWidth, capabilities.PageImageableArea.ExtentHeight / this.ActualHeight);
//Transform the Visual to scale
this.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
System.Windows.Size sz = new System.Windows.Size(this.ActualWidth, this.ActualHeight); //(8.5 * 96.0, 11.0 * 96.0);
//update the layout of the visual to the printer page size.
this.Measure(sz);
this.Arrange(new Rect(new System.Windows.Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(this, "Certificate - " + serialNumberTextBlock.Text);

Microsoft Report page orientation (VS 2013, C#)

I am printing labels with MS Report (c#/VS2013). The labels are 8 cm wide and 4 cm high. The printer sees and feeds them as portrait (no rotation) but the report viewer prints them landscape because the width is greater than the hight. Printer specific page orientation change before printing are ignored (!), so the labels are always printed in a 90° rotation against the label orientation. printer is an industial thermo transfer printer.
I don't understand why the page orientation is bound to the relation between hight and width and cannot be set indepenently! I already tried to change the orientation right before printing - this caused the labes be printed in several pieces but did not rotate them.
The only thing that helped was changing the paper size to 8 width and 8.1 height. Then the labels print correctly but this leads to a lot of empty pages (labels) and is no good solution.
The only way I see currently is redesigning all the labels rotated by 90° which is quite some effort, so I would be grateful if someone had a solution for this stange behaviour!
I solved this issue by rendering to an image and printing that through a PrintDocument object. This way I am able to send the landscape-formed label (8cm width, 4cm height) to the printer in portrait.
I am sure this can be done more elegant but due to time pressure I now go with this solution:
String tempDir = Path.GetTempFileName();
File.Delete(tempDir);
Directory.CreateDirectory(tempDir);
tempDir = Utils.addBackslash(tempDir);
ExportToPNG(tempDir);
String imageFile = Utils.FindFirstFile(tempDir, "*.png");
if (imageFile != "")
{
PrintDocument pd = new PrintDocument();
try
{
pd.DefaultPageSettings.PrinterSettings.PrinterName = DB.Instance.getSetting("label.printername");
}
catch
{
// This is just a preset, it may fail without consequences
}
pd.DefaultPageSettings.Landscape = false; // Now I can do it!
pd.PrintPage += (sender, args) =>
{
Image i = Image.FromFile(imageFile);
Point p = new Point(100, 100);
args.Graphics.DrawImage(i, 0, 0, i.Width, i.Height);
};
PrintDialog pdi = new PrintDialog();
pdi.Document = pd;
if (pdi.ShowDialog() == DialogResult.OK)
pd.Print();
pd.Dispose();
}

Add and display FixedDocument to WPF form

I'm programmatically generating a FixedDocument to help me print. FixedDocument.ActualWidth is coming out as 0. I suspect this is because I am not actually displaying the FixedDocument. How can I add and display a FixedDocument object?
This is a beginner question. I'm not skilled with WPF. I looked on MSDN/Goog. Sites make the assumption that I've already added the FixedDocument and just need to manipulate it.
I have:
private FixedDocument CreateFixedDocumentWithPages()
{
FixedDocument fixedDocument = CreateFixedDocument();
fixedDocument.DocumentPaginator.PageSize = size;
PageContent content = AddContentFromImage();
fixedDocument.Pages.Add(content);
return fixedDocument;
}
Pseudocode of what I want: myWpfFormObject.AddChild(fixedDocument)
for show FixedDocument:
in your Wpf window, add the DocumentViewer Controle, then set the Document property.
for ActualWidth pb:
I think you should call the methods Measure & Arrange for each FixedPage.
See the code below from the exapmle in msdn:
Size sz = new Size(8.5 * 96, 11 * 96);
fixedPage.Measure(sz);
fixedPage.Arrange(new Rect(new Point(), sz));
fixedPage.UpdateLayout();
see also https://stackoverflow.com/a/1695518/1271037
So I had a slightly different situation, but this answer got me close. I'm using the fixed document to display Tiffs from a scanner. Some of those Tiffs can be in legal letter format (so longer than the standard A4 8.5 by 11 size). The code below fixed my issue and this answer helped.
So ended up I'm taking a fixed document, creating a page content, creating a fixed page, creating an image.
Then taking the image and adding it to the fixed page, then taking the fixed page and adding it to the page content, then taking the page content and adding it to the fixed document.
System.Windows.Documents.FixedPage fixedPage = new System.Windows.Documents.FixedPage();
System.Windows.Documents.PageContent pageContent = new System.Windows.Documents.PageContent();
pageContent.Child = fixedPage;
if (fixedDocument == null)
{
fixedDocument = new System.Windows.Documents.FixedDocument();
}
fixedDocument.Pages.Add(pageContent);
System.Windows.Controls.Image image = new System.Windows.Controls.Image();
TiffBitmapDecoder decoder = new TiffBitmapDecoder(new Uri(tiffImage, UriKind.Relative), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnDemand);
image.Source = decoder.Frames[0];
fixedPage.Children.Add(image);
//Code to make the legal letter size work.
Size sz = new Size(decoder.Frames[0].Width, decoder.Frames[0].Height);
fixedPage.Width = sz.Width;
fixedPage.Height = sz.Height;
pageContent.Width = sz.Width;
pageContent.Height = sz.Height;

Copy UI element with adorner

I am working on taking screenshot of UI Element (WPF) in various size and i'm able to achieve this using "RenderTargetBitmap. But the UIElement which has an Adorner part is not coming while take copy. What should i do to achieve this. Any reference or code snippet?
As far as I know, elements don't have direct references to their adorners. Adorners do reference their element through AdornedElement though, so you could search for adorners assigned to your element like this:
var layer = AdornerLayer.GetAdornerLayer(element);
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element);
Here GetVisualChildren is an extension method which is defined as:
public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) {
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i));
}
The size of the adorner seems to include the size of the adorned element (although I'm not sure if this is always the case), so if there is only one adorner, that is your screenshot size. If there is more than one adorner, you would need to find the maximum for each bound (left, top, right, bottom) to calculate the screenshot region.
You will need to capture the AdornerDecorator which contains both the elements being adorned and the AdornerLayer (layer in the above code). That would be the visual parent of the layer:
var container = VisualTreeHelper.GetParent(layer) as Visual;
Once you have the container, you can render it with RenderTargetBitmap and crop it to the screenshot region.
For the screenshot region, you need the element bounds relative to the container. First, get the non-relative bounds:
var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));
Then get those bounds relative to the container:
var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds);
As I mentioned above, you will need to do this for the element as well as each of its adorners and combine the maximum bounds into one final Rect which is just large enough to contain all of them.
Finally, use CroppedBitmap to get a cropped version of the RenderTargetBitmap:
var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height));
CroppedBitmap and RenderTargetBitmap both inherit from BitmapSource, so you should be able to save it the same way.
You can use the native WPF Printing name space to print to an XPS file and this will include the adorner in the result (I tested it successfully)...
using System.Windows.Controls;
private void ExecutePrintCommand(object obj)
{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintVisual(_mainWindow, "Main Window with Adorner");
}
}
If you did not want to use the PrintDialog (which actually opens a dialog box). You can use the XpsDocumentWriter class to programmatically control the process. The enabling snippet for this is...
XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q);
xpsdw.Write(viewer.Document);
...which was extracted from here: Print FixedDocument programmatically And there are more articles about fine-tuning the process if that is part of your requirements. NOTE that the XPS file is actually a 'zip' file masquerading as an 'xps' file, so you can unzip it by changing the extension to see if the contents are of any use.
Secondarily, I tested saving a window with an adorner on a TextBox with this code...
private void SaveWithAdorner()
{
RenderTargetBitmap rtb = RenderVisaulToBitmap(_mainWindow, 500, 300);
MemoryStream file = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(file);
using (FileStream fstream = File.OpenWrite("Myimage.jpg"))
{
file.WriteTo(fstream);
fstream.Flush();
fstream.Close();
}
}
...with good results. I.e., the adorner appeared in the saved bitmap with its red border. This might differ from your code because I use a Png encoder (but saved to a 'jpg' file).
Although I have tested both approaches successfully, you'll need to check them on your hardware.
And lastly, as a last-ditch resort, you can deactivate WPF's hardware rendering mode and set it to software rendering...
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
...for which there's a nice SO thread here: Software rendering mode - WPF
In my case all I needed was call the AdornerLayer class like so:
public void GetScreenshotWithAdorner(Canvas canvas, string filename)
{
AdornerLayer adornerlayer = AdornerLayer.GetAdornerLayer(canvas);
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas); //renders the canvas screen first...
rtb.Render(adornerlayer); //... then it renders the adorner layer
SaveRTBAsPNG(rtb, filename);
}
private void SaveRTBAsPNG(RenderTargetBitmap bmp, string filename)
{
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(bmp));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
This works if you want to include ALL adorners in your canvas.

WPF Print Problem

When i select Microsoft XPS Document Writer as Printer, my output is perfect but when i select my HP 1020 printer machine, the printer outputs blank copy...Following is the code....
private void printButton_Click(object sender, RoutedEventArgs e)
{
PrintInvoice pi = new PrintInvoice();
pi.DataContext = this.DataContext;
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
if (printDlg.ShowDialog() == true)
{
pi.Margin = new Thickness(30);
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(pi, "First Fit to Page WPF Print");
}
}
This could be caused by a number of different things. There are a few steps that you can add which, when performed correctly, may cause the flying men to return with goods and knowledge.
First, you should scale to the printed page (code from a2zdotnet):
System.Printing.PrintCapabilities capabilities =
printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / this.ActualWidth, capabilities.PageImageableArea.ExtentHeight /
this.ActualHeight);
//Transform the Visual to scale
this.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
this.Measure(sz);
this.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(this, "Code ganked from http://www.a2zdotnet.com/View.aspx?id=66");
The cargo-cult code is in the Measure and Arrange steps. Often, if you call Measure and pass in a new Size(Double.MaxValue, Double.MaxValue) that's all you need to do.
The second ritual involves bumping the Dispatcher.
visual.DataContext = foo;
Dispatcher.Invoke((Action)()=>{;}); // bamp
// print here
Try these and see if it helps.
this XAML does the trick in some situations
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<... Name="myPrintElement" />
</ScrollViewer >

Categories