Printing more than one page for control - c#

This code will only print one page but the height of the control exceeds the page height and therefore will need to be printed on a second page. I have been doing some investigation as to what will enable the control to proceed onto another page. I got as far as DocumentPaginator and fiddled with the size making it bigger and smaller than the sz variable but no difference. Any Ideas as to what controls a creation of a new page? does the size relate to pagination?
private void Print()
{
var pd = new PrintDialog();
var document = new FixedDocument();
var fixedPage = new FixedPage();
var pageContent = new PageContent();
System.Printing.PrintCapabilities capabilities = pd.PrintQueue.GetPrintCapabilities(pd.PrintTicket);
System.Windows.Size sz = new System.Windows.Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
MarSheetReport mar = new MarSheetReport();
document.DocumentPaginator.PageSize = sz;
Transform originalScale = fixedPage.LayoutTransform;
//get selected printer capabilities
fixedPage.LayoutTransform = new ScaleTransform(0.2823293807641634 + 0.2498215560314061, 0.2823293807641634 + 0.2498215560314061);
fixedPage.Width = sz.Width;
fixedPage.Height = sz.Height;
// Add visual, measure/arrange page.
fixedPage.Children.Add(mar.o);
fixedPage.Measure(sz);
fixedPage.Arrange(new System.Windows.Rect(new System.Windows.Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
fixedPage.UpdateLayout();
//fixedPage.LayoutTransform = originalScale;
((IAddChild)pageContent).AddChild(fixedPage);
document.Pages.Add(pageContent);
pd.PrintDocument(document.DocumentPaginator, "My Document");
}

Here is one way of doing it i.e creating the bitmap from the visual and breaking it on multiple pages.
http://www.codeproject.com/Articles/339416/Printing-large-WPF-UserControls

Related

Printing a stackpanel containing a listbox over multiple pages wpf

Hi I am looking to print a StackPanel that contains a listbox which can contain an infinite number of items and therefore needs to print over multiple pages. I found this code online and it works fine.
public static FixedDocument GetFixedDocument(FrameworkElement toPrint, PrintDialog printDialog)
{
if (printDialog == null)
{
printDialog = new PrintDialog();
}
var capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
var pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
var visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
var fixedDoc = new FixedDocument();
//If the toPrint visual is not displayed on screen we neeed to measure and arrange it
toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));
//
var size = toPrint.DesiredSize;
//Will assume for simplicity the control fits horizontally on the page
double yOffset = 0;
while (yOffset < size.Height)
{
var vb = new VisualBrush(toPrint)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Top,
ViewboxUnits = BrushMappingMode.Absolute,
TileMode = TileMode.None,
Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height)
};
var pageContent = new PageContent();
var page = new FixedPage();
((IAddChild)pageContent).AddChild(page);
fixedDoc.Pages.Add(pageContent);
page.Width = pageSize.Width;
page.Height = pageSize.Height;
var canvas = new Canvas();
FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
canvas.Width = visibleSize.Width;
canvas.Height = visibleSize.Height;
canvas.Background = vb;
page.Children.Add(canvas);
yOffset += visibleSize.Height;
}
return fixedDoc;
}
However this causes certain items of a listbox to be cut off at the bottom of a page and continued on the next page (as shown below). Is it possible to modify this code in any way to determine the size of the page and if the current listboxitem does not fit onto this page that it starts on the next page? Quite new to all this so any help would be greatly appreciated.
I had a similiar task once and came up with this code, which uses a 'dummy' renderer to determine the height of the element up front and then either adds it to the current page or creates a new one. For sure, that's not a very beautiful solution, but it did the job at the time. Maybe you can take sth. away from it.
Size A4Size = new Size(793.92, 1122.24);
Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
var pages = new List<FrameworkElement>();
int pageNumber = 0;
var printDate = DateTime.Now;
var size = A4Size;
var currentPage = new Report(size, printDate);
currentPage.Render();
var listElements = new Queue<ElementsToPrint>(...);
var dummyRenderer = new Viewbox();
dummyRenderer.Child = currentPage;
dummyRenderer.Measure(InfiniteSize);
dummyRenderer.Arrange(new Rect(dummyRenderer.DesiredSize));
dummyRenderer.UpdateLayout();
var template = (DataTemplate)View.FindResource("ItemTemplate");
dummyRenderer.Child = null;
var availableHeight = currentPage.View.ActualHeight;
while (listElements.Count > 0)
{
var elementToRender = listElements.Dequeue();
dummyRenderer.Child = new ListViewItem()
{
Content = elementToRender,
ContentTemplate = template,
Foreground = Brushes.Black
};
dummyRenderer.Measure(InfiniteSize);
dummyRenderer.Arrange(new Rect(dummyRenderer.DesiredSize));
dummyRenderer.UpdateLayout();
var renderedItem = (ListViewItem)dummyRenderer.Child;
dummyRenderer.Child = null;
var willItFit = availableHeight > renderedItem.ActualHeight;
if (willItFit)
{
currentPage.DataListView.Items.Add(renderedItem);
availableHeight -= renderedItem.ActualHeight;
}
else
{
dummyRenderer.Child = currentPage;
dummyRenderer.Measure(InfiniteSize);
dummyRenderer.Arrange(new Rect(dummyRenderer.DesiredSize));
dummyRenderer.UpdateLayout();
dummyRenderer.Child = null;
pages.Add(currentPage);
// Set up a new Page
pageNumber++;
currentPage = new DiaryReport(size,pageNumber,printDate,anonymous);
dummyRenderer.Child = currentPage;
dummyRenderer.Measure(InfiniteSize);
dummyRenderer.Arrange(new Rect(dummyRenderer.DesiredSize));
dummyRenderer.UpdateLayout();
dummyRenderer.Child = null;
availableHeight = currentPage.DataListView.ActualHeight;
currentPage.DataListView.Items.Add(renderedItem);
availableHeight -= renderedItem.ActualHeight;
}

WPF FixedPage with same instance of UserControl

First off, I'm not sure if I phrased the title correctly. There are UserControls which are added via a ViewModel and I find them by searching the VisualTree and add them to a ObservableCollection<Grid>. What I would like to do is Print each instance of the UserControl that I retrieve from the VisualTree into a FixedDocument but with each UserControl being on a single page until it fills that page and moves on to the next page.
Here is the code for my current Print Button Click Event:
private async void btnPrint_Click(object sender, RoutedEventArgs e)
{
try
{
if (tabMain.Items.Count > 0)
{
tab = new TabLayout();
tab.Payslip = new ObservableCollection<Grid>();
foreach (var grid in FindVisualChildren<Grid>(this))
{
if (grid.Name == "pdfFile")
{
tab.Payslip.Add(grid);
}
}
FrameworkElement toPrint = new FrameworkElement();
PrintDialog printDialog = new PrintDialog();
PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
Size visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
FixedDocument fixedDoc = new FixedDocument();
StackPanel panel = new StackPanel(); //was trying to stack them in a stackpanel first but it threw an exception about same instance of usercontrol blah blah...
foreach (var doc in tab.Payslip.ToList())
{
double yOffset = 0;
doc.Measure((new Size(double.PositiveInfinity, double.PositiveInfinity)));
doc.Arrange(new Rect(new Point(0, 0), doc.DesiredSize));
Size size = doc.DesiredSize;
while (yOffset < size.Height)
{
VisualBrush vb = new VisualBrush(doc);
vb.Stretch = Stretch.None;
vb.AlignmentX = AlignmentX.Left;
vb.AlignmentY = AlignmentY.Top;
vb.ViewboxUnits = BrushMappingMode.Absolute;
vb.TileMode = TileMode.None;
vb.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);
FixedPage page = new FixedPage();
PageContent pageContent = new PageContent();
((IAddChild)pageContent).AddChild(page);
fixedDoc.Pages.Add(pageContent);
page.Width = fixedDoc.DocumentPaginator.PageSize.Width;
page.Height = fixedDoc.DocumentPaginator.PageSize.Height;
Canvas canvas = new Canvas();
FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
canvas.Width = visibleSize.Width;
canvas.Height = visibleSize.Height;
canvas.Background = vb;
page.Children.Add(canvas);
yOffset += visibleSize.Height;
}
}
//printDialog.PrintDocument(fixedDoc.DocumentPaginator, "");
ShowPrintPreview(fixedDoc);
}
}
catch(Exception ex)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = ex.ToString() }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
This is how it looks when I try printing three Tabs (UserControls are hosted in Tabs):
As you can see here it prints three separate pages
I want all three on one page and if I print 10 tabs, then it should fill the first page and go on to the next page.
The last time I asked a similar question I was questioned on whether I wrote the code. Bits and pieces of this code came from similar FixedDocument questions on StackOverflow but it has been edited to the point where it actually works for me. So yes I know that the FixedPage reference inside the foreach is whats causing the creation of the three separate pages and I do understand the code.
Summary:
What I want to know is how to get the UserControls from each Tab, onto a single page until its full, without getting the "Specified element is already the logical child of another element. Disconnect it first." error.
I ended up using ItemsControl to hold my ObservableCollection of Controls as a list and print from the ItemsControl using this XpsDocumentWriter method.
public void PrintItemsTo(ItemsControl ic, String jobName)
{
PrintDialog dlg = new PrintDialog();
dlg.UserPageRangeEnabled = true;
if (dlg.ShowDialog().GetValueOrDefault())
{
PageRange range = dlg.PageRange;
// range check - user selection starts from 1
if (range.PageTo > ic.Items.Count)
range.PageTo = ic.Items.Count;
dlg.PrintQueue.CurrentJobSettings.Description = jobName;
XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);
if (dlg.UserPageRangeEnabled == false || range.PageTo < range.PageFrom)
WriteAllItems(ic, xdw);
else
WriteSelectedItems(ic, xdw, range);
}
}
private void WriteAllItems(ItemsControl ic, XpsDocumentWriter xdw)
{
PageRange range = new PageRange(1, ic.Items.Count);
WriteSelectedItems(ic, xdw, range);
}
private void WriteSelectedItems(ItemsControl ic, XpsDocumentWriter xdw, PageRange range)
{
IItemContainerGenerator generator = ic.ItemContainerGenerator;
// write visuals using a batch operation
VisualsToXpsDocument collator = (VisualsToXpsDocument)xdw.CreateVisualsCollator();
collator.BeginBatchWrite();
if (WritePageRange(collator, generator, range))
collator.EndBatchWrite();
}
private bool WritePageRange(VisualsToXpsDocument collator, IItemContainerGenerator generator, PageRange range)
{
// Get the generator position of the first data item
// PageRange reflects user's selection starts from 1
GeneratorPosition startPos = generator.GeneratorPositionFromIndex(range.PageFrom - 1);
// If PageFrom > PageTo, print in reverse order
// UPDATE: never occurs; PrintDialog always 'normalises' the PageRange
GeneratorDirection direction = (range.PageFrom <= range.PageTo) ? GeneratorDirection.Forward : GeneratorDirection.Backward;
using (generator.StartAt(startPos, direction, true))
{
for (int numPages = Math.Abs(range.PageTo - range.PageFrom) + 1; numPages > 0; --numPages)
{
bool newlyRealized;
// Get or create the child
UIElement next = generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
generator.PrepareItemContainer(next);
}
// The collator doesn't like ContentPresenters and produces blank pages
// for pages 2-n. Finding the child of the ContentPresenter and writing
// that solves seems to solve the problem
if (next is ContentPresenter)
{
ContentPresenter presenter = (ContentPresenter)next;
presenter.UpdateLayout();
presenter.ApplyTemplate(); // not sure if this is necessary
DependencyObject child = VisualTreeHelper.GetChild(presenter, 0);
if (child is UIElement)
next = (UIElement)child;
}
try
{
collator.Write(next);
}
catch
{
// if user prints to Microsoft XPS Document Writer
// and cancels the SaveAs dialog, we get an exception.
return false;
}
}
}
return true;
}
PrintItemsTo(), displays a PrintDialog to print to any printer while WriteAllItems displays a Dialog to save as PDF.

