Печать BlockUIContainer в XpsDocument / FixedDocument

Вопрос

  1. Как распечатать FlowDocument с BlockUIContainer?
  2. Как я могу принудительно выполнить измерение / обновление / упорядочение в FlowDocument?

Фон

У меня есть сгенерированный FlowDocument с абзацами текста с несколькими Rectangle элементами, заполненными DrawingBrushes из словаря ресурсов и BlockUIContainer с настраиваемыми элементами управления.

Документ отображается правильно при просмотре в любом из элементов управления FlowDocument * ОДНАКО, когда документ преобразован в FixedDocument / XpsDocument, ни один из элементов Rectangle или BlockUIContainer не отображается.

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

  • Я прошел LogicalTree рекурсивно и сделал следующее:

    UIElement element = (UIElement)d;
    element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    element.Arrange(new Rect(element.DesiredSize));
    element.UpdateLayout();
    

    где d - это DependencyObject. Я вижу, что это устанавливает свойства ActualWidth и ActualHeight при установке точки останова в отладчике.

  • Я попытался заставить Dispatcher отображаться, как это было предложено Будет ♦.

Код, используемый для печати XpsDocument

public class XpsDocumentConverter
{

    public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
    {
        // Need to clone the document so that the paginator can work
        FlowDocument clonedDocument = DocumentHelper.Clone<FlowDocument>(document);

        Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
        MemoryStream ms = new MemoryStream();

        Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
        PackageStore.AddPackage(uri, pkg);
        XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);

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

        return new XpsDocumentReference(ms, xpsDocument);
    }

}

Как видите, я также использую пользовательский DocumentPaginator с именем «FixedDocumentPaginator»; однако я не буду публиковать этот код, поскольку сомневаюсь, что проблема существует, поскольку к тому времени, когда он начинает разбивать документ на страницы в GetPage(int pageNumber), все уже было преобразовано в Visual, и уже слишком поздно для макета.


Изменить

Хм. Когда я это набирал, мне пришла в голову мысль, что клонированный документ, возможно, не был Measure/Arrange/UpdateLayout выполнен.

Вопрос: Как я могу принудительно выполнить измерение / обновление / упорядочение в FlowDocument?

Возможный взлом, с которым я мог бы работать, - это показать клонированный документ в одном из FlowDocumentViewers (возможно, за пределами экрана).

Другое возможное решение, о котором я только что узнал и не пробовал, - это позвонить: ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();

ContextLayoutManager просматривает логическое дерево за вас и обновляет макет.

Код, использованный для клонирования документа

public static FlowDocument Clone(FlowDocument originalDocument)
{
    FlowDocument clonedDocument = new FlowDocument();
    TextRange sourceDocument = new TextRange(originalDocument.ContentStart, originalDocument.ContentEnd);
    TextRange clonedDocumentRange = new TextRange(clonedDocument.ContentStart, clonedDocument.ContentEnd);
    try
    {
        using (MemoryStream ms = new MemoryStream())
        {
            sourceDocument.Save(ms, DataFormats.XamlPackage);
            clonedDocumentRange.Load(ms, DataFormats.XamlPackage);
        }

        clonedDocument.ColumnWidth = originalDocument.ColumnWidth;
        clonedDocument.PageWidth = originalDocument.PageWidth;
        clonedDocument.PageHeight = originalDocument.PageHeight;
        clonedDocument.PagePadding = originalDocument.PagePadding;
        clonedDocument.LineStackingStrategy = clonedDocument.LineStackingStrategy;

        return clonedDocument;
    }
    catch (Exception)
    {               
    }

    return null;
} 

person Dennis    schedule 25.02.2012    source источник


Ответы (2)


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

Несколько замечаний:

  • BlockUIContainers не клонируются при использовании вышеуказанного метода. Это было не сразу очевидно, пока я не распечатал логическое дерево в окне отладки с помощью некоторых вспомогательных методов (эти методы опубликованы ниже - они невероятно полезны).
  • Вам необходимо отобразить документ в средстве просмотра и кратко показать его на экране. Ниже приведен вспомогательный метод, который я написал, чтобы сделать это за меня.

ForceRenderFlowDocument

private static string ForceRenderFlowDocumentXaml = 
@"<Window xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
          xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
       <FlowDocumentScrollViewer Name=""viewer""/>
  </Window>";

public static void ForceRenderFlowDocument(FlowDocument document)
{
    using (var reader = new XmlTextReader(new StringReader(ForceRenderFlowDocumentXaml)))
    {
        Window window = XamlReader.Load(reader) as Window;
        FlowDocumentScrollViewer viewer = LogicalTreeHelper.FindLogicalNode(window, "viewer") as FlowDocumentScrollViewer;
        viewer.Document = document;
        // Show the window way off-screen
        window.WindowStartupLocation = WindowStartupLocation.Manual;
        window.Top = Int32.MaxValue;
        window.Left = Int32.MaxValue;
        window.ShowInTaskbar = false;
        window.Show();
        // Ensure that dispatcher has done the layout and render passes
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
        viewer.Document = null;
        window.Close();
    }
}

