wpfvalidationprintingadorner

WPF validation red border adornment not shown during printing


I'm trying to print a Window containing a single TextBox. When displayed on screen the red validation error border is correctly present. When printed via PrintDialog, the border is missing. The window is defined as:

public partial class ReportPage : INotifyDataErrorInfo
{
    public ReportPage()
    {
        DataContext = this;
        SomeText = "hi there";
        InitializeComponent();
    }

    public string SomeText { get; set; }

    public IEnumerable GetErrors(string propertyName)
    {
        return propertyName == "SomeText" ? new [] {"some error"} : null;
    }

    public bool HasErrors => true;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

Note how it serves as the Window, view model, and error implementation. It specifies that the lone property, SomeText, is in error. The corresponding XAML is as follows:

<Window x:Class="ReportPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    <Grid x:Name="MainContent" VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBox x:Name="MyText" Text="{Binding SomeText}"/>
    </Grid>
</Window>  

To print the contents of the Window I use:

var printDialog = new PrintDialog { PrintTicket = { PageOrientation = PageOrientation.Portrait } };
printDialog.ShowDialog();

var caps = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
var rect = new Rect(new Point(caps.PageImageableArea.OriginWidth, caps.PageImageableArea.OriginHeight),
    new Size(caps.PageImageableArea.ExtentWidth, caps.PageImageableArea.ExtentHeight));

var pageSize = rect.Size;
var origin = rect.TopLeft;
var fullSize = new Size(printDialog.PrintTicket.PageMediaSize.Width.Value,
    printDialog.PrintTicket.PageMediaSize.Height.Value);

var page = new ReportPage
{
    Margin = new Thickness(
        Math.Max(72 - origin.X, 0),
        Math.Max(48 - origin.Y, 0),
        Math.Max(72 - (fullSize.Width - pageSize.Width - origin.X), 0),
        Math.Max(96 - (fullSize.Height - pageSize.Height - origin.Y), 0))
};

page.MainContent.Measure(pageSize);
page.MainContent.Arrange(new Rect(origin, pageSize));
page.MainContent.UpdateLayout();

printDialog.PrintVisual(page.MainContent, "some description");

I see the TextBox just fine, but no red border. I've tried adding an <AdornerDecorator> around the MainContent Grid to no avail. What happens when showing a Window on-screen to cause the red border to be visible that doesn't happen when printing?


Solution

  • First, adornments aren't rendered without showing the Window. To fix this the Window, prior to printing, is shown off-screen (Left/Top are arbitrarily set to -10000 and ShowInTaskbar/ShowActivated are set to false):

    page.Show();
    page.MainContent.Measure(pageSize);
    page.MainContent.Arrange(new Rect(origin, pageSize));
    page.MainContent.UpdateLayout();
    
    printDialog.PrintVisual(page.MainContent, "some description");
    

    Second, there's no adornment layer when printing just MainContent, so one must added (see XPS Printing of an Adorner:

    <Window x:Class="ReportPage"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
        ShowInTaskbar="False"
        Left="-10000"
        Top="-10000"
        ShowActivated="False">
        <Window.Resources>
            <Style TargetType="{x:Type ContentControl}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ContentControl}">
                            <AdornerDecorator>
                                <ContentPresenter
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}" />
                            </AdornerDecorator>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Window.Resources>    
        <ContentControl x:Name="MainContent" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBox x:Name="MyText" Text="{Binding SomeText}"/>
        </Grid>
    </Window>