Printed wpf visual is clipped in landscape mode under Windows 8.1

I have a simple wpf desktop application which prints a bitmap in landscape mode.
Under Windows 8/8.1 the printout is clipped on the bottom of the page while under Windows 7 it is printed correctly.
The code is really simple: load a bitmap, put it into an Image object, measure the printable area, arrange the image and print.
void printButton_Click(object sender, RoutedEventArgs e)
{
var pd = new PrintDialog();
if (!pd.ShowDialog().Value)
{
return;
}
pd.PrintTicket.PageOrientation = PageOrientation.Landscape;
pd.PrintTicket.PageBorderless = PageBorderless.None;
var printingCapabilities = pd.PrintQueue.GetPrintCapabilities(pd.PrintTicket);
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri("D:\\printTest.bmp");
bitmapImage.EndInit();
var imageuiElement = new Image { Source = bitmapImage };
var desiredSize = new Size(printingCapabilities.PageImageableArea.ExtentWidth, printingCapabilities.PageImageableArea.ExtentHeight);
imageuiElement.Measure(desiredSize);
imageuiElement.Arrange(new Rect(new Point(printingCapabilities.PageImageableArea.OriginWidth, printingCapabilities.PageImageableArea.OriginHeight), imageuiElement.DesiredSize));
pd.PrintVisual(imageuiElement, "MyImage");
}
The bitmap size is 1518 x 1092 pixels, 96 DPI, which is 40.2 x 28.9 cm.
I have found the question Cannot print a document with landscape orientation under Windows 8 (WPF, .NET 4.0)
but there is no good response for my issue (additionally I have no problem with printing as landscape itself).
I have tested it with different printers of different vendors, the printouts are clipped in all of them. A software CutePDF writer prints it to PDF correctly.
Any help appreciated.
It seems that Windows 8 does not draw ui elements outside of their container bounds. This is why the printout was clipped at the bottom.
Anyway, I ended up with a code like this, which is able to print a centered bitmap on a landscape page:
printDialog.PrintTicket.PageOrientation = PageOrientation.Landscape;
printDialog.PrintTicket.PageBorderless = PageBorderless.None;
var printingCapabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
if (printingCapabilities.PageImageableArea == null)
{
return;
}
var document = new FixedDocument();
document.DocumentPaginator.PageSize = new Size(printingCapabilities.PageImageableArea.ExtentWidth, printingCapabilities.PageImageableArea.ExtentHeight);
foreach (var imageStream in imageStreams)
{
document.Pages.Add(GeneratePageContent(imageStream, printingCapabilities, printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight));
}
try
{
printDialog.PrintDocument(document.DocumentPaginator, GlobalConstants.SoftwareName);
}
private PageContent GeneratePageContent(Stream imageStream, PrintCapabilities printingCapabilities, double paperWidth, double paperHeight)
{
imageStream.Seek(0, SeekOrigin.Begin);
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.StreamSource = imageStream;
bmp.EndInit();
var margin = new Thickness();
var pageSize = new Size();
if (printingCapabilities.PageImageableArea != null)
{
margin = new Thickness(
printingCapabilities.PageImageableArea.OriginWidth,
printingCapabilities.PageImageableArea.OriginHeight,
printingCapabilities.PageImageableArea.OriginWidth,
printingCapabilities.PageImageableArea.OriginHeight);
pageSize = new Size(printingCapabilities.PageImageableArea.ExtentWidth, printingCapabilities.PageImageableArea.ExtentHeight);
}
var imageUiElement = new Image
{
Source = bmp,
Margin = margin
};
var canvas = new Grid { Width = paperWidth, Height = paperHeight };
canvas.Children.Add(imageUiElement);
var fixedPage = new FixedPage
{
Width = paperWidth,
Height = paperHeight
};
fixedPage.Children.Add(canvas);
var pageContent = new PageContent();
((IAddChild)pageContent).AddChild(fixedPage);
pageContent.Measure(pageSize);
pageContent.Arrange(new Rect(new Point(), pageSize));
pageContent.UpdateLayout();
return pageContent;
}

