有没有办法将XAML元素定义为不可打印?

时间:2009-12-28 08:02:21

标签: c# wpf xaml printing

我的屏幕上显示了大量的XAML,我在可打印中打印了一个打印按钮,如下所示:

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

但是当这个块打印出来时,我不希望打印打印按钮,所以在打印之前我隐藏它并在我之后再次显示它打印,像这样:

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

这样可行,但是当出现打印对话框时,您会看到后面打印对话框,打印按钮消失然后再次出现,这只是我想要避免的一些非传统的UI行为

有没有办法让屏幕上的元素保持可见但却无法打印?

e.g。像这样的东西(伪代码):

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

务实的回答:

好的,我只是通过改变可见性来解决这个特殊问题,只有当它们实际打印时,但原则上知道是否有办法在XAML中设置“可打印的可见性”仍然很好,所以这个问题不会总是必须在这样的代码中处理:

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 个答案:

答案 0 :(得分:2)

我找不到任何简单的答案,所以我决定吓唬所有读下这个代码的人。它创建附加属性,称为PrintExtension.IsPrintable,每次在项目上将其设置为true时,它都会开始“跟踪”该项目。在打印之前,应拨打PrintExtension.OnBeforePrinting(),并在完成后拨打PrintExtension.OnAfterPrinting()。它与您的代码完全相同,但更轻松。

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

注意:我还没有测试过这段代码。小心一点。正如你所看到的,它远非完美,我真的希望有更简单的解决方案。

干杯,安瓦卡。

答案 1 :(得分:0)

这个问题与the one you'd asked an hour earlier密切相关(大约六年前,我意识到......但到目前为止,我发现答案都不令人满意,最近我自己偶然发现了这些问题,因此这些答案都是如此)。也就是说,两者中的很大一部分问题是你试图使用你在屏幕上显示的对象来进行打印,而实际上你应该利用WPF的数据模板功能来解决你的问题

在这个特定的例子中,您可以通过几种不同的方式解决问题:

  1. 声明单个DataTemplate用于屏幕和打印目的。在视图模型中包括指示是否正在打印对象的标志。打印时制作视图模型的副本,但将“正在打印”标志设置为true。在模板中,将Button的可见性绑定到此标记(例如,如果标记为Visibility="Collapsed",则使用转换器设置true,或定义将执行的Trigger同样的事情)。
  2. 执行上述操作,但在打印数据时,不是在视图模型中包含标记,而是在Button加载其模板化内容后,在ControlControl显式搜索Button并在打印控件之前折叠Button
  3. 专门为打印视图模型数据而声明一个单独的模板,并将ContentControl保留在该模板中。这将使您能够最大程度地控制特定于打印的行为和外观,但需要维护两个不同但相关的模板的额外成本。
  4. 在所有三个选项中,以及该主题的其他变体,关键是您将使用数据模板功能使WPF填充新的可视树以与您正在处理的视图模型对象一起使用。通过这种方式,您可以避免打印代码的需求与屏幕上发生的事情之间发生不必要的交互(对于此处发布的其他答案,情况并非如此)。

    尽管如此,所有这三种选择都有其缺点。复制视图模型(每个选项#1)很好,如果它很简单,但对于更复杂的数据结构可能会变得难以处理。挖掘生成的内容(选项#2)有明显的负面影响,当然维护两个不同的模板(选项#3)只是一种痛苦(在某些情况下,但并非所有情况下,都可以通过合并“打印”来减轻“屏幕”模板中的模板通过Trigger,取决于控件的排序有多重要。)


    花了更多的时间在我自己的代码中处理这个问题,我得出结论,虽然有点“hacky”方面,最适合我的解决方案是设置ContentControl即基于搜索将出现在屏幕上的祖先元素,但不是在DataTemplate中加载数据模板时。例如,<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> 中的内容如下:

    p:Style

    我不会说这是100%犹太人让模板如此自我意识。我更喜欢他们对使用它们的环境更加不了解。但是你在这里有一个相当独特的情况,其中一个自我意识需要以某种方式发生。我发现这种方法是所有可能的邪恶中最不重要的。 :)

    (抱歉Style这些东西......它只是一种符合XAML标准的方法来解决Stack Overflow使用的XML格式代码中的错误,以便代码着色继续在{{1}内部工作如果您愿意,可以将p:命名空间限定符保留在您自己的XAML中,或者只是继续并适当地声明p: XML命名空间。)