2015-05-29 14 views
6

Zunächst möchte ich darauf hinweisen, dass ich dies als einen Fehler mit Microsoft angesprochen habe, aber sie sind nicht bereit, es zu diesem Zeitpunkt zu beheben. Was ich suche, ist ein Workaround oder ein besserer Weg, um das zu erreichen, was ich versuche zu tun, da unser Kunde dies als ein ziemlich wichtiges Thema betrachtet.Doppelte Bilder in XPS-Datei gedruckt

Der 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 
} 

Entschuldigung für die Veröffentlichung des gesamten Codes, aber es deckt alle Bereiche ab, in denen ich das Problem sehe. Wenn es hier hilft, ist die Microsoft bug report, die ein Beispielprojekt beigefügt hat, wo das Problem reproduziert werden kann.

Das Problem
Das Problem tritt nur beim Schreiben in eine XPS-Datei zu sehen ist, wo nur das erste Bild 3 mal gedruckt wird, wenn die „Print to device“ Taste werden dann angeklickt wird die korrekten Bilder gedruckt.

Der Grund, warum ich an ein Byte [] bin, ist, weil ich meine Bilder in einer lokalen SQL CE-Datenbank behalte. Wir speichern sie in einer DB, da sie nur ~ 2KB klein sind. Außerdem erlauben wir Benutzern, ihre eigenen Icons in das System zu importieren und wir wollten einen Mechanismus, der garantiert, dass sie nicht versehentlich gelöscht werden.

HINWEIS
Ich habe bemerkt, dass, wenn ich auf das Byte nicht binden [], wie oben erwähnt, dann ich das Problem nicht sehen. Angesichts der Tatsache, dass das System derzeit von dem Ansatz arbeitet, die Bilder in einer Datenbank zu speichern, würde ich es vorziehen, bei einer Problemumgehung zu bleiben, aber ich bin nicht völlig dagegen, den Speichermechanismus für diese Bilder zu ersetzen.

Antwort

1

Ich habe eine benutzerdefinierte Drucklösung für ein Dokumentenmanagementsystem auf WPF erstellt. Ich fing an, den System.Printing Namespace zu benutzen, aber fand endlose Programmfehler in .NET, die Microsoft seit langem nicht aufgelöst hatte. Ohne mögliche Problemumgehungen habe ich den ausgereifteren Namespace System.Drawing.Printing für Windows Forms verwendet, mit dem ich keine Probleme festgestellt habe.

Es würde wahrscheinlich einige Zeit dauern, um Ihren Code in System.Drawing.Printing umzuschreiben, aber Sie sind weit weniger wahrscheinlich, irgendwo auf dem Weg eine Mauer zu treffen.

+0

danke dafür. Ich hatte gehofft, eine ziemlich schnelle Lösung für das Problem zu finden, aber der Kunde hat nun entschieden, dass er die XPS-Unterstützung fallen lassen und zum PDF wechseln möchte. Ich denke, wir werden die Drucklogik, die wir verwenden, neu schreiben. Ich werde definitiv in System.Drawing.Printing nachsehen. – Bijington

1

Ich habe ein ähnliches Problem erlebt, bei dem das erste Bild dupliziert und alle anderen Bilder ersetzt wurde. In meinem Fall spielte das Drucken auf einem Gerät, einem XPS-Dokument oder PDF-Dokument keine Rolle, das Problem war immer noch da.

Analyse

ich eine .NET-Assembly Decompiler verwendet haben, um herauszufinden, wie die System.Windows.Xps.XpsDocumentWriter Klasse beschäftigt sich mit Bildern, wenn das Problem in meinem Code war, um herauszufinden, oder in den Code des Rahmens. Ich entdeckte, dass das Framework Dictionnairies verwendet, um Ressourcen wie Bilder in das Dokument zu importieren.Auch wenn Bilder nur einmal in das XPS-Dokument importiert werden, darf das Dokument sie mehrmals referenzieren. In meinem Fall konnte ich herausfinden, dass das Problem in der System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTable-Methode gefunden wurde. Wenn das Bild aus einem System.Windows.Media.Imaging.BitmapFrame erstellt wird, sucht der Konverter nach der Ressource in einem seiner Wörterbücher mithilfe eines Schlüssels. In diesem Fall entspricht der Schlüssel dem Hash-Code der Zeichenfolge, die von der BitmapFrame.Decoder.ToString()-Methode zurückgegeben wird. Da meine Bilder aus Byte-Arrays anstelle eines URIs erstellt werden, gibt die Methode ToString des Decoders leider "image" zurück. Da diese Zeichenfolge unabhängig vom Bild immer den gleichen Hash-Code generiert, berücksichtigt die ImageSourceTypeConverter, dass alle Bilder bereits zum XPS-Dokument hinzugefügt wurden und die Uri des ersten und einzigen zu verwendenden Bilds zurückgibt. Dies erklärt, warum das erste Bild dupliziert wird und alle anderen Bilder ersetzt.

Versuch

Mein erster Versuch, das Problem zu umgehen war die System.Windows.Media.Imaging.BitmapDecoder.ToString() Methode außer Kraft zu setzen. Um dies zu tun, habe ich versucht, die BitmapFrame und BitmapDecoder in meine eigenen BitmapFrame und BitmapDecoder zu wickeln. Leider enthält die BitmapDecoder Klasse eine internal abstract Methode, die ich nicht definieren kann. Da ich meine eigene BitmapDecoder nicht erstellen konnte, konnte ich diese Problemumgehung nicht implementieren.

Umgehung

Wie bereits erwähnt, die System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTable Verfahren für einen Hash-Code in einem Wörterbuch aussehen wird, wenn die BitmapSource ein BitmapFrame ist. Wenn es kein BitmapFrame ist, wird stattdessen ein CRC-Wert basierend auf den Binärdaten des Bildes generiert und in einem anderen Wörterbuch gesucht.

In meinem Fall habe ich beschlossen, die BitmapFrame zu wickeln, die von Byte-Arrays durch die System.Windows.Media.ImageSourceConverter in eine andere Art von BitmapSource wie System.Windows.Media.Imaging.CachedBitmap generiert wurden. Da ich nicht wirklich im Cache gespeicherten Bitmap wollte habe, habe ich die CachedBitmap werden die folgenden Optionen:

var imageSource = new CachedBitmap(bitmapFrame, BitmapCreateOptions.None, BitmapCacheOption.None); 

Mit diesen Optionen die CachedBitmap ist meist eine einfache BitmapSource-Wrapper. In meinem Fall hat diese Problemumgehung mein Problem gelöst.