Печать BlockUIContainer в XpsDocument / FixedDocument - PullRequest
8 голосов
/ 25 февраля 2012

Вопрос

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

Фон

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

Документ отображается правильно при просмотре в любом из элементов управления FlowDocument * HOWEVER , когда документ конвертируется в 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 сделать так, как предлагает Will ♦ .

Код, используемый для печати 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, и уже слишком поздно для макета.


Редактировать

Хм. Когда я набирал это, мне только что пришла в голову мысль, что клонированный документ 1069 *, возможно, не выполнил 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;
} 

Ответы [ 2 ]

10 голосов
/ 27 февраля 2012

Публикация этого как будущей ссылки для других, у которых есть подобные проблемы рендеринга с 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
6 голосов
/ 06 августа 2013

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

String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;
...