First of all, I would like to note that I raised this as an error with Microsoft, but they do not want to fix it at this point in time. What I'm looking for is a workaround or the best way to achieve what I'm trying to do, as our client considers this a pretty important issue.
Code
MainWindow.xaml
<Grid x:Name="mainGrid"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ListBox ItemsSource="{Binding Images}"> <ListBox.ItemTemplate> <DataTemplate> <Image Source="{Binding}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="Print to file" Grid.Row="1" Click="PrintToFile_Click"/> <Button Content="Print to device" Grid.Row="2" Click="PrintToDevice_Click"/> </Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window { public IList<byte[]> Images { get; set; } public MainWindow() { InitializeComponent(); Assembly currentAssembly = Assembly.GetExecutingAssembly(); this.Images = new List<byte[]> { ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Chrysanthemum.jpg")), ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Desert.jpg")), ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Hydrangeas.jpg")), }; this.DataContext = this; } public static byte[] ReadToEnd(System.IO.Stream stream) { long originalPosition = 0; if (stream.CanSeek) { originalPosition = stream.Position; stream.Position = 0; } try { byte[] readBuffer = new byte[4096]; int totalBytesRead = 0; int bytesRead; while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) { totalBytesRead += bytesRead; if (totalBytesRead == readBuffer.Length) { int nextByte = stream.ReadByte(); if (nextByte != -1) { byte[] temp = new byte[readBuffer.Length * 2]; Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); readBuffer = temp; totalBytesRead++; } } } byte[] buffer = readBuffer; if (readBuffer.Length != totalBytesRead) { buffer = new byte[totalBytesRead]; Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); } return buffer; } finally { if (stream.CanSeek) { stream.Position = originalPosition; } } } private void PrintToDevice_Click(object sender, RoutedEventArgs e) { PrintDialog dialog = new PrintDialog(); if (dialog.ShowDialog() == true) { Thickness pageMargins; if (dialog.PrintTicket.PageBorderless.HasValue == true) { if (dialog.PrintTicket.PageBorderless.Value == PageBorderless.Borderless) { pageMargins = new Thickness(0, 0, 0, 0); } else { pageMargins = new Thickness(20, 20, 20, 20); } } else { pageMargins = new Thickness(20, 20, 20, 20); } int dpiX = 300; int dpiY = 300; if (dialog.PrintTicket.PageResolution != null && dialog.PrintTicket.PageResolution.X.HasValue && dialog.PrintTicket.PageResolution.Y.HasValue) { dpiX = dialog.PrintTicket.PageResolution.X.Value; dpiY = dialog.PrintTicket.PageResolution.Y.Value; } else { dialog.PrintTicket.PageResolution = new PageResolution(dpiX, dpiY); } VisualDocumentPaginator paginator = new VisualDocumentPaginator(this.mainGrid, this.mainGrid.ActualWidth); paginator.PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight); dialog.PrintDocument(paginator, "My first print"); GC.Collect(); } } private void PrintToFile_Click(object sender, RoutedEventArgs e) { string filePath = this.PrintToFile(null, this.mainGrid, "My first print", this.mainGrid.ActualHeight, this.mainGrid.ActualWidth); Process.Start(filePath); } public string PrintToFile(Visual titleVisual, Visual contentVisual, string title, double bottomMost, double rightMost) { string printedFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), string.Format(CultureInfo.InvariantCulture, "{0}.xps", title)); XpsDocument printedDocument = new XpsDocument(printedFilePath, FileAccess.Write, System.IO.Packaging.CompressionOption.SuperFast); VisualDocumentPaginator paginator = new VisualDocumentPaginator(contentVisual as FrameworkElement, rightMost); paginator.PageSize = new Size(793.7, 1122.5); XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(printedDocument); writer.Write(paginator, new PrintTicket { Collation = Collation.Collated, CopyCount = 1, DeviceFontSubstitution = DeviceFontSubstitution.On, Duplexing = Duplexing.OneSided, InputBin = InputBin.AutoSelect, OutputColor = OutputColor.Color, OutputQuality = OutputQuality.High, PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4), PageOrientation = PageOrientation.Portrait, PageResolution = new PageResolution(PageQualitativeResolution.High), PagesPerSheet = 1, TrueTypeFontMode = TrueTypeFontMode.Automatic }); printedDocument.Close(); return printedFilePath; } }
VisualDocumentPaginator.cs
public class VisualDocumentPaginator : DocumentPaginator { #region Fields private double desiredWidth; private FrameworkElement element; #endregion #region Properties public int Columns { get { return 1;// (int)Math.Ceiling(Element.ActualWidth / PageSize.Width); } } public int Rows { get { return (int)Math.Ceiling(element.ActualHeight / PageSize.Height); } } #endregion #region Constructors public VisualDocumentPaginator(FrameworkElement element, double desiredWidth) { this.desiredWidth = desiredWidth; this.element = element; } #endregion #region DocumentPaginator Members public override DocumentPage GetPage(int pageNumber) { TransformGroup transforms = new TransformGroup(); double scaleRatio = this.PageSize.Width / this.desiredWidth; int row = (pageNumber / Columns); double pageHeight = -PageSize.Height * row / scaleRatio; double pageWidth = -PageSize.Width * (pageNumber % Columns); transforms.Children.Add(new TranslateTransform(pageWidth, pageHeight)); // Make sure the control is stretched to fit the page size. if (scaleRatio != double.NaN) { ScaleTransform st = new ScaleTransform(scaleRatio, scaleRatio); transforms.Children.Add(st); } element.RenderTransform = transforms; Size elementSize = new Size(this.desiredWidth, element.ActualHeight); element.Measure(elementSize); element.Arrange(new Rect(new Point(0, 0), elementSize)); var page = new DocumentPage(element, this.PageSize, new Rect(), new Rect()); element.RenderTransform = null; return page; } public override bool IsPageCountValid { get { return true; } } public override int PageCount { get { return Columns * Rows; } } public override Size PageSize { set; get; } public override IDocumentPaginatorSource Source { get { return null; } } #endregion }
Sorry to post all the code, but it covers all areas where I see the problem. If this helps here, a Microsoft bug report in which there is an example project in which the problem can be reproduced.
Problem
The problem occurs only when writing to the XPS file, when only the first image is printed 3 times, if the "Print to Device" button is pressed, then the correct images are printed.
The reason I attach to byte [] is because I save my images in a local SQL CE database. We store them in the database, because they are only small ~ 2 KB each plus we allow users to import their own icons into the system for use, and we need a mechanism to ensure that they are not accidentally deleted.
Note
I noticed that if I do not contact the byte [], as mentioned above, I see no problem. Given the fact that the system is currently working with an approach to storing images in the database, I would prefer to stick to it if there is a workaround, however I am not completely against replacing the storage mechanism for these images.