Skip to main content

...

....

C# WPF Tutorial - Printing [Beginner]


Printing. Ugh. Every programmer I know hates writing code to do printing. Stuff never seems to appear quite how it should - the transition from the screen to the page can often be a very messy one. Printing with the Win32 API is ugly - something more akin to black magic and dark incantations than actual computer code. The .NET framework made it a little bit better in WinForms, but it was still just a very thin wrapper around the Win32 api - and it was still in the world of GDI. But now we are in the land of WPF! The land of lollipops and hope and magical wonders! So everything should be awesome, right?

Well, actually, in many ways it is. It is a lot easier to just step right up and do some printing - and in all likelihood what you see on the screen will match what comes out on paper. This is due to two major factors. The obvious one is that they made a nicer API - it is a much thicker wrapper around the Win32 black magic than what WinForms had. The more subtle factor is the fact that WPF is all vector based graphics and does not deal directly with pixels. This means that many more things scale exactly as you expect when you go from the 96 DPI world of the computer monitor to the 300-1200 DPI world of the printer.

OK, enough abstract talk. Let's do some printing. Below you can see a screenshot of a simple sample app. When the "Print Me" button is pressed, the canvas area above the button is printed.

Sample App Screenshot

And here is an example of what that printed output looks like (in this case, printed through the Windows XPS printer, so that I could take a screenshot):

Sample Printed Output

And it looks like that if you print it out as well (assuming you print it in color :P). So what is the code to do this? Well, we of course defined that interface in a couple lines of XAML:
<Window x:Class="WpfPrinting.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="360" Width="325">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Canvas Grid.Row="0" x:Name="_PrintCanvas" Margin="10">
      <TextBlock Canvas.Left="30" Canvas.Top="70">
        A bunch of text.
      </TextBlock>
      <Ellipse Width="30" Height="50" Canvas.Left="200" 
               Canvas.Top="10" Fill="Blue" />
      <Rectangle Width="100" Height="10" Canvas.Left="175" 
                 Canvas.Top="150" Fill="Red" />
      <TextBlock FontSize="100" Foreground="Green" 
                 FontWeight="Bold" Canvas.Top="150" 
                 Canvas.Left="20">
        { }
      </TextBlock>
    </Canvas>
    <Button Click="PrintClick" Grid.Row="1">
      Print Me
    </Button>
  </Grid>
</Window>
 
So now we have that canvas area defined in XAML. How do we print it? Well, that is the great thing about WPF printing - you can print any Visual! This canvas is a visual, so we can just straight up print it:
private void PrintClick(object sender, RoutedEventArgs e)
{
  PrintDialog dialog = new PrintDialog();
  if (dialog.ShowDialog() == true)
  { dialog.PrintVisual(_PrintCanvas, "My Canvas"); }
}
 
That is actually all you need to do to print a visual. Well, actually, you don't even need to show the dialog - it would just print to the default printer, then. All we are doing here is creating a PrintDialog, showing it, and (if the user clicks ok) calling PrintVisual (which takes a visual and a name).

Calling PrintVisual will always print what you give it to a single page, and it will be placed in the very upper left of the page. This means that if your visual is larger than a page, it will get clipped, and if your visual has no built in margin, it will probably clip a little bit on the edges (because most printers can't print on the very edges of pages).

For doing more complex, multiple page printing, there is another method called PrintDocument, which takes a DocumentPaginator. However, that is a topic for another tutorial.

OK, so we have seen how to print out a visual that already exists - but how do we print out something we have created on the fly? Well, all it does is take a couple extra lines:
private void PrintSomethingNew()
{
  PrintDialog dialog = new PrintDialog();
  if (dialog.ShowDialog() != true)
  { return; }

  StackPanel myPanel = new StackPanel();
  myPanel.Margin = new Thickness(15);
  Image myImage = new Image();
  myImage.Width = 128;
  myImage.Stretch = Stretch.Uniform;
  myImage.Source = new BitmapImage(new Uri("C:\\Tree.jpg", UriKind.Absolute));
  myPanel.Children.Add(myImage);
  TextBlock myBlock = new TextBlock();
  myBlock.Text = "A Great Image.";
  myBlock.TextAlignment = TextAlignment.Center;
  myPanel.Children.Add(myBlock);

  myPanel.Measure(new Size(dialog.PrintableAreaWidth,
    dialog.PrintableAreaHeight));
  myPanel.Arrange(new Rect(new Point(0, 0), 
    myPanel.DesiredSize));

  dialog.PrintVisual(myPanel, "A Great Image.");
}
 
This code prints out something that looks like this:

Second Example Printout

As you might expect, we have to do a bunch of element creation so we can get something laid out. The interesting pieces are the call to Measure and Arrange. Since these elements have never appeared on the screen, they have never been rendered. But if we don't render them, then the printout will just be blank. So we have to do our own calls to Measure and Arrange. In this case, we do a Measure using the page size of the printer we are printing to. This page size is available off of the PrintDialog, using the PrintableAreaWidth and PrintableAreaHeight properties. And once measuring is done, we arrange (using the desired size produced through the measure pass).

You might think that those two properties represent the printable area on a page, but it is actually just the exact size of the page in DIU (so for an 8.5x11 piece of paper, the width and height are 816 and 1056, respectively). This means that you still need to account for the fact that most printers can't print on the extreme edge of a page, which is why I set a margin on myPanel.

Well, that is it for the basics of printing in WPF. You can grab the the Visual Studio project for this code below. And stay tuned for more printing tutorials - delving into writing your own DocumentPaginator, as well as using the PrintQueue and the XpsDocumentWriter.

Source Files:

Comments

  1. Mudra Exchange is India's foremost secure crypto exchange platform, including SSL, TSL, and other cybersecurity elements.
    for more information visit our site www.mudra.exchange

    ReplyDelete
  2. There a lot of ways to get a message to your audience. Choosing the wrong one could lead to very bad results. A basic knowledge of what is available will help you avoid the pitfalls that have trapped so many others. In this article, we will look at three of the types of printing we might use in an effective marketing campaign. Specifically, offset printing, digital printing and hybrid printing. asia printing

    ReplyDelete

Post a Comment

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV