1

I have chunks of XAML displayed on my screen that I make printable with a print button inside that chunk, something like this:

<Border DockPanel.Dock="Top" x:Name="PrintableArea">
    <StackPanel 
            HorizontalAlignment="Right"
            VerticalAlignment="Bottom">
        <ContentControl Background="Green" x:Name="ManageButtonsContainer"/>
        <Button x:Name="PrintButton" Content="Print" Click="Button_Click_Print"/>
    </StackPanel>
</Border>  

But when this chunk prints out, I don't want the print button to be printed, so I hide it before I print and make it visible again after I print, like this:

private void Button_Click_Print(object sender, RoutedEventArgs e)
{
    PrintButton.Visibility = Visibility.Collapsed;
    PrintDialog dialog = new PrintDialog();
    if (dialog.ShowDialog() == true)
    { dialog.PrintVisual(PrintableArea, "Print job"); }

    PrintButton.Visibility = Visibility.Visible;
}

This works, but when the print dialog appears, you see behind the print dialog that the print button disappears and then reappears again, which is just a little unconventional UI behavior that I would like to avoid.

Is there a way to keep elements visible on the screen yet hide them from printing?

e.g. something like this (pseudo-code):

<Button x:Name="PrintButton" Content="Print" 
   HideWhenPrinting=True"
   Click="Button_Click_Print"/>

Pragmatic answer:

Ok, I solved this particular issue simply by changing the visibility only if they actually print, but it would still be nice to know in principle if there is a way to set "printable visibility" in XAML so this issue doesn't always have to be taken care of in code like this:

private void Button_Click_Print(object sender, RoutedEventArgs e)
{
    PrintDialog dialog = new PrintDialog();
    if (dialog.ShowDialog() == true)
    {
        PrintButton.Visibility = Visibility.Collapsed;
        dialog.PrintVisual(PrintableArea, "Print job");
        PrintButton.Visibility = Visibility.Visible;
    }
}
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047

2 Answers2

2

I couldn't find any easy answer for your question, so I decided to scary everybody who reads this with the huge code below. It creates attached property, called PrintExtension.IsPrintable, and every time you set it to true on an item, it starts "tracking" that item. Before printing one should call PrintExtension.OnBeforePrinting(), and when you are done call PrintExtension.OnAfterPrinting(). It does exactly the same thing you have in your code, but more effortless.

  /// <summary>
  /// Hides PrintExtensions.IsPrintable="False" elements before printing,
  /// and get them back after. Not a production quality code.
  /// </summary>
  public static class PrintExtensions
  {
    private static readonly List<WeakReference> _trackedItems = new List<WeakReference>();

    public static bool GetIsPrintable(DependencyObject obj)
    {
      return (bool)obj.GetValue(IsPrintableProperty);
    }

    public static void SetIsPrintable(DependencyObject obj, bool value)
    {
      obj.SetValue(IsPrintableProperty, value);
    }

    public static readonly DependencyProperty IsPrintableProperty =
        DependencyProperty.RegisterAttached("IsPrintable",
        typeof(bool),
        typeof(PrintExtensions),
        new PropertyMetadata(true, OnIsPrintableChanged));

    private static void OnIsPrintableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var printable = (bool)e.NewValue;
      bool isTracked = IsTracked(d);
      if (printable && !isTracked)
      {
        StartTracking(d);
      }
      else if (!printable && isTracked)
      {
        StopTracking(d);
      }
    }

    /// <summary>
    /// Call this method before printing.
    /// </summary>
    public static void OnBeforePrinting()
    {
      IterateTrackedItems(
        item =>
        {
          var fe = item.Target as FrameworkElement;
          if (fe != null)
          {
            fe.Visibility = Visibility.Collapsed; // Boom, we break bindings here, if there are any.
          }
        });
    }

    /// <summary>
    /// Call this after printing.
    /// </summary>
    public static void OnAfterPrinting()
    {
      IterateTrackedItems(
        item =>
        {
          var fe = item.Target as FrameworkElement;
          if (fe != null)
          {
            fe.Visibility = Visibility.Visible; // Boom, binding is broken again here.
          }
        });
    }

    private static void StopTracking(DependencyObject o)
    {
      // This is O(n) operation.
      var reference = _trackedItems.Find(wr => wr.IsAlive && wr.Target == o);
      if (reference != null)
      {
        _trackedItems.Remove(reference);
      }
    }

    private static void StartTracking(DependencyObject o)
    {
      _trackedItems.Add(new WeakReference(o));
    }

    private static bool IsTracked(DependencyObject o)
    {
      // Be careful, this function is of O(n) complexity.
      var tracked = false;

      IterateTrackedItems(
        item =>
        {
          if (item.Target == o)
          {
            tracked = true;
          }
        });

      return tracked;
    }

    /// <summary>
    /// Iterates over tracked items collection, and perform eachAction on
    /// alive items. Don't want to create iterator, because we do house
    /// keeping stuff here. Let it be more prominent.
    /// </summary>
    private static void IterateTrackedItems(Action<WeakReference> eachAction)
    {
      var trackedItems = new WeakReference[_trackedItems.Count];
      _trackedItems.CopyTo(trackedItems);
      foreach (var item in trackedItems)
      {
        if (!item.IsAlive) // do some house keeping work.
        {
          _trackedItems.Remove(item); // Don't care about GC'ed objects.
        }
        else
        {
          eachAction(item);
        }
      }
    }
  }

