Skip to main content

C# WPF Tutorial - Print Queues And Capabilities [Intermediate]


We have taken a look at printing in WPF twice before here at SOTC - first with a simple tutorial on just getting somethingprinted, and then a more complex one on pagination. Today we are not going to focus much on the printing side of things, but more on the printer side. For example, how do you get a list of the printers available on the system? Or their capabilities? If you need the answers to those questions, then this is the tutorial for you.

Today, we will be creating a little sample application that finds all the printers on your system (both local and network printers) and lists them out. When you pick a particular printer, you will get a list of the supported page sizes for that printer. Once you pick a page size, you can then print a test page to the chosen printer at the chosen page size (and you can even pick landscape or portrait). Oh, and did I mention that we never have to show the standard print dialog for any of this?

Example App Screenshot

Ok, to start off we first have to get a hold of the list of printers attached to the system - well, actually, the list of print queues attached to the system. These are not necessarily physical printers (for example, if you have a PDF Printer installed on your system), but they do represent something you can print to. Doing this is actually pretty easy - you just have to know where to go. First, we need to add the System.Printing dll as a reference to our Visual Studio project, since most of what we need resides in that dll. Once we have that, we want to get a hold of the local printer server - which really couldn't be any easier:
var server = new PrintServer();
 
By default, when you create a new PrintServer instance, it connects to the local print server. There are other constructors on PrintServer that take things like a path to a different machine (in case you wanted the print server, for, say, some central network system), but for today, all we care about is the local server.

Now that we have the the print server, to get all the available print queues, we need to call the method GetPrintQueues. This method has a number of different signatures to make it easy for you to get the queues that you want. The no argument version of the function will generally do what you need - it will return all the queues that are attached directly to that print server (in this case, any printer attached to your computer).

For our sample application, we want to go a little bit beyond that - we want to grab any network printers as well. To do this we need to use the GetPrintQueues call that takes an array of EnumeratedPrintQueueTypes:
var server = new PrintServer();
var queues = server.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local,
     EnumeratedPrintQueueTypes.Connections});

The no argument version of GetPrintQueues is equivalent to calling this with just EnumeratedPrintQueueTypes.Local, and by adding EnumeratedPrintQueueTypes.Connections, we get network printers.

Now that we have a collection of PrintQueues, let's take a look at what we can do with one. There is a whole bunch of stuff available off of PrintQueue - you can look at printer status, what jobs are currently queued, and all sorts of other things. At the moment, though, we are interesting in printer capabilities. To get the capabilities, you call the method GetPrintCapabilities, which returns a PrintCapabilities. The capabilities class covers everything from paper size, to duplexing, even down to if the printer supports automatic stapling. Oh, and an important thing to note - the PrintCapabilities class is in the ReachFramework dll, so to do anything with capabilities, you have to add that dll to your Visual Studio project references.
var server = new PrintServer();
var queues = server.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local,
     EnumeratedPrintQueueTypes.Connections});

foreach (var queue in queues)
{
  Console.WriteLine(queue.Name);
  var capabilities = queue.GetPrintCapabilities();
  foreach (PageMediaSize size in capabilities.PageMediaSizeCapability)
  { Console.WriteLine(size.ToString()); }
  Console.WriteLine();
}
 
The code above will print out, for each printer, every paper size that that printer is capable of handling. For example, on my system:
Send To OneNote 2007
NorthAmericaLetter (816 x 1056)
NorthAmericaTabloid (1056 x 1632)
NorthAmericaLegal (816 x 1344)
ISOA3 (1122.51968503937 x 1587.40157480315)
ISOA4 (793.700787401575 x 1122.51968503937)
ISOA5 (559.370078740158 x 793.700787401575)
JISB4 (971.338582677165 x 1375.74803149606)
JISB5 (687.874015748032 x 971.338582677165)
JapanHagakiPostcard (377.952755905512 x 559.370078740158)

HP Color LaserJet CP2020 Series PCL 6
NorthAmericaLetter (816 x 1056)
NorthAmericaLegal (816 x 1344)
NorthAmericaExecutive (695.811023622047 x 1008)
ISOA3 (1122.51968503937 x 1587.40157480315)
ISOA4 (793.700787401575 x 1122.51968503937)
ISOA5 (559.370078740158 x 793.700787401575)
JISB4 (971.338582677165 x 1375.74803149606)
JISB5 (687.874015748032 x 971.338582677165)
NorthAmerica11x17 (1056 x 1632)
NorthAmericaNumber10Envelope (395.716535433071 x 912)
ISODLEnvelope (415.748031496063 x 831.496062992126)
ISOC5Envelope (612.283464566929 x 865.511811023622)
ISOB5Envelope (665.196850393701 x 944.88188976378)
NorthAmericaMonarchEnvelope (371.905511811024 x 720)
ISOA6 (396.850393700787 x 559.370078740158)
 
So that covers enough of the basics that we can start putting together the example application. First, we have most of the code behind:
using System;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace PrintQueuesExample
{
  public partial class Window1 : Window
  {
    PrintQueueCollection _Printers;

    public Window1()
    {
      _Printers = new PrintServer().GetPrintQueues(new[] {
          EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections});

      InitializeComponent();
    }

    public PrintQueueCollection Printers
    { get { return _Printers; } }

    private void PrintTestPageClick(object sender, RoutedEventArgs e)
    {
      //TODO: Print Test Page
    }
  }

  public class PrintQueueToPageSizesConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      return value == null ? null :
        ((PrintQueue)value).GetPrintCapabilities().PageMediaSizeCapability;
    }

    public object ConvertBack(object value, Type targetType, 
      object parameter, System.Globalization.CultureInfo culture)
    { throw new NotImplementedException(); }
  }
}
 
