My MVVM application is using on-screen visual objects to render screen content to a printed document. My View has a ContentControl
that uses DataTemplate
resources to determine what to display, but when I try to add that content to a FixedPage
object, I get an ArgumentException
with the message:
Specified Visual is already a child of another Visual or the root of a CompositionTarget.
The (simplified) XAML code for the view that handles the printing looks like this:
<DockPanel>
<!-- Print button -->
<Button DockPanel.Dock="Top"
DataContext="{Binding Path=PrintCommand}"
Click="PrintButton_Click" />
<!-- Print container. -->
<Border BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top" >
<!-- The visual content of this is what needs adding to the FixedPage. -->
<ContentControl x:Name="printContentControl"
Content="{Binding Path=PrintMe}" />
</Border>
</DockPanel>
... and the code behind for the print button click handler that has the problem looks like this:
private void PrintButton_Click(object sender, RoutedEventArgs e)
{
// Get a reference to the Visual objects to be printed.
var content = VisualTreeHelper.GetChild(printContentControl, 0);
Debug.Assert((content as UIElement) != null, "Print content is not a UIElement");
#region This doesn't work. See below for other things that also don't work.
printContentControl.Content = null;
// The line below doesn't help, but tried it in case the framework
// needed a poke to get it to update the visual tree.
UpdateLayout();
#endregion
// Make sure that all the data binding, layout, etc. has completed
// before the visual object is printed by queuing the print job at
// a later priority.
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(delegate()
{
// Create a fixed page to hold the content to print.
FixedPage page = new FixedPage();
// Add the content to print to the page.
#region Here be dragons: 'System.ArgumentException'
page.Children.Add(content as UIElement);
#endregion
// Send the document back to the View model for actual printing.
var command = ((sender as Button).DataContext as ICommand);
command.Execute(page);
}));
}
I've tried the following things to solve the problem, all with no success:
- Setting the
Content
member of theContentControl
to null (as in the code above). - Setting
this.Content=null
. Screen goes blank, but still get the exception. - Using
RemoveLogicalChild()
on various controls. None of which worked. - Using
RemoveVisualChild()
on theprintContentControl
and thecontent
object. Neither of those were children of the current object. - Using
DataTrigger
s in the XAML to replace the template when theContent
orDataContext
of theContentControl
is `{x:Null}'. No effect.
It doesn't matter if the appearance of the screen is messed up, as the ViewModel
changes the View
when its print command is executed.
How can I move the printContentControl
's visual content (or the entire control) from the View
's visual tree to the ViewModel
's FixedPage
object?
Dirty work-around
By creating a wrapper class for the content to be printed, I can make the functionality work, but it doesn't answer the original question.
Wrapper class
public class PrintContentControl : UserControl { internal UIElement GetPrintContent() { UIElement printContent = Content as UIElement; Content = null; return printContent; } }
Updated XAML using the wrapper to hold the printable content
<Border BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top" > <c:PrintContentControl x:Name="printContent"> <ContentControl ContentTemplate="{StaticResource ReportTemplate}" Content="{Binding Path=PrintMe}" /> </c:PrintContentControl> </Border>
Updated code behind to get the printable content from the wrapper
private void PrintButton_Click(object sender, RoutedEventArgs e) { // Use the wrapper to get the Visual objects to be printed. UIElement content = printContent.GetPrintContent(); Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(delegate() { // Works fine now. FixedPage page = new FixedPage(); page.Children.Add(content as UIElement); var command = ((sender as Button).DataContext as ICommand); command.Execute(page); })); }