NB: I haven't tested this code. Be careful with it. As you can see, it's far from being perfect, and I really hope there is simpler solution.

Cheers, Anvaka.

Anvaka
  • 15,658
  • 2
  • 47
  • 56
0

This question is closely related to the one you'd asked an hour earlier (some six years ago, I realize…but I find the answers so far to both unsatisfactory, having recently stumbled across these questions myself, hence these answers). That is, a big part of the problem in both is that you are attempting to use the objects you're displaying on the screen for the purpose of printing, when in fact you should be taking advantage of WPF's data templating features to address your concerns.

In this particular example, you could approach the problem in a few different ways:

  1. Declare a single DataTemplate for on-screen and printing purposes. Including in the view model a flag indicating whether the object is being printed or not. Make a copy of the view model when printing, except set the "is printing" flag to true. In the template, bind the visibility of the Button to this flag (i.e. use a converter to set Visibility="Collapsed" if the flag is true, or define a Trigger that will do the same thing).
  2. Do the above, but instead of including a flag in the view model, when you are printing the data, just explicitly search the visual tree for the Button after the ControlControl has loaded its templated content and collapse the Button before you print the control.
  3. Declare a separate template specifically for the purpose of printing the view model data, and just leave the Button out in that template. This would give you the most control over printing-specific behaviors and appearances, but at the added cost of having to maintain two different-but-related templates.

In all three options, as well as other variations on that theme, the key is that you would use the data templating features to cause WPF to populate a new visual tree to go along with the view model object you're dealing with. In this way, you avoid unwanted interactions between the needs of the printing code and what's happening on the screen (something that is not true for the other answer posted here).

That said, all of these three options have their drawbacks. Copying a view model (per option #1) is fine if it's simple, but it could get unwieldy for more complex data structures. Digging into the generated content (option #2) has obvious negative ramifications, and of course maintaining two different templates (option #3) is just a pain (which in some, but not all cases, could be mitigated by incorporating the "print" template inside the "screen" template via a ContentControl, depending on how important the ordering of the controls is).


Having spent more time dealing with this question in my own code, I've come to the conclusion that, while a bit on the "hacky" side, the solution that works best for me is to set a Trigger that is based on searching for an ancestor element that would be present on the screen, but not when the data template is loaded in a ContentControl. E.g., in the DataTemplate, something like this:

<Button Content="Print" Click="Button_Click_Print">
  <Button.Style>
    <p:Style TargetType="Button">
      <p:Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}}" Value="{x:Null}">
          <Setter Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
      </p:Style.Triggers>
    </p:Style>
  </Button.Style>
</Button>

I wouldn't say it's 100% kosher for the template to be so self-aware. I prefer they be more agnostic about the context in which they're being used. But you have a fairly unique situation here, one in which being self-aware is going to need to happen one way or the other. I have found this approach to be the least of all possible evils. :)

(Sorry about the p:Style stuff…it's just a XAML-compliant way to work-around the bug in the XML-formatting code Stack Overflow uses, so that code coloring continues to work inside the Style element. You can leave the p: namespace qualifier out in your own XAML if you like, or just go ahead and declare the p: XML namespace appropriately.)

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136