Изменить: я просто добавил window.ShowInTaskbar = false к методу, как если бы вы быстро увидели окно, появившееся на панели задач.

Пользователь никогда не «увидит» окно, поскольку оно расположено за пределами экрана в Int32.MaxValue - уловка, которая была обычным делом в те времена при ранней разработке мультимедиа (например, Macromedia / Adobe Director).

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

Помощники по визуальному и логическому дереву

public static string WriteVisualTree(DependencyObject parent)
{
    if (parent == null)
        return "No Visual Tree Available. DependencyObject is null.";

    using (var stringWriter = new StringWriter())
    using (var indentedTextWriter = new IndentedTextWriter(stringWriter, "  "))
    {               
        WriteVisualTreeRecursive(indentedTextWriter, parent, 0);
        return stringWriter.ToString();
    }
}

private static void WriteVisualTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
    if (parent == null)
        return;

    int childCount = VisualTreeHelper.GetChildrenCount(parent);
    string typeName = parent.GetType().Name;
    string objName = parent.GetValue(FrameworkElement.NameProperty) as string;

    writer.Indent = indentLevel;
    writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel, 
                                                              String.IsNullOrEmpty(objName) ? typeName : objName, 
                                                              typeName, childCount)
                    );

    for (int childIndex = 0; childIndex < childCount; ++childIndex)
        WriteVisualTreeRecursive(writer, VisualTreeHelper.GetChild(parent, childIndex), indentLevel + 1);
}

public static string WriteLogicalTree(DependencyObject parent)
{
    if (parent == null)
        return "No Logical Tree Available. DependencyObject is null.";

    using (var stringWriter = new StringWriter())
    using (var indentedTextWriter = new IndentedTextWriter(stringWriter, "  "))
    {
        WriteLogicalTreeRecursive(indentedTextWriter, parent, 0);
        return stringWriter.ToString();
    }
}

private static void WriteLogicalTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
    if (parent == null)
        return;

    var children = LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>();
    int childCount = children.Count();

    string typeName = parent.GetType().Name;
    string objName = parent.GetValue(FrameworkElement.NameProperty) as string;

    double actualWidth = (parent.GetValue(FrameworkElement.ActualWidthProperty) as double?).GetValueOrDefault();
    double actualHeight = (parent.GetValue(FrameworkElement.ActualHeightProperty) as double?).GetValueOrDefault();

    writer.Indent = indentLevel;
    writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel,
                                                              String.IsNullOrEmpty(objName) ? typeName : objName,
                                                              typeName, 
                                                              childCount)
                    );

    foreach (object child in LogicalTreeHelper.GetChildren(parent))
    {
        if (child is DependencyObject)
            WriteLogicalTreeRecursive(writer, (DependencyObject)child, indentLevel + 1);
    }

}

Использование

#if DEBUG
    Debug.WriteLine("--- Start -------");
    Debug.WriteLine(VisualAndLogicalTreeHelper.WriteLogicalTree(document));
    Debug.WriteLine("--- End -------");
#endif
person Dennis    schedule 27.02.2012
comment
Большое спасибо за это, только что сам прошел через этот ад, вы очень помогли =) - person JMK; 30.04.2013
comment
Что происходит в XpsDocumentReference? Я все время пытаюсь печатать из PrintDialog, вытаскивая DocumentPaginator из документа XPS через GetFixedDocumentSequence().DocumentPaginator. Я получаю исключение синтаксического анализа Xaml в результате исключения InvalidURI. Видимо, фиксированный URI документа каким-то образом искажен. - person Austin Mullins; 21.08.2015
comment
@AustinMullins взгляните на мой ответ на странице stackoverflow.com / questions / 9647401 / Здесь показана реализация XpsDocumentReference. Прошло несколько лет с тех пор, как я посмотрел на XPS. Рад вернуться к нам и помочь. Это была бы хорошая возможность создать библиотеку с открытым исходным кодом. - person Dennis; 21.08.2015
comment
@AustinMullins. Прочитав этот ответ еще раз, я теперь вспоминаю боль, через которую пришлось пройти, чтобы выяснить это исключение Invalid URI. - person Dennis; 21.08.2015
comment
Привет, @Dennis, как тебе удалось клонировать FlowDocument с помощью BlockUIContainer? - person Ruslan Veselov; 02.08.2018
comment
@RuslanVeselov, честно говоря, не может вспомнить, это было почти 6 лет назад. Я посмотрю, смогу ли я найти исходный код и поищу вас. - person Dennis; 03.08.2018

Я нашел это решение здесь, и это помогло мне распечатать FlowDocment, не выводя его за пределы экрана ... Так что я надеюсь, что это поможет вам !!

String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;
person Marwan مروان    schedule 06.08.2013
comment
Я столкнулся с бесконечным циклом рекурсии при встраивании ItemsControl в BlockUIElement во время XamlWriter.Save. Я думаю, проблема в том, что какое-то свойство ItemsControl ссылается на его контейнер, а функция MarkupWriter.RecordNamespaces рекурсивно перемещается по каждому свойству элемента для сохранения. - person Austin Mullins; 21.08.2015