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