1

I want to print an XPS document with multiple pages. Each page in the document is the same datagrid but whose ItemSource property is different every time.

I learnt that I need to disconnect the datagrid from its visual parent since otherwise, it will not let me add it as a child to the System.Windows.Documents.FixedPage object (which I use to print). I decided to clone the datagrid for each new binding (serialize and then deserialize as a new object) based on suggestions on the web.

The cloning does not work properly. Although it preserves the data, the datagrid visual markup is lost - so while printing, I only get empty pages. I tried different ways to clone and nothing helped.

Please advise.

Here is my xaml:

<Window x:Class="TestDataGridVisual.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" >
<Grid Name="TestGrid">
    <DataGrid Name="dgUsers" AutoGenerateColumns="False" CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
            <DataGridTextColumn Header="Birthday" Binding="{Binding Birthday}" />
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Details}" Margin="10" />
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>
    <Grid>
        <Button Content="Print" Height="23" HorizontalAlignment="Center"  Name="printButton" VerticalAlignment="Top" Width="75" Margin="71,282,0,0" Click="printButton_Click" />
    </Grid>      
</Grid>

Here is my code behind:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    List<List<User>> dataset = new List<List<User>>()
    {
        new List<User> () {new User() {Id = 1, Name = "John Doe", Birthday = new DateTime(1971, 7, 23)}}, //for page1
        new List<User> () {new User() { Id = 2, Name = "Jane Doe", Birthday = new DateTime(1974, 1, 17)}} //for page2
    };

    public MainWindow()
    {
        InitializeComponent();
        dgUsers.ItemsSource = dataset[0];
    }

    private void printButton_Click(object sender, RoutedEventArgs e)
    {
        PrintDialog printDialog = new PrintDialog();

        //We want to change bindings on the same element and print each in a new page
        FixedDocument fixedDocument = new FixedDocument();
        var documentPageSize = new Size(8.26 * 96, 11.69 * 96); // A4 page, at 96 dpi
        fixedDocument.DocumentPaginator.PageSize = documentPageSize;

        //Keep changing binding of ItemSource and append the resulting visual to the fixedDocument as a new page
        foreach(var data in dataset)
        {
            dgUsers.ItemsSource = data;
            dgUsers.UpdateLayout();

            //I have to clone the datagrid since otherwise it gives this message:
            //"Specified element is already the logical child of another element. Disconnect it first."
            //But for some reason, cloning the datagrid loses its visual markup although it has data
            //Hence an empty page prints - So how do I fix the cloning ?
            var pageContent = GetPageContent(CloneDataGrid(dgUsers), documentPageSize);
            fixedDocument.Pages.Add(pageContent);
        }

        printDialog.PrintDocument(fixedDocument.DocumentPaginator, "Test Document");            
    }

    private PageContent GetPageContent(Visual visual, Size pageSize)
    {
        // Create FixedPage
        var fixedPage = new FixedPage();
        fixedPage.Width = pageSize.Width;
        fixedPage.Height = pageSize.Height;

        // Add visual, measure/arrange page.
        fixedPage.Children.Add((UIElement)visual);
        fixedPage.Measure(pageSize);
        fixedPage.Arrange(new Rect(new Point(5.0, 5.0), pageSize));
        fixedPage.UpdateLayout();

        // Add page to document
        var pageContent = new PageContent();
        ((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);

        return pageContent;
    }

    private DataGrid CloneDataGrid(DataGrid inputVisual)
    {
        DataGrid clonedVisual;
        //Problem seems to be in this method
        //I try to serialize input visual as string
        //But if I inspect this string, it loses all visual markup - has only the data - why ?
        //Hence this functions ends up returning a visually empty datagrid on de-serializing
        string inputVisualAsString = System.Windows.Markup.XamlWriter.Save(inputVisual);
        if (inputVisualAsString == null) return null;

        //Write the string into a memory stream and read it into a new Visual
        using (System.IO.MemoryStream stream = new System.IO.MemoryStream(inputVisualAsString.Length))
        using (System.IO.StreamWriter sw = new System.IO.StreamWriter(stream))
        {
            sw.Write(inputVisualAsString);
            sw.Flush();
            stream.Seek(0, System.IO.SeekOrigin.Begin);

            //Load from memory stream into a new Visual - On the WPF viewer, the grid looks empty since serialization did not have all the wpf markup.
            clonedVisual = System.Windows.Markup.XamlReader.Load(stream) as DataGrid;
        }

        return clonedVisual;
    }
}
Community
  • 1
  • 1
sks
  • 11
  • 1

1 Answers1

0

I learned that there are limitations with XamlWriter.Save() method, [http://social.msdn.microsoft.com/Forums/en-US/fe627934-9e3d-49ca-bee6-5faabe7deb44/wpf-cloning-datagrid-does-not-preserve-visual-markup?forum=csharpgeneral&prof=required][1]

I did not use visual cloning and I went with the following approach

private void printButton_Click(object sender, RoutedEventArgs e)
{
    PrintDialog printDialog = new PrintDialog();

    //We want to change bindings on the same element and print each in a new page
    FixedDocument fixedDocument = new FixedDocument();
    var documentPageSize = new Size(8.26 * 96, 11.69 * 96); // A4 page, at 96 dpi
    fixedDocument.DocumentPaginator.PageSize = documentPageSize;

    //Keep changing binding of ItemSource and append the resulting visual to the fixedDocument as a new page
    foreach(var data in dataset)
    {
        MainWindow mw = new MainWindow();//I create a new object for each page and change its binding.

       mw.dgUsers.ItemsSource = data;

        mw.dgUsers.UpdateLayout();

        var pageContent = GetPageContent(CloneDataGrid(dgUsers), documentPageSize);
        fixedDocument.Pages.Add(pageContent);
    }

    printDialog.PrintDocument(fixedDocument.DocumentPaginator, "Test Document");            
}
Rakib
  • 7,435
  • 7
  • 29
  • 45
sks
  • 1