Ошибка привязки DocumentViewer к RichTextBox

У меня есть приложение с RichTextBox и DocumentViewer (помещенное в TabControl), и я хочу сделать что-то вроде «горячего предварительного просмотра». Я привязал свойство DocumentViewer.Document к RichTextBox.Document

Привязка:

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

А это код преобразователя:

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

Когда я запускаю это приложение, все в порядке, пока я не переключаюсь на вкладку с DocumentViewer. Приложение падает, и я получаю такое исключение:

Не удается выполнить операцию чтения в режиме только записи.

Что я делаю неправильно? Можно ли сделать эту привязку?


person Artur Michajluk    schedule 10.03.2012    source источник


Ответы (1)


Сообщение об ошибке действительно сбивает с толку, и причина не сразу очевидна. По сути, вы слишком рано закрываете MemoryStream, который содержит XpsDocument, и когда DocumentViewer пытается прочитать документ, он не может, поскольку это режим только для записи (потому что поток был закрыт).

Решение состоит в том, чтобы не закрывать MemoryStream сразу, пока после просмотра документа не будет завершено. Для этого я написал XpsDocumentConverter, который возвращает XpsReference.

Кроме того, поскольку вы никогда не могли преобразовать и отобразить один XpsDocument, вы еще не столкнулись со следующей проблемой наличия нескольких пакетов в PackageStore с одним и тем же Uri. Я позаботился об этом в своей реализации ниже.

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 реализует IDisposable, поэтому не забудьте вызвать на нем Dispose().

Кроме того, как только вы устраните вышеуказанную ошибку, следующая проблема, с которой вы, вероятно, столкнетесь, будет заключаться в том, что контент не будет отображаться так, как вы ожидаете. Это связано с тем, что вам нужно клонировать FlowDocument и он не прошел полную проверку и не организовал проход макета. Прочитайте Печать BlockUIContainer в XpsDocument/FixedDocument, чтобы узнать, как решить эту проблему.

person Dennis    schedule 11.03.2012
comment
Если вы ищете магию, стоящую за ForceRenderFlowDocument, она доступна в этом ответе StackOverflow. stackoverflow.com/questions/9447338/ - person Dennis; 14.03.2012