views:

66

answers:

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;
    }
}
+2  A: 

Hi Edward,

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