printing a wpf usercontrol on custom paper size

i have a wpf usercontrol with bellow properties:
Width=170mm(642px)
Height=85mm(321px)
i want to print that on a paper with above size(Width=170mm and Height=85mm)
my problem is:when i print it,some items print out of the paper,i think the paper size if Letter by default,if it is correct,how can i change it to above Width and Height?
my code is bellow:
var p = new myUserControl();
var pDoc = new System.Windows.Controls.PrintDialog();
if (pDoc.ShowDialog().Value)
{
pDoc.PrintVisual(p, "MyPrint");
}
maybe something like this needs(this is a settings for System.Windows.Forms.PrintDialog but i use System.Windows.Controls.PrintDialog that it has no PrinterSettings property):
var printerSettings = new PrinterSettings();
var labelPaperSize = new PaperSize { RawKind = (int)PaperKind.A6, Height = 148, Width = 105 };
printerSettings.DefaultPageSettings.PaperSize = labelPaperSize;
var labelPaperSource = new PaperSource { RawKind = (int)PaperSourceKind.Manual };
printerSettings.DefaultPageSettings.PaperSource = labelPaperSource;
if (printerSettings.CanDuplex)
{
printerSettings.Duplex = Duplex.Default;
}
In WPF 1 unit = 1/96 of inch, so you can calculate your size in inches using this formula
you can set printDlg.PrintTicket.PageMediaSize to the size of the Paper and then transform your window to print in that area as below:
private void _print()
{
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
PrintTicket pt = printDlg.PrintTicket;
Double printableWidth = pt.PageMediaSize.Width.Value;
Double printableHeight = pt.PageMediaSize.Height.Value;
Double xScale = (printableWidth - xMargin * 2) / printableWidth;
Double yScale = (printableHeight - yMargin * 2) / printableHeight;
this.Transform = new MatrixTransform(xScale, 0, 0, yScale, xMargin, yMargin);
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(this, "Print Page");
}