Nothing new here - this is just a reorganization of the code that we have already covered, getting it into a form that can be easily used by WPF controls (a public collection of the print queues, a converter to get from a print queue to a collection of PageMediaSizes). Now for some XAML:
<Window x:Class="PrintQueuesExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:PrintQueuesExample" x:Name="This"
    Title="Print Queue Example" Height="300" Width="300">
  <Window.Resources>
    <local:PrintQueueToPageSizesConverter x:Key="printQueueToPageSizesConverter" />
    <Canvas Margin="10" x:Key="MyPrintingExample">
      <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>
  </Window.Resources>

  <Grid Margin="4">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="Available Printers" FontSize="14" />
    <ListBox x:Name="_PrinterList" DisplayMemberPath="Name" 
             x:FieldModifier="private" Grid.Row="1" 
             ItemsSource="{Binding ElementName=This, Path=Printers}"  />
    <TextBlock Text="Available Page Sizes for Selected Printer" FontSize="14" 
               Grid.Row="2" Margin="0 5 0 0"/>      
    <ListBox Grid.Row="3" x:Name="_SizeList" x:FieldModifier="private"
             ItemsSource="{Binding ElementName=_PrinterList, Path=SelectedItem, 
                 Converter={StaticResource printQueueToPageSizesConverter}}" />
    <StackPanel Orientation="Horizontal" Margin="0 5 0 0" Grid.Row="4">
      <RadioButton Content="Portrait" x:Name="_PortraitRadio" Margin="0 0 10 0"
                   x:FieldModifier="private" IsChecked="True" />
      <RadioButton Content="Landscape" />
    </StackPanel>
    <Button Grid.Row="5" HorizontalAlignment="Right" Content="Print Test Page" 
            Click="PrintTestPageClick" />
  </Grid>
</Window>
 
Walking through this XAML, at the top we have some resources - notably the PrintQueueToPageSizesConverter and a Canvas. It might seem a little odd to have a Canvas in resources, but here it is the contents of our test page (the page that will get printed when a user clicks on the "Print Test Page" button). By putting it in the resources, we get the benefit of defining it in XAML, without the downside of it actually being in the visual tree of the Window.

Past that, we get to the meat. We have a ListView bound to the collection of print queues (Printers) that we created in the C# code. We then have a second ListView that is bound to the selected item in the first list view, using the PrintQueueToPageSizesConverter. This way, we get the collection of available PageMediaSizes for the selected print queue.

Then we have two radio buttons for portrait and landscape - these aren't attached to anything, we will just be querying their values when the user clicks "Print Test Page". And finally, we have the "Print Test Page" button, which is hooked to the method PrintTestPageClick.

Now the only thing we haven't covered so far is how to take a print queue and some selected configuration information, and actually print something. For that, we have the contents of the PrintTestPageClick method:
private void PrintTestPageClick(object sender, RoutedEventArgs e)
{
  var queue = _PrinterList.SelectedItem as PrintQueue;
  if (queue == null)
  {
    MessageBox.Show("Please select a printer.");
    return;
  }

  var size = _SizeList.SelectedItem as PageMediaSize;
  if (size == null)
  {
    MessageBox.Show("Please select a page size.");
    return;
  }

  queue.UserPrintTicket.PageMediaSize = size;
  queue.UserPrintTicket.PageOrientation = _PortraitRadio.IsChecked == true ? 
    PageOrientation.Portrait : PageOrientation.Landscape;

  var canvas = (Canvas)Resources["MyPrintingExample"];
  canvas.Measure(new Size(size.Width.Value, size.Height.Value));
  canvas.Arrange(new Rect(0, 0, canvas.DesiredSize.Width, 
      canvas.DesiredSize.Height));

  var writer = PrintQueue.CreateXpsDocumentWriter(queue);
  writer.Write(canvas);
}
 
To set up custom settings for a print job, you want to modify the UserPrintTicker on the print queue you want to print to. The UserPrintTicket is what will be looked at when the time comes to print, and is in fact what the standard print dialog modifies as the user changes setting in the dialog. So here, we want to set the PageMediaSize property to the selected size, and the PageOrientation property to the selected orientation.

One important thing to note - just because a printer is capable of a particular paper size does not mean that it currently has a tray filled with that type of paper. Choosing a paper size that a printer supports but does not have any of is valid, and the end result varies depending on the printer. Some printers will print on the nearest possible size or their default size, others will wait until the user puts in the correct size paper. Unfortunately, there isn't a way (that I know of) to query what types of paper a printer has at this very moment - all the print capability stuff is about what a printer can potentially do.

Ok, back to printing out the test page. We grab the canvas out from the resource dictionary, and measure and arrange it according to the chosen paper size. Then we use a static method on PrintQueue called CreateXpsDocumentWriter to create an XpsDocumentWriter for the print queue we want to print on. It is this XpsDocumentWriter that we can hand our canvas to to print - and lo and behold, printed output:

Sample Printout

Well, that is it for this quick introduction to PrintServers, PrintQueues, and PrintCapabilities. You can grab the Visual Studio project for the example application below if you would like to poke at the printers on your own computer.

Source Files:

Comments

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