如何在WPF中打印用户控件的集合?

时间:2014-02-22 07:05:19

标签: wpf printing .net-4.5

说我有以下小UserControl

<StackPanel>
   <TextBlock Text="{Binding Title, StringFormat=Name: {0}}" FontWeight="Bold"/>
   <Separator/>
   <ItemsControl ItemsSource="{Binding Items}">
      <ItemsControl.ItemTemplate>
         <DataTemplate>
            <TextBlock Text="{Binding Path=. ,StringFormat=Detail: {0}}"/>
         </DataTemplate>
      </ItemsControl.ItemTemplate>
  </ItemsControl>
  <Line Stroke="Black" HorizontalAlignment="Stretch" Margin="0,10"
                X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
                StrokeDashArray="2 2" StrokeThickness="1" />
</StackPanel>

我的应用程序将生成这些控件的集合,然后应使用自动分页打印它们。 ItemsControl中显示的项目数是可变的。我抨击网络,阅读关于在Pro WPF 4.5 Unleashed中打印的章节,但我仍然看不到如何实现这一点。

非常欢迎任何指导。

编辑:欢迎任何指导。由于我在视图模型中可以获得数据,因此将数据重定向到其他地方并不困难。但在哪里?

2 个答案:

答案 0 :(得分:3)

由于您需要自动分页,因此您需要创建一个DocumentPaginator对象。

以下内容从this example被盗并根据您的情况进行了修改:

/// <summary>
/// Document paginator.
/// </summary>
public class UserControlDocumentPaginator : DocumentPaginator
{
    private readonly UserControlItem[] userControlItems;
    private Size pageSize;

    private int pageCount;

    private int maxRowsPerPage;
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="userControlItems">The list of userControl items to display.</param>
    /// <param name="pageSize">The size of the page in pixels.</param>
    public UserControlDocumentPaginator
    (
        UserControlItem[] userControlItems,
        Size pageSize
    )
    {
        this.userControlItems = userControlItems;
        this.pageSize = pageSize;
        PaginateUserControlItems();
    }

    /// <summary>
    /// Computes the page count based on the number of userControl items
    /// and the page size.
    /// </summary>
    private void PaginateUserControlItems()
    {
        double actualHeight;
        foreach (var uc in userControlItems)
        {
            actualHeight += uc.ActualHeight;
        }
        pageCount = (int)Math.Ceiling(actualHeight / pageSize.Height);
    }

    /// <summary>
    /// Gets a range of userControl items from an array.
    /// </summary>
    /// <param name="array">The userControl items array.</param>
    /// <param name="start">Start index.</param>
    /// <param name="end">End index.</param>
    /// <returns></returns>
    private static UserControlItem[] GetRange(UserControlItem[] array, int start, int end)
    {
        List<UserControlItem> userControlItems = new List<UserControlItem>();
        for (int i = start; i < end; i++)
        {
            if (i >= array.Count())
            {
                break;
            }
            userControlItems.Add(array[i]);
        }
        return userControlItems.ToArray();
    }

    #region DocumentPaginator Members

    /// <summary>
    /// When overridden in a derived class, gets the DocumentPage for the
    /// specified page number.
    /// </summary>
    /// <param name="pageNumber">
    /// The zero-based page number of the document page that is needed.
    /// </param>
    /// <returns>
    /// The DocumentPage for the specified pageNumber, or DocumentPage.Missing
    /// if the page does not exist.
    /// </returns>
    public override DocumentPage GetPage(int pageNumber)
    {
        // Compute the range of userControl items to display
        int start = pageNumber * maxRowsPerPage;
        int end = start + maxRowsPerPage;

        UserControlListPage page = new UserControlListPage(GetRange(userControlItems, start, end), pageSize);
        page.Measure(pageSize);
        page.Arrange(new Rect(pageSize));

        return new DocumentPage(page);
    }
    /// <summary>
    /// When overridden in a derived class, gets a value indicating whether
    /// PageCount is the total number of pages.
    /// </summary>
    public override bool IsPageCountValid
    {
        get { return true; }
    }

    /// <summary>
    /// When overridden in a derived class, gets a count of the number of
    /// pages currently formatted.
    /// </summary>
    public override int PageCount
    {
        get { return pageCount; }
    }

    /// <summary>
    /// When overridden in a derived class, gets or sets the suggested width
    /// and height of each page.
    /// </summary>
    public override System.Windows.Size PageSize
    {
        get
        {
            return pageSize;
        }
        set
        {
            if (pageSize.Equals(value) != true)
            {
                pageSize = value;
                PaginateUserControlItems();
            }
        }
    }

    /// <summary>
    /// When overridden in a derived class, returns the element being paginated.
    /// </summary>
    public override IDocumentPaginatorSource Source
    {
        get { return null; }
    }

    #endregion

}

然后在您Window的代码后面,获取您的用户控件列表(将YourUserControlContainer替换为Window中容器的名称)。创建一个名为PrintButton的按钮,并将Click事件附加到下面的PrintButton_Click方法。在适当的地方放置代码:

List<UserControl> userControlItems = new List<UserControl>();

// 8.5 x 11 paper
Size pageSize = new Size(816, 1056);

private void PrintButton_Click(object sender, RoutedEventArgs e)
{
    userControlItems = YourUserControlContainer.Children.ToList();
    UserControlDocumentPaginator paginator = new UserControlDocumentPaginator
    (
        userControlItems.ToArray(),
        pageSize
    );
    var dialog = new PrintDialog();
    if (dialog.ShowDialog() != true) return;
    dialog.PrintDocument(paginator, "Custom Paginator Print Job");
}

编辑你是对的,我忘了上课。你需要这样的东西:

public partial class UserControlListPage : UserControl
{
    private readonly UserControlItem[] userControlItems;

    private readonly Size pageSize;

    public UserControlListPage
    (
        UserControlItem[] userControlItems,
        Size pageSize
    )
    {
        InitializeComponent();
        this.userControlItems = userControlItems;
        this.pageSize = pageSize;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        Point point = new Point(0, 0);
        foreach (UserControlItem item in userControlItems)
        {
            point.X = 0;
            itemImageSource = CopyControlToImageSource(item);
            drawingContext.DrawImage(itemImageSource, point);
            point.Y += itemImageSource.Height;
        }
    }
}

然后在某处把这个:

/// <summary>
/// Gets an image "screenshot" of the specified UIElement
/// </summary>
/// <param name="source">UIElement to screenshot</param>
/// <param name="scale" value="1">Scale to render the screenshot</param>
/// <returns>Byte array of BMP data</returns>
private static byte[] GetUIElementSnapshot(UIElement source, double scale = 1)
{
    double actualHeight = source.RenderSize.Height;
    double actualWidth = source.RenderSize.Width;

    double renderHeight = actualHeight * scale;
    double renderWidth = actualWidth * scale;

    RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
    VisualBrush sourceBrush = new VisualBrush(source);

    DrawingVisual drawingVisual = new DrawingVisual();
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    using (drawingContext)
    {
        drawingContext.PushTransform(new ScaleTransform(scale, scale));
        drawingContext.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new System.Windows.Point(actualWidth, actualHeight)));
    }
    renderTarget.Render(drawingVisual);

    Byte[] _imageArray = null;

    BmpBitmapEncoder bmpEncoder = new BmpBitmapEncoder();
    bmpEncoder.Frames.Add(BitmapFrame.Create(renderTarget));

    using (MemoryStream outputStream = new MemoryStream())
    {
        bmpEncoder.Save(outputStream);
        _imageArray = outputStream.ToArray();
    }

    return _imageArray;
}

public static System.Windows.Media.Imaging.BitmapImage CopyControlToImageSource(UIElement UserControl)
{
    ImageConverter ic = new ImageConverter();
    System.Drawing.Image img = (System.Drawing.Image)ic.ConvertFrom(GetUIElementSnapshot(UserControl));
    MemoryStream ms = new MemoryStream();
    img.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
    System.Windows.Media.Imaging.BitmapImage image = new BitmapImage();
    image.BeginInit();
    ms.Seek(0, SeekOrigin.Begin);
    image.StreamSource = ms;
    image.EndInit();            
    return image;
}

答案 1 :(得分:0)

由于我们没有您的代码并且我们不知道您的数据,因此有点难以理解您所处的位置,但这是一个简单的示例:

<Window x:Class="WpfApplication1.PrintVisualDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Print Visual Demo" Height="350" Width="300">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="0.9*" />
        <RowDefinition Height="0.2*"/>
    </Grid.RowDefinitions>
    <StackPanel Name="printPanel"
                Grid.Row="0"
                Margin="5">
        <Label Content="Sweet Baby"
            HorizontalAlignment="Center"
            FontSize="40"
            FontFamily="Calibri">
            <Label.Foreground>
                <LinearGradientBrush Opacity="1" 
                     StartPoint="0,0.5" EndPoint="1,0.5">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Color="Blue" Offset="0" />
                        <GradientStop Color="Red" Offset="0.5" />
                        <GradientStop Color="Green" Offset="1" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Label.Foreground>
        </Label>
        <Image Source="D:\Temp\baby.jpg"
                Height="150"
                Width="200">
        </Image>
    </StackPanel>
    <Button Content="Print"
                Grid.Row="1" Margin="5"
                Height="40" Width="150"
                HorizontalAlignment="Center"
                VerticalAlignment="Center" Click="Button_Click" />
</Grid>
</Window>


private void Button_Click(object sender, RoutedEventArgs e)
{
    PrintDialog pd = new PrintDialog();
    if (pd.ShowDialog() != true) return;
    pd.PrintVisual(printPanel, "Printing StackPanel...");
}

PrintVisual方法应该完成这项工作。

如果您想扩展视觉以适应页面,您可以按照以下步骤操作:

PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
if (printDlg.ShowDialog() == true)
   {
      //get selected printer capabilities
      System.Printing.PrintCapabilities capabilities = printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);

     //get scale of the print wrt to screen of WPF visual
     double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / this.ActualWidth, capabilities.PageImageableArea.ExtentHeight /
                    this.ActualHeight);

     //Transform the Visual to scale
     this.LayoutTransform = new ScaleTransform(scale, scale);

     //get the size of the printer page
     Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);

     //update the layout of the visual to the printer page size.
     this.Measure(sz);
     this.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));

      //now print the visual to printer to fit on the one page.
      printDlg.PrintVisual(this, "First Fit to Page WPF Print");

}

正如您所看到的,您需要做的就是重新测量并重新排列新尺寸的特定元素,然后您可以调用PrintVisual方法。

我希望这对你有所帮助。考虑发布更多代码或在某处上传您的项目。

相关问题