Grid not printing content

The button is placed in a second grid. The first grid contains the content I want to print
However when I print the first grid doesn't show anything and is printing a blank document
Button and grid code:
Button Print = new Button();
Print.Content = "Print";
Print.Click += new RoutedEventHandler(OnPrintClick);
secondGrid.Children.Add(Print);
Grid.SetColumn(Print, 2);
Grid.SetRow(Print, 5);
ColumnDefinition myColumsecondGrid = new ColumnDefinition();
RowDefinition myRowsecondGrid = new RowDefinition();
myRowsecondGrid.Height = new GridLength(300);
myColumsecondGrid.Width = new GridLength(165);
secondGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = myColumsecondGrid.Width });
secondGrid.RowDefinitions.Add(new RowDefinition() { Height = myRow.Height });
Button print code:
private void OnPrintClick(object sender, RoutedEventArgs e)
{
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
if (printDlg.ShowDialog() == true)
{
//System.Printing
//get selected printer capabilities
System.Printing.PrintCapabilities capabilities = printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);
//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.
myGrid.Measure(sz);
myGrid.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.PageRangeSelection(printQty);
//now print the visual to printer to fit on the one page.
String printerName = "PDF reDirect v2";
System.Printing.PrintQueue queue = new System.Printing.LocalPrintServer().GetPrintQueue(printerName);
printDlg.PrintQueue = queue;
printDlg.PrintVisual(myGrid, "");
}
}
maybe this question will help you. You can look at the Measure and Arrange methods, probably smth in there.
Print Grid which generated dynamically in wpf
Does this code print for you anything? If it does, there's something wrong with the measurements. Also, don't forget that the grid has to be rendered at the moment you're trying to print it using PrintVisual method.
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
dialog.PrintVisual(myGrid, "CustomDescription");
}

Categories