在XPS文件中打印的重复图像

时间:2015-05-29 07:36:33

标签: c# wpf printing xps

首先,我想指出,我已将此作为微软的一个错误提出,但他们不愿意在此时修复它。我正在寻找的是一种解决方法或更好的方式来实现我想要做的事情,因为我们的客户认为这是一个相当重要的问题。

代码

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
}

发布所有代码的道歉,但它涵盖了我遇到问题的所有领域。如果它有帮助,Microsoft bug report附有一个示例项目,可以重现问题。

问题
仅在写入XPS文件时才会出现此问题,其中只有第一个图像被打印3次,如果&#34;打印到设备&#34;单击按钮,然后打印正确的图像。

我绑定到byte []的原因是因为我将我的图像保存在本地SQL CE数据库中。我们将它们存储在数据库中,因为它们每个只有2KB左右,我们允许用户将自己的图标导入到系统中使用,我们想要一种机制来保证它们不会被意外删除。

注意
我注意到如果我没有绑定到如上所述的byte []那么我没有看到问题。鉴于系统目前的工作方式是将数据存储在数据库中,我宁愿坚持使用它,但是我并不完全反对更换这些图像的存储机制。

2 个答案:

答案 0 :(得分:1)

我为基于WPF构建的文档管理系统构建了自定义打印解决方案。我开始使用System.Printing命名空间,但在.NET中发现了微软在很长一段时间内没有解决的无数错误。由于没有可能的解决方法,我最终使用了为Windows Forms构建的更成熟的System.Drawing.Printing命名空间,我没有发现任何问题。

您可能需要花一些时间将代码重写为System.Drawing.Printing,但在此过程中您不太可能碰到砖墙。

答案 1 :(得分:1)

我遇到了类似的问题,其中第一张图片被复制并替换了所有其他图片。在我的情况下,打印到设备,XPS文档或PDF文档并不重要,问题仍然存在。

分析

我使用.NET程序集反编译器来确定System.Windows.Xps.XpsDocumentWriter类如何处理图像,以确定问题是在我的代码中还是在框架代码中。我发现该框架使用dictionnairies将图像等资源导入文档。即使图像仅在XPS文档中导入一次,也允许文档多次引用它们。

就我而言,我能够发现该问题位于System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTable方法中。当图像从System.Windows.Media.Imaging.BitmapFrame构建时,转换器将使用密钥在其字典中查找资源。在这种情况下,密钥对应于BitmapFrame.Decoder.ToString()方法返回的字符串的哈希码。不幸的是,由于我的图像是从字节数组而不是URI构建的,因此解码器的ToString方法返回&#34;图像&#34;。由于该字符串将始终生成相同的哈希码,无论图像如何,ImageSourceTypeConverter都会认为所有图像都已添加到XPS文档中,并将返回第一个也是唯一一个要使用的图像的Uri。这解释了为什么第一张图像重复并替换所有其他图像。

尝试

我首次尝试解决此问题的方法是覆盖System.Windows.Media.Imaging.BitmapDecoder.ToString()方法。为此,我尝试将BitmapFrameBitmapDecoder包装到我自己的BitmapFrameBitmapDecoder中。不幸的是,BitmapDecoder类包含一个我无法定义的internal abstract方法。由于我无法创建自己的BitmapDecoder,因此无法实现该解决方法。

解决方法

如前所述,当System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTableBitmapSource时,BitmapFrame方法将在字典中查找哈希码。如果它不是BitmapFrame,它将根据图像二进制数据生成CRC值,并在另一个字典中查找。

就我而言,我决定将BitmapFrame的字节数组生成的System.Windows.Media.ImageSourceConverter包装到BitmapSource的另一种System.Windows.Media.Imaging.CachedBitmap中,例如CachedBitmap。由于我并不真的想要缓存位图,因此我创建了var imageSource = new CachedBitmap( bitmapFrame, BitmapCreateOptions.None, BitmapCacheOption.None ); 以下选项:

CachedBitmap

使用这些选项,BitmapSource主要是一个简单的19-Dec-16 9:59:50 AM - Searching for applicable products... 19-Dec-16 9:59:51 AM - Found installed product - Global Location 19-Dec-16 9:59:51 AM - Found installed product - ssms 19-Dec-1> 9:59:51 AM - VSIXInstaller.NoApplicableSKUsException: This extension is not installable on any currently installed products. at VSIXInstaller.App.InitializeInstall(Boolean isRepairSupported) 包装器。就我而言,这种解决方法解决了我的问题。