DocumentViewer to RichTextBox Binding Error

2019-05-21 04:11发布

问题:

I have an application with RichTextBox and DocumentViewer (placed in a TabControl), and I want to make something like "hot preview". I've binded DocumentViewer.Document property to RichTextBox.Document

Binding:

<DocumentViewer Document="{Binding Document, Converter={StaticResource FlowDocumentToPaginatorConverter}, ElementName=mainRTB, Mode=OneWay}" />

And this is Converter code:

 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            FlowDocument d = value as FlowDocument;
            DocumentPaginator pagin = ((IDocumentPaginatorSource)d).DocumentPaginator;
            FixedDocumentSequence result = null;
            Size s = new Size(793.700787402, 1122.519685039);
            pagin.PageSize = s;

            using (MemoryStream ms = new MemoryStream())
            {
                TextRange tr = new TextRange(d.ContentStart, d.ContentEnd);
                tr.Save(ms, DataFormats.XamlPackage);
                Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
                Uri uri = new Uri(@"memorystream://doc.xps");
                PackageStore.AddPackage(uri, p);
                XpsDocument xpsDoc = new XpsDocument(p);
                xpsDoc.Uri = uri;
                XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(pagin);
                result = xpsDoc.GetFixedDocumentSequence();
            }
            return result;
        }

When I start this application everything is ok until I switch to tab with DocumentViewer. Application crushes and I get such Exception:

Cannot perform a read operation in write-only mode.

What I am doing wrong? Is it possible to make this binding?

回答1:

The error message is indeed confusing and reason not immediately obvious. Basically you are closing the MemoryStream that holds XpsDocument too early and when the DocumentViewer attempts to read the document it cannot as it is write-only mode (because the stream was closed).

The solution is to not immediately close the MemoryStream until after you have finished viewing the document. To achieve this I wrote an XpsDocumentConverter that returns XpsReference.

Also, as you never been able to convert and display a single XpsDocument you won't have yet encountered the next issue of having multiple packages in the PackageStore with the same Uri. I have taken care of this in my implementation below.

public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
{            
    // Do not close the memory stream as it still being used, it will be closed 
    // later when the XpsDocumentReference is Disposed.
    MemoryStream ms = new MemoryStream();

    // We store the package in the PackageStore
    Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
    Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
    PackageStore.AddPackage(uri, pkg);
    XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);

    // Need to force render the FlowDocument before pagination. 
    // HACK: This is done by *briefly* showing the document.
    DocumentHelper.ForceRenderFlowDocument(document);

    XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
    DocumentPaginator paginator = new FixedDocumentPaginator(document, A4PageDefinition.Default);            
    rsm.SaveAsXaml(paginator);

    return new XpsDocumentReference(ms, xpsDocument);
}

public class XpsDocumentReference : IDisposable
{
    private MemoryStream MemoryStream;            
    public XpsDocument XpsDocument { get; private set; }
    public FixedDocument FixedDocument { get; private set; }

    public XpsDocumentReference(MemoryStream ms, XpsDocument xpsDocument)
    {
        MemoryStream = ms;
        XpsDocument = xpsDocument;

         DocumentReference reference = xpsDocument.GetFixedDocumentSequence().References.FirstOrDefault();
         if (reference != null)
             FixedDocument = reference.GetDocument(false);
    }

    public void Dispose()
    {
        Package pkg = PackageStore.GetPackage(XpsDocument.Uri);
        if (pkg != null)
        {
            pkg.Close();
            PackageStore.RemovePackage(XpsDocument.Uri);
        }

        if (MemoryStream != null)
        {
            MemoryStream.Dispose();
            MemoryStream = null;
        }
    }

}

XpsReference implements IDisposable so remember to call Dispose() on it.

Also, once you resolve the above error the next problem you are likely to encounter will be content not rendering as you would expect. This is caused by the fact you need to clone FlowDocument and it has not undergone a full measure and arrange layout pass. Read Printing BlockUIContainer to XpsDocument/FixedDocument on how to solve this.