Laden...

Aus dynamisch geladenem Flowdocument ein XpsDocument generieren

Erstellt von IsNull vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.433 Views
I
IsNull Themenstarter:in
8 Beiträge seit 2010
vor 12 Jahren
Aus dynamisch geladenem Flowdocument ein XpsDocument generieren

Hallo,

Ich habe in Flowdocuments, die von xaml Files dynamisch geladen werden, meine Rapport-Layouts definiert.
Bei statischen Inhalten funktioniert das problemlos. Mein Problem liegt nun darin, dass ich in den Flowdocuments Databindings habe, die den Inhalt dynamisch erzeugen.

_Flowdocuments unterstützen Databinding von sich aus nicht wirklich, daher folge ich dem Weg den Vincent Van Den Berghe in einem interessanten Artikel (Create Flexible UIs with Flow Documents And Data Binding) beschrieben hat, und ich habe das ganze so adaptiert. _

Ein Beispiel eines solchen xamls:


    <FlowDocument 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:flowdoc="clr-namespace:Archimedes.Patterns.WPF.FlowDocuments;assembly=Archimedes.Patterns.WPF"
              PageHeight="29.7cm" PageWidth="21cm" ColumnWidth="21cm">

        <Paragraph>
            <Bold>Mein schöner Rapport</Bold>
        </Paragraph>


        <flowdoc:ItemsContent ItemsSource="{Binding AllCustomers}" >
            <flowdoc:ItemsContent.ItemsPanel>
                <DataTemplate>
                    <flowdoc:Fragment>
                        <Table BorderThickness="1" BorderBrush="Black">
                            <TableRowGroup flowdoc:Attached.IsItemsHost="True">
                                <TableRow Background="LightBlue">
                                    <TableCell>
                                        <Paragraph>Customer</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Last visit</Paragraph>
                                    </TableCell>
                                </TableRow>
                            </TableRowGroup>
                        </Table>
                    </flowdoc:Fragment>
                </DataTemplate>

            </flowdoc:ItemsContent.ItemsPanel>
            <flowdoc:ItemsContent.ItemTemplate>
                <DataTemplate>
                    <flowdoc:Fragment>
                        <TableRow>
                            <TableCell>
                                <Paragraph>
                                    <flowdoc:BindableRun BoundText="{Binding MatchCode}" />
                                </Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>
                                    <flowdoc:BindableRun BoundText="{Binding LastSalesmanVisit}"/>
                                </Paragraph>
                            </TableCell>
                        </TableRow>
                    </flowdoc:Fragment>
                </DataTemplate>
            </flowdoc:ItemsContent.ItemTemplate>
        </flowdoc:ItemsContent>

    </FlowDocument>

Zu beachten ist, das die interessante Klasse flowdoc:ItemsContent 1:1 der aus dem oben verlinkten Artikel entspricht.

Das Problem an der Sache ist nun folgendes:
Um die dynamischen Inhalte zu erzeugen muss das Flowdocument nun irgendwie geladen und als Visual gerendert werden. Will ich es dem User als Vorschau darstellen funktioniert das also problemlos. (Dynamisch laden, DataContext setzten, als Child eines Window/Controls ) setzten und das ganze funktioniert.

Was ich aber möchte ist, aus dem FlowDocument z.B. ein XpsDocument generieren, welches ich dann wahlweise dem Nutzer anzeigen aber eben auch direkt drucken kann.

Ich habe folgenden Code geschrieben (mehr zusammengeklaut), um aus den FlowDocument ein XpsDocument zu erzeugen:


        /// <summary>
        /// Helper method to create page header or footer from flow document template
        /// </summary>
        /// <param name="data">report data</param>
        /// <returns></returns>
        public static XpsDocument CreateXpsDocument(FlowDocument document) {
            MemoryStream ms = new MemoryStream();
            Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
            string pack = "pack://report.xps";
            PackageStore.RemovePackage(new Uri(pack));
            PackageStore.AddPackage(new Uri(pack), pkg);
            XpsDocument xpsdoc = new XpsDocument(pkg, CompressionOption.NotCompressed, pack);
            XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsdoc), false);

            DocumentPaginator dp = 
                ((IDocumentPaginatorSource)document).DocumentPaginator;
            rsm.SaveAsXaml(dp);
            return xpsdoc;
        }

Das liefert mir aber bei den dynamischen Inhalten nichts zurück, weil das Flowdocument nie als VisualChild eines Controls definiert wird, und somit auch nie das Databinding zum Zuge kommt.

Jemand eine Idee wie man das elegant löst, oder einen einfacheren Weg um Flowdocuments mit Databinding dynamisch zu laden um daraus ein Rapport-Dokument zu erstellen?

Grüsse
IsNull

_
Cross-Post Hinweis:
Ich habe im Vorfeld eine sehr ähnliche (die Frage konnte ich nun weiter konkretisieren) Anfrage hier gestellt aber noch keine Antwort erhalten._

I
IsNull Themenstarter:in
8 Beiträge seit 2010
vor 12 Jahren

Ich habe nun als Notlösung das dynamisch geladene Flowdocument einem Fenster als Child zugewiesen. Sobald das geschehen ist, ziehen die Databindings und das Flowdocument hat seinen Inhalt.

So klappt es mit dem Databinding, nur ist das natürlich relativ umständlich, wenn man ohne Vorschau drucken will, oder mehrere Flowdocuments mit einem Paginator weiter bearbeiten will.

Ich bin in meinen Recherchen über einige Blogs/Beiträge gestolpert, die genau das Problem auch angehen, aber es scheint nicht wirklich eine zuverlässige Methode zu geben.


            private static void FlushDispatcher(Dispatcher ctx, DispatcherPriority priority) {
                ctx.Invoke(priority, new DispatcherOperationCallback(delegate { return null; }), null);
            }

Dieser Hack wird mit dem CurrentDispatcher aufgerufen, und lässt gewisse Databindings ausführen - in meinem konkreten Fall hat das aber auch nichts gebracht, da das Element weiterhin nicht den "IsLoaded" Status bekommt.
Quelle

Falls jemand meinen Monolog hier etwas bereichern will - nur